diff options
Diffstat (limited to 'dom/base/test/chrome')
84 files changed, 9304 insertions, 0 deletions
diff --git a/dom/base/test/chrome/bug418986-1.js b/dom/base/test/chrome/bug418986-1.js new file mode 100644 index 0000000000..7c39df0c13 --- /dev/null +++ b/dom/base/test/chrome/bug418986-1.js @@ -0,0 +1,88 @@ +/* globals chromeWindow */ +// The main test function. +var test = function (isContent) { + SimpleTest.waitForExplicitFinish(); + + SpecialPowers.pushPrefEnv({ + set: [["security.allow_eval_with_system_principal", true]], + }); + + if (!isContent) { + let { ww } = SpecialPowers.Services; + window.chromeWindow = ww.activeWindow; + } + + // The pairs of values expected to be the same when + // fingerprinting resistance is enabled. + let pairs = [ + ["screenX", 0], + ["screenY", 0], + ["mozInnerScreenX", 0], + ["mozInnerScreenY", 0], + ["screen.pixelDepth", 24], + ["screen.colorDepth", 24], + ["screen.availWidth", "innerWidth"], + ["screen.availHeight", "innerHeight"], + ["screen.left", 0], + ["screen.top", 0], + ["screen.availLeft", 0], + ["screen.availTop", 0], + ["screen.width", "innerWidth"], + ["screen.height", "innerHeight"], + ["screen.orientation.type", "'landscape-primary'"], + ["screen.orientation.angle", 0], + ["screen.mozOrientation", "'landscape-primary'"], + ["devicePixelRatio", 1], + ]; + + // checkPair: tests if members of pair [a, b] are equal when evaluated. + let checkPair = function (a, b) { + // eslint-disable-next-line no-eval + is(eval(a), eval(b), a + " should be equal to " + b); + }; + + // Returns generator object that iterates through pref values. + let prefVals = (function* () { + yield false; + yield true; + })(); + + // The main test function, runs until all pref values are exhausted. + let nextTest = function () { + let { value: prefValue, done } = prefVals.next(); + if (done) { + SimpleTest.finish(); + return; + } + SpecialPowers.pushPrefEnv( + { set: [["privacy.resistFingerprinting", prefValue]] }, + function () { + // We will be resisting fingerprinting if the pref is enabled, + // and we are in a content script (not chrome). + let resisting = prefValue && isContent; + // Check each of the pairs. + pairs.map(function ([item, onVal]) { + if (resisting) { + checkPair("window." + item, onVal); + } else if (!isContent && !item.startsWith("moz")) { + checkPair("window." + item, "chromeWindow." + item); + } + }); + if (!isContent && !resisting) { + // Hard to predict these values, but we can enforce constraints: + ok( + window.mozInnerScreenX >= chromeWindow.mozInnerScreenX, + "mozInnerScreenX" + ); + ok( + window.mozInnerScreenY >= chromeWindow.mozInnerScreenY, + "mozInnerScreenY" + ); + } + nextTest(); + } + ); + }; + + nextTest(); +}; diff --git a/dom/base/test/chrome/bug421622-referer.sjs b/dom/base/test/chrome/bug421622-referer.sjs new file mode 100644 index 0000000000..14cab00de4 --- /dev/null +++ b/dom/base/test/chrome/bug421622-referer.sjs @@ -0,0 +1,9 @@ +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/plain", false); + response.setHeader("Cache-Control", "no-cache", false); + + var referer = request.hasHeader("Referer") + ? request.getHeader("Referer") + : ""; + response.write("Referer: " + referer); +} diff --git a/dom/base/test/chrome/bug884693.sjs b/dom/base/test/chrome/bug884693.sjs new file mode 100644 index 0000000000..f2650753f2 --- /dev/null +++ b/dom/base/test/chrome/bug884693.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) { + let [status, statusText, encodedBody] = request.queryString.split("&"); + let body = decodeURIComponent(encodedBody); + response.setStatusLine(request.httpVersion, status, statusText); + response.setHeader("Content-Type", "text/xml", false); + response.setHeader("Content-Length", "" + body.length, false); + response.write(body); +} diff --git a/dom/base/test/chrome/chrome.toml b/dom/base/test/chrome/chrome.toml new file mode 100644 index 0000000000..b8439a2d2e --- /dev/null +++ b/dom/base/test/chrome/chrome.toml @@ -0,0 +1,135 @@ +[DEFAULT] +skip-if = ["os == 'android'"] +support-files = [ + "bug418986-1.js", + "clonedoc/**", + "file_bug549682.xhtml", + "file_bug616841.xhtml", + "file_bug816340.xhtml", + "file_bug990812-1.xhtml", + "file_bug990812-2.xhtml", + "file_bug990812-3.xhtml", + "file_bug990812-4.xhtml", + "file_bug990812-5.xhtml", + "file_bug1139964.xhtml", + "file_bug1209621.xhtml", + "fileconstructor_file.png", + "frame_custom_element_content.html", + "custom_element_ep.js", + "window_nsITextInputProcessor.xhtml", + "title_window.xhtml", + "window_swapFrameLoaders.xhtml", +] +prefs = ["gfx.font_rendering.fallback.async=false"] + +["test_bug120684.xhtml"] + +["test_bug206691.xhtml"] + +["test_bug289714.xhtml"] + +["test_bug339494.xhtml"] + +["test_bug357450.xhtml"] +support-files = ["../file_bug357450.js"] + +["test_bug380418.html"] + +["test_bug383430.html"] + +["test_bug418986-1.xhtml"] + +["test_bug421622.xhtml"] + +["test_bug429785.xhtml"] + +["test_bug430050.xhtml"] + +["test_bug467123.xhtml"] + +["test_bug473284.xhtml"] + +["test_bug549682.xhtml"] +skip-if = ["verify"] + +["test_bug571390.xhtml"] + +["test_bug616841.xhtml"] + +["test_bug635835.xhtml"] + +["test_bug682305.html"] + +["test_bug683852.xhtml"] + +["test_bug752226-3.xhtml"] + +["test_bug752226-4.xhtml"] + +["test_bug765993.html"] + +["test_bug780199.xhtml"] + +["test_bug780529.xhtml"] + +["test_bug800386.xhtml"] + +["test_bug816340.xhtml"] + +["test_bug884693.xhtml"] + +["test_bug914381.html"] + +["test_bug990812.xhtml"] + +["test_bug1063837.xhtml"] + +["test_bug1098074_throw_from_ReceiveMessage.xhtml"] + +["test_bug1139964.xhtml"] + +["test_bug1209621.xhtml"] + +["test_bug1339722.html"] + +["test_bug1346936.html"] + +["test_bug380418.html^headers^"] + +["test_chromeOuterWindowID.xhtml"] +support-files = ["window_chromeOuterWindowID.xhtml"] + +["test_custom_element_content.xhtml"] + +["test_custom_element_ep.xhtml"] + +["test_document-element-inserted.xhtml"] +support-files = [ + "file_document-element-inserted.xhtml", + "file_document-element-inserted-inner.xhtml", +] + +["test_domparsing.xhtml"] + +["test_fileconstructor.xhtml"] + +["test_getElementsWithGrid.html"] + +["test_input_value_set_preserve_undo.xhtml"] + +["test_nsITextInputProcessor.xhtml"] + +["test_nsITextInputProcessorCallback_at_changing_default_value_of_textarea.html"] + +["test_permission_hasValidTransientUserActivation.xhtml"] +support-files = ["../dummy.html"] + +["test_range_getClientRectsAndTexts.html"] + +["test_swapFrameLoaders.xhtml"] +skip-if = ["os == 'mac'"] # bug 1674413 + +["test_title.xhtml"] +support-files = ["file_title.xhtml"] + +["test_windowroot.xhtml"] diff --git a/dom/base/test/chrome/clonedoc/chrome.manifest b/dom/base/test/chrome/clonedoc/chrome.manifest new file mode 100644 index 0000000000..5d7e720416 --- /dev/null +++ b/dom/base/test/chrome/clonedoc/chrome.manifest @@ -0,0 +1 @@ +content clonedoc content/ diff --git a/dom/base/test/chrome/clonedoc/content/doc.xml b/dom/base/test/chrome/clonedoc/content/doc.xml new file mode 100644 index 0000000000..fdd7e7c6e0 --- /dev/null +++ b/dom/base/test/chrome/clonedoc/content/doc.xml @@ -0,0 +1,4 @@ +<?xml version='1.0' encoding='UTF-8'?> +<something> + <somethinglese/> +</something> diff --git a/dom/base/test/chrome/custom_element_ep.js b/dom/base/test/chrome/custom_element_ep.js new file mode 100644 index 0000000000..d933ecbbab --- /dev/null +++ b/dom/base/test/chrome/custom_element_ep.js @@ -0,0 +1,14 @@ +/* globals finishTest */ +class XFoo extends HTMLElement { + constructor() { + super(); + this.magicNumber = 42; + } + + connectedCallback() { + finishTest(this.magicNumber === 42); + } +} +customElements.define("x-foo", XFoo); + +document.firstChild.appendChild(document.createElement("x-foo")); diff --git a/dom/base/test/chrome/file_bug1139964.xhtml b/dom/base/test/chrome/file_bug1139964.xhtml new file mode 100644 index 0000000000..8bf7f27e0b --- /dev/null +++ b/dom/base/test/chrome/file_bug1139964.xhtml @@ -0,0 +1,60 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1139964 +--> +<window title="Mozilla Bug 1139964" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="run()"> + <label value="Mozilla Bug 1139964"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /* global messageManager, sendAsyncMessage */ + var ppm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(); + + function ok(cond, msg) { + window.arguments[0].ok(cond, msg); + } + + var msgName = "TEST:Global_has_Promise"; + + function mmScriptForPromiseTest() { + /* eslint-env mozilla/process-script */ + sendAsyncMessage("TEST:Global_has_Promise", + { + hasPromise: ("Promise" in this), + hasTextEncoder: ("TextEncoder" in this), + hasWindow: ("Window" in this), + }); + } + + function processListener(m) { + ppm.removeMessageListener(msgName, processListener); + ok(m.data.hasPromise, "ProcessGlobal should have Promise object in the global scope!"); + ok(m.data.hasTextEncoder, "ProcessGlobal should have TextEncoder object in the global scope!"); + ok(m.data.hasWindow, "ProcessGlobal should have Window object in the global scope!"); + + messageManager.addMessageListener(msgName, tabListener) + messageManager.loadFrameScript("data:,(" + mmScriptForPromiseTest.toString() + ")()", true); + } + + function tabListener(m) { + messageManager.removeMessageListener(msgName, tabListener); + ok(m.data.hasPromise, "BrowserChildGlobal should have Promise object in the global scope!"); + ok(m.data.hasTextEncoder, "BrowserChildGlobal should have TextEncoder object in the global scope!"); + ok(m.data.hasWindow, "BrowserChildGlobal should have Window object in the global scope!"); + + window.arguments[0].setTimeout(function() { this.done(); }, 0); + window.close(); + } + + function run() { + ppm.addMessageListener(msgName, processListener) + ppm.loadProcessScript("data:,(" + mmScriptForPromiseTest.toString() + ")()", true); + } + + ]]></script> + <browser type="content" src="about:blank" id="ifr"/> +</window> diff --git a/dom/base/test/chrome/file_bug1209621.xhtml b/dom/base/test/chrome/file_bug1209621.xhtml new file mode 100644 index 0000000000..3ba58975bd --- /dev/null +++ b/dom/base/test/chrome/file_bug1209621.xhtml @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1209621 +--> +<window title="Mozilla Bug 1209621" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="run()"> + <label value="Mozilla Bug 1209621"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + function ok(cond, msg) { + window.arguments[0].ok(cond, msg); + } + + function is(actual, expected, msg) { + window.arguments[0].is(actual, expected, msg); + } + + function run() { + var docshell = window.docShell; + ok(docshell, "Active window should have a DocShell"); + var treeOwner = docshell.treeOwner; + ok(treeOwner, "Active docshell should have a TreeOwner!"); + + is(treeOwner.primaryContentShell, null, + "There shouldn't be primaryContentShell because no browser has primary=true."); + is(treeOwner.primaryRemoteTab, null, + "There shouldn't be primaryRemoteTab because no remote browser has primary=true."); + is(treeOwner.primaryContentBrowsingContext, null, + "There shouldn't be primaryContentBrowsingContext because no browser has primary=true."); + + var ip = document.getElementById("inprocess"); + var remote = document.getElementById("remote"); + var remote2 = document.getElementById("remote2"); + + ip.setAttribute("primary", "true"); + ok(ip.docShell, "non-remote browser should have a DocShell."); + is(treeOwner.primaryContentShell, ip.docShell, + "primary browser should be the primaryContentShell."); + is(treeOwner.primaryRemoteTab, null, + "There shouldn't be primaryRemoteTab because no remote browser has primary=true."); + is(treeOwner.primaryContentBrowsingContext, ip.browsingContext, + "primary browsing context should be the primaryContentBrowsingContext."); + + ip.removeAttribute("primary"); + remote.setAttribute("primary", "true"); + is(treeOwner.primaryContentShell, null, + "There shouldn't be primaryContentShell because no browser has primary=true."); + var tp = remote.frameLoader.remoteTab; + ok(tp, "Remote browsers should have a remoteTab."); + is(treeOwner.primaryRemoteTab, tp, + "primary remote browser should be the primaryRemoteTab."); + is(treeOwner.primaryContentBrowsingContext, remote.browsingContext, + "primary remote browser should be the primaryContentBrowsingContext."); + + remote.removeAttribute("primary"); + is(treeOwner.primaryContentShell, null, + "There shouldn't be primaryContentShell because no browser has primary=true."); + is(treeOwner.primaryRemoteTab, null, + "There shouldn't be primaryRemoteTab because no remote browser has primary=true."); + is(treeOwner.primaryContentBrowsingContext, null, + "There shouldn't be primaryContentBrowsingContext because no browser has primary=true."); + + remote2.setAttribute("primary", "true"); + var tp2 = remote2.frameLoader.remoteTab; + ok(tp2, "Remote browsers should have a remoteTab."); + is(treeOwner.primaryRemoteTab, tp2, + "primary remote browser should be the primaryRemoteTab."); + is(treeOwner.primaryContentShell, null, + "There shouldn't be primaryContentShell because no browser has primary=true."); + is(treeOwner.primaryContentBrowsingContext, remote2.browsingContext, + "primary remote browser should be the primaryContentBrowsingContext."); + + window.arguments[0].setTimeout(function() { this.done(); }, 0); + window.close(); + } + + ]]></script> + <browser type="content" src="about:blank" id="inprocess"/> + <browser type="content" remote="true" src="about:blank" id="remote"/> + <browser type="content" remote="true" src="about:blank" id="remote2"/> +</window> diff --git a/dom/base/test/chrome/file_bug549682.xhtml b/dom/base/test/chrome/file_bug549682.xhtml new file mode 100644 index 0000000000..8ae05d38d8 --- /dev/null +++ b/dom/base/test/chrome/file_bug549682.xhtml @@ -0,0 +1,214 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=549682 +--> +<window title="Mozilla Bug 549682" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="run()"> + <label value="Mozilla Bug 549682"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /* global messageManager, sendAsyncMessage */ + var didRunAsync = false; + var didRunLocal = false; + + var global = Cc["@mozilla.org/globalmessagemanager;1"].getService(); + var ppm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(); + var cpm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(); + + function ok(cond, msg) { + window.arguments[0].ok(cond, msg); + } + + function is(actual, expected, msg) { + window.arguments[0].is(actual, expected, msg); + } + + var asyncPPML = false; + function ppmASL(m) { + asyncPPML = true; + } + var syncPPML = false; + function ppmSL(m) { + syncPPML = true; + } + ppm.addMessageListener("processmessageAsync", ppmASL); + ppm.addMessageListener("processmessageSync", ppmSL); + + cpm.sendAsyncMessage("processmessageAsync", ""); + cpm.sendSyncMessage("processmessageSync", ""); + + var asyncCPML = false; + function cpmASL(m) { + asyncCPML = true; + } + cpm.addMessageListener("childprocessmessage", cpmASL); + ppm.broadcastAsyncMessage("childprocessmessage", ""); + + function checkPMMMessages() { + ok(asyncPPML, "should have handled async message"); + ok(syncPPML, "should have handled sync message"); + ok(asyncCPML, "should have handled async message"); + ppm.removeMessageListener("processmessageAsync", ppmASL); + ppm.removeMessageListener("processmessageSync", ppmSL); + cpm.removeMessageListener("childprocessmessage", cpmASL); + } + + var globalListenerCallCount = 0; + function globalListener(m) { + ++globalListenerCallCount; + if (m.name == "sync") { + global.removeMessageListener("async", globalListener); + global.removeMessageListener("sync", globalListener); + global.removeMessageListener("global-sync", globalListener); + // Note, the result depends on what other windows are open. + ok(globalListenerCallCount >= 4, + "Global listener should have been called at least 4 times!"); + ok(didRunLocal, "Local message received."); + } + } + + function asyncL(m) { + didRunAsync = true; + is(m.name, "async", "Wrong message!"); + is(m.json.data, 1234, "Wrong data!"); + } + + function syncL(m) { + is(m.name, "sync", "Wrong message!"); + is(m.json.data, 1234, "Wrong data!"); + ok(didRunAsync, "Should have run async!"); + } + + function localL(m) { + is(m.name, "lasync", "Wrong message!"); + is(m.json.data, 2345, "Wrong data!"); + didRunLocal = true; + } + + var weakMessageReceived = false; + var weakListener = { + QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), + + receiveMessage(msg) { + if (weakMessageReceived) { + ok(false, 'Weak listener fired twice.'); + return; + } + + ok(true, 'Weak listener fired once.'); + weakMessageReceived = true; + document.getElementById('ifr').messageManager + .removeWeakMessageListener('weak', weakListener); + } + }; + + var weakListener2 = { + QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), + + receiveMessage(msg) { + ok(false, 'Should not have received a message.'); + } + }; + + function weakDoneListener() { + ok(weakMessageReceived, 'Got "weak" message.'); + finish(); + } + + function finish() { + window.arguments[0].setTimeout(function() { this.done(); }, 0); + var i = document.getElementById("ifr"); + i.remove(); // This is a crash test! + window.close(); + } + + function loadScript() { + // Async should be processed first! + messageManager.loadFrameScript("data:,\ + sendAsyncMessage('async', { data: 1234 });\ + sendSyncMessage('sync', { data: 1234 });\ + sendAsyncMessage('weak', {});\ + sendAsyncMessage('weak', {});\ + sendAsyncMessage('weakdone', {});", false); + } + + function run() { + var localmm = document.getElementById('ifr').messageManager; + + var docShell = document.getElementById('ifr').contentWindow.docShell; + ok(docShell, "Should have docshell"); + var cfmm = docShell.messageManager; + ok(cfmm, "Should have content messageManager"); + + var didGetSyncMessage = false; + function syncContinueTestFn() { + didGetSyncMessage = true; + } + localmm.addMessageListener("syncContinueTest", syncContinueTestFn); + cfmm.sendSyncMessage("syncContinueTest", {}); + localmm.removeMessageListener("syncContinueTest", syncContinueTestFn); + ok(didGetSyncMessage, "Should have got sync message!"); + + localmm.addMessageListener("lasync", localL); + localmm.loadFrameScript("data:,sendAsyncMessage('lasync', { data: 2345 })", false); + + messageManager.addMessageListener("async", asyncL); + messageManager.addMessageListener("sync", syncL); + global.addMessageListener("async", globalListener); + global.addMessageListener("sync", globalListener); + global.addMessageListener("global-sync", globalListener); + global.loadFrameScript("data:,sendSyncMessage('global-sync', { data: 1234 });", true); + var toBeRemovedScript = "data:,sendAsyncMessage('toberemoved', { data: 2345 })"; + var c = 0; + messageManager.addMessageListener("toberemoved", function() { + ++c; + is(c, 1, "Should be called only once!"); + }); + // This loads the script in the existing <browser> + messageManager.loadFrameScript(toBeRemovedScript, true); + // But it won't be loaded in the dynamically created <browser> + messageManager.removeDelayedFrameScript(toBeRemovedScript); + + var oldValue = globalListenerCallCount; + var b = document.createXULElement("browser"); + b.setAttribute("type", "content"); + document.documentElement.appendChild(b); + is(globalListenerCallCount, oldValue + 1, + "Wrong message count"); + + localmm.addWeakMessageListener('weak', weakListener); + localmm.addMessageListener('weakdone', weakDoneListener); + + // Add weakListener2 as a weak message listener, then force weakListener2 + // to be gc'ed. weakListener2 shouldn't be run. + var weakRef = Cu.getWeakReference(weakListener2); + localmm.addWeakMessageListener('weak', weakListener2); + weakListener2 = null; + + // Force a gc/cc in a loop until weakRef's referent has gone away. + function waitForWeakRefToDie() { + if (weakRef.get()) { + var mgr = Cc["@mozilla.org/memory-reporter-manager;1"] + .getService(Ci.nsIMemoryReporterManager); + mgr.minimizeMemoryUsage(waitForWeakRefToDie); + + // Print a message so that if the test hangs in a minimizeMemoryUsage + // loop, we'll be able to see it in the log. + ok(true, "waitForWeakRefToDie spinning..."); + return; + } + + setTimeout(checkPMMMessages, 0); + setTimeout(loadScript, 0); + } + + waitForWeakRefToDie(); + } + + ]]></script> + <browser type="content" src="about:blank" id="ifr"/> +</window> diff --git a/dom/base/test/chrome/file_bug616841.xhtml b/dom/base/test/chrome/file_bug616841.xhtml new file mode 100644 index 0000000000..b0512d162c --- /dev/null +++ b/dom/base/test/chrome/file_bug616841.xhtml @@ -0,0 +1,63 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=616841 +--> +<window title="Mozilla Bug 616841" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="start()"> + <label value="Mozilla Bug 616841"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /* global messageManager, sendAsyncMessage */ + const FRAME_SCRIPT = +"data:,addMessageListener(\n"+ +" 'cmp',\n"+ +" function (m) {\n"+ +" sendAsyncMessage('cmp', { i: m.json.i,\n"+ +" cmp: m.json.a.localeCompare(m.json.b) });\n"+ +" });\n"+ +"sendAsyncMessage('contentReady');"; + + var toCompare = [ [ "C", "D" ], + [ "D", "C" ], + [ "\u010C", "D" ], + [ "D", "\u010C" ] ]; + var nCmps = 0; + + function recvContentReady(m) { + for (var i = 0; i < toCompare.length; ++i) { + var pair = toCompare[i]; + messageManager.broadcastAsyncMessage("cmp", + { i, a: pair[0], b: pair[1] }); + } + } + + function recvCmp(m) { + var i = m.json.i, cmp = m.json.cmp; + var pair = toCompare[i]; + window.arguments[0].is(pair[0].localeCompare(pair[1]), cmp, "localeCompare returned same result in frame script"); + + if (toCompare.length == ++nCmps) { + messageManager.removeMessageListener("cmp", recvCmp); + finish(); + } + } + + function start() { + messageManager.addMessageListener("contentReady", recvContentReady); + messageManager.addMessageListener("cmp", recvCmp); + messageManager.loadFrameScript(FRAME_SCRIPT, true); + } + + function finish() { + window.arguments[0].setTimeout(function() { this.done(); }, 0); + window.close(); + } + + ]]></script> + + <browser id="browser" type="content" src="about:blank"/> +</window> diff --git a/dom/base/test/chrome/file_bug816340.xhtml b/dom/base/test/chrome/file_bug816340.xhtml new file mode 100644 index 0000000000..0b1a9adcab --- /dev/null +++ b/dom/base/test/chrome/file_bug816340.xhtml @@ -0,0 +1,69 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=816340 +--> +<window title="Mozilla Bug 816340" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="start();"> + <label value="Mozilla Bug 816340"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + function ok(val, msg) { + window.arguments[0].ok(val, msg); + } + + var elems = + [ + "input", + "textarea", + "select", + "button", + ]; + + var chromeDidGetEvent = false; + function chromeListener() { + chromeDidGetEvent = true; + } + + function testElement(el, disabled, contentShouldGetEvent) { + chromeDidGetEvent = false; + var b = document.getElementById("browser"); + b.contentDocument.body.innerHTML = null; + var e = b.contentDocument.createElement(el); + if (disabled) { + e.setAttribute("disabled", "true"); + } + b.contentDocument.body.appendChild(e); + var contentDidGetEvent = false; + b.contentDocument.body.addEventListener("foo", + function() { contentDidGetEvent = true }, true); + + b.addEventListener("foo", chromeListener, true); + e.dispatchEvent(new Event("foo")); + b.removeEventListener("foo", chromeListener, true); + ok(contentDidGetEvent == contentShouldGetEvent, "content: " + el + (disabled ? " disabled" : "")); + ok(chromeDidGetEvent, "chrome: " + el + (disabled ? " disabled" : "")); + } + + function start() { + // Test common element. + testElement("div", false, true); + testElement("div", true, true); + + for (var i = 0; i < elems.length; ++i) { + testElement(elems[i], false, true); + testElement(elems[i], true, false); + } + ok(true, "done"); + window.arguments[0].setTimeout(function() { this.done(); }, 0); + window.close(); + } + + ]]></script> + + <browser id="browser" type="content" src="about:blank"/> +</window> diff --git a/dom/base/test/chrome/file_bug990812-1.xhtml b/dom/base/test/chrome/file_bug990812-1.xhtml new file mode 100644 index 0000000000..8b8da3d136 --- /dev/null +++ b/dom/base/test/chrome/file_bug990812-1.xhtml @@ -0,0 +1,61 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=990812 +--> +<window title="Mozilla Bug 990812" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="start();"> + <label value="Mozilla Bug 990812"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /* global messageManager, sendAsyncMessage, getGroupMessageManager */ + var FRAME_SCRIPT_GLOBAL = "data:,sendSyncMessage('test', 'global')"; + var FRAME_SCRIPT_WINDOW = "data:,sendSyncMessage('test', 'window')"; + var FRAME_SCRIPT_GROUP = "data:,sendSyncMessage('test', 'group')"; + + var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(); + + function is(val, exp, msg) { + window.arguments[0].is(val, exp, msg); + } + + /** + * Ensures that delayed frame scripts are loaded in the expected order. + * Global frame scripts will be loaded before delayed frame scripts from + * window message managers. The latter will be loaded before group message + * manager frame scripts. + */ + function start() { + globalMM.loadFrameScript(FRAME_SCRIPT_GLOBAL, true); + messageManager.loadFrameScript(FRAME_SCRIPT_WINDOW, true); + getGroupMessageManager("test").loadFrameScript(FRAME_SCRIPT_GROUP, true); + + var order = ["global", "window", "group"]; + + messageManager.addMessageListener("test", function onMessage(msg) { + var next = order.shift(); + window.arguments[0].is(msg.data, next, "received test:" + next); + + if (!order.length) { + window.arguments[0].setTimeout(function() { this.next(); }); + window.close(); + } + }); + + var browser = document.createXULElement("browser"); + browser.setAttribute("messagemanagergroup", "test"); + browser.setAttribute("src", "about:mozilla"); + browser.setAttribute("type", "content"); + document.documentElement.appendChild(browser); + + globalMM.removeDelayedFrameScript(FRAME_SCRIPT_GLOBAL); + messageManager.removeDelayedFrameScript(FRAME_SCRIPT_WINDOW); + getGroupMessageManager("test").removeDelayedFrameScript(FRAME_SCRIPT_GROUP); + } + + ]]></script> + +</window> diff --git a/dom/base/test/chrome/file_bug990812-2.xhtml b/dom/base/test/chrome/file_bug990812-2.xhtml new file mode 100644 index 0000000000..e13b47f589 --- /dev/null +++ b/dom/base/test/chrome/file_bug990812-2.xhtml @@ -0,0 +1,56 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=990812 +--> +<window title="Mozilla Bug 990812" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="start();"> + <label value="Mozilla Bug 990812"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /* global messageManager, sendAsyncMessage, getGroupMessageManager */ + var FRAME_SCRIPT = "data:,sendAsyncMessage('test')"; + var order = ["group", "window", "global"]; + + var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(); + + function is(val, exp, msg) { + window.arguments[0].is(val, exp, msg); + } + + function promiseMessage(type, mm) { + return new Promise(function (resolve) { + mm.addMessageListener("test", function onMessage() { + mm.removeMessageListener("test", onMessage); + is(type, order.shift(), "correct type " + type); + resolve(); + }); + }); + } + + /** + * Tests that async messages sent by frame scripts bubble up as expected, + * passing the group, window, and global message managers in that order. + */ + function start() { + var global = promiseMessage("global", globalMM); + var window = promiseMessage("window", messageManager); + var group = promiseMessage("group", getGroupMessageManager("test")); + + var browser = document.querySelector("browser"); + browser.messageManager.loadFrameScript(FRAME_SCRIPT, true); + + Promise.all([global, window, group]).then(function () { + self.arguments[0].setTimeout(function() { this.next(); }); + self.close(); + }); + } + + ]]></script> + + <browser messagemanagergroup="test" type="content" src="about:mozilla" /> + +</window> diff --git a/dom/base/test/chrome/file_bug990812-3.xhtml b/dom/base/test/chrome/file_bug990812-3.xhtml new file mode 100644 index 0000000000..1f3e1d69f2 --- /dev/null +++ b/dom/base/test/chrome/file_bug990812-3.xhtml @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=990812 +--> +<window title="Mozilla Bug 990812" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="start();"> + <label value="Mozilla Bug 990812"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /* global messageManager, sendAsyncMessage, getGroupMessageManager */ + var FRAME_SCRIPT = "data:,addMessageListener('test', function (msg) {" + + "sendSyncMessage('test', msg.data)})"; + + var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(); + + function is(val, exp, msg) { + window.arguments[0].is(val, exp, msg); + } + + function promiseMessage(type, mm) { + var order = [type, "window", "global"]; + + return new Promise(function (resolve) { + mm.addMessageListener("test", function onMessage(msg) { + is(msg.data, order.shift(), "correct message " + msg.data); + + if (!order.length) { + mm.removeMessageListener("test", onMessage); + resolve(); + } + }); + }); + } + + /** + * Ensures that broadcasting an async message does only reach descendants + * of a specific message manager and respects message manager groups. + */ + function start() { + var mm1 = document.querySelector("browser").messageManager; + var promise1 = promiseMessage("group1", mm1); + mm1.loadFrameScript(FRAME_SCRIPT, true); + + var mm2 = document.querySelector("browser + browser").messageManager; + var promise2 = promiseMessage("group2", mm2); + mm2.loadFrameScript(FRAME_SCRIPT, true); + + getGroupMessageManager("test1").broadcastAsyncMessage("test", "group1"); + getGroupMessageManager("test2").broadcastAsyncMessage("test", "group2"); + messageManager.broadcastAsyncMessage("test", "window"); + globalMM.broadcastAsyncMessage("test", "global"); + + Promise.all([promise1, promise2]).then(function () { + window.arguments[0].setTimeout(function() { this.next(); }); + window.close(); + }); + } + + ]]></script> + + <browser messagemanagergroup="test1" type="content" src="about:mozilla" /> + <browser messagemanagergroup="test2" type="content" src="about:mozilla" /> + +</window> diff --git a/dom/base/test/chrome/file_bug990812-4.xhtml b/dom/base/test/chrome/file_bug990812-4.xhtml new file mode 100644 index 0000000000..1c16ceb02c --- /dev/null +++ b/dom/base/test/chrome/file_bug990812-4.xhtml @@ -0,0 +1,63 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=990812 +--> +<window title="Mozilla Bug 990812" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="start();"> + <label value="Mozilla Bug 990812"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /* global messageManager, sendAsyncMessage, getGroupMessageManager */ + var FRAME_SCRIPT1 = "data:,addMessageListener('test', function () {" + + "sendSyncMessage('test', 'frame1')})"; + var FRAME_SCRIPT2 = "data:,addMessageListener('test', function () {" + + "sendSyncMessage('test', 'frame2')})"; + + var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(); + + function is(val, exp, msg) { + window.arguments[0].is(val, exp, msg); + } + + function promiseMessage(type, mm) { + return new Promise(function (resolve) { + mm.addMessageListener("test", function onMessage(msg) { + mm.removeMessageListener("test", onMessage); + is(msg.data, type, "correct message " + type); + resolve(); + }); + }); + } + + /** + * Tests that swapping docShells works as expected wrt to groups. + */ + function start() { + var browser1 = document.querySelector("browser"); + browser1.messageManager.loadFrameScript(FRAME_SCRIPT1, true); + + var browser2 = document.querySelector("browser + browser"); + browser2.messageManager.loadFrameScript(FRAME_SCRIPT2, true); + + var promise1 = promiseMessage("frame2", getGroupMessageManager("test1")); + var promise2 = promiseMessage("frame1", getGroupMessageManager("test2")); + + browser1.swapFrameLoaders(browser2); + messageManager.broadcastAsyncMessage("test"); + + Promise.all([promise1, promise2]).then(function () { + window.arguments[0].setTimeout(function() { this.next(); }); + window.close(); + }); + } + + ]]></script> + + <browser messagemanagergroup="test1" type="content" src="about:mozilla" /> + <browser messagemanagergroup="test2" type="content" src="about:mozilla" /> + +</window> diff --git a/dom/base/test/chrome/file_bug990812-5.xhtml b/dom/base/test/chrome/file_bug990812-5.xhtml new file mode 100644 index 0000000000..8c418492a1 --- /dev/null +++ b/dom/base/test/chrome/file_bug990812-5.xhtml @@ -0,0 +1,74 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=990812 +--> +<window title="Mozilla Bug 990812" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="start();"> + <label value="Mozilla Bug 990812"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /* global messageManager, sendAsyncMessage, getGroupMessageManager */ + var FRAME_SCRIPT1 = "data:,addMessageListener('test', function () {" + + "sendSyncMessage('test', 'group1')})"; + var FRAME_SCRIPT2 = "data:,addMessageListener('test', function () {" + + "sendSyncMessage('test', 'group2')})"; + + var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(); + + function is(val, exp, msg) { + window.arguments[0].is(val, exp, msg); + } + + function promiseTwoMessages(type, mm) { + var numLeft = 2; + + return new Promise(function (resolve) { + mm.addMessageListener("test", function onMessage(msg) { + is(msg.data, type, "correct message " + type); + + if (--numLeft == 0) { + mm.removeMessageListener("test", onMessage); + resolve(); + } + }); + }); + } + + /** + * This test ensures that having multiple message manager groups with + * multiple frame loaders in those works as expected. For a specific + * group message manager, frame scripts should only be loaded by its + * descendants and messages should only be received by and from those + * child message managers. + */ + function start() { + var gmm1 = getGroupMessageManager("test1"); + gmm1.loadFrameScript(FRAME_SCRIPT1, true); + + var gmm2 = getGroupMessageManager("test2"); + gmm2.loadFrameScript(FRAME_SCRIPT2, true); + + var promise1 = promiseTwoMessages("group1", gmm1); + var promise2 = promiseTwoMessages("group2", gmm2); + + messageManager.broadcastAsyncMessage("test"); + + Promise.all([promise1, promise2]).then(function () { + window.arguments[0].setTimeout(function() { this.next(); }); + window.close(); + }); + } + + ]]></script> + + <browser messagemanagergroup="test1" type="content" src="about:mozilla" /> + <browser messagemanagergroup="test1" type="content" src="about:mozilla" /> + + <browser messagemanagergroup="test2" type="content" src="about:mozilla" /> + <browser messagemanagergroup="test2" type="content" src="about:mozilla" /> + +</window> diff --git a/dom/base/test/chrome/file_bug990812.xhtml b/dom/base/test/chrome/file_bug990812.xhtml new file mode 100644 index 0000000000..02662d5749 --- /dev/null +++ b/dom/base/test/chrome/file_bug990812.xhtml @@ -0,0 +1,55 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=990812 +--> +<window title="Mozilla Bug 990812" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="start();"> + <label value="Mozilla Bug 990812"/> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /* global messageManager, sendAsyncMessage, getGroupMessageManager */ + var FRAME_SCRIPT_GLOBAL = "data:,sendSyncMessage('test', 'global')"; + var FRAME_SCRIPT_WINDOW = "data:,sendSyncMessage('test', 'window')"; + var FRAME_SCRIPT_GROUP = "data:,sendSyncMessage('test', 'group')"; + + var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(); + + function is(val, exp, msg) { + opener.wrappedJSObject.is(val, exp, msg); + } + + function start() { + globalMM.loadFrameScript(FRAME_SCRIPT_GLOBAL, true); + messageManager.loadFrameScript(FRAME_SCRIPT_WINDOW, true); + getGroupMessageManager("test").loadFrameScript(FRAME_SCRIPT_GROUP, true); + + var order = ["global", "window", "group"]; + + messageManager.addMessageListener("test", function onMessage(msg) { + var next = order.shift(); + opener.wrappedJSObject.is(msg.data, next, "received test:" + next); + + if (!order.length) { + opener.setTimeout("next()"); + window.close(); + } + }); + + var browser = document.createXULElement("browser"); + browser.setAttribute("messagemanagergroup", "test"); + browser.setAttribute("src", "about:mozilla"); + browser.setAttribute("type", "content"); + document.documentElement.appendChild(browser); + + globalMM.removeDelayedFrameScript(FRAME_SCRIPT_GLOBAL); + messageManager.removeDelayedFrameScript(FRAME_SCRIPT_WINDOW); + getGroupMessageManager("test").removeDelayedFrameScript(FRAME_SCRIPT_GROUP); + } + + ]]></script> + +</window> diff --git a/dom/base/test/chrome/file_document-element-inserted-inner.xhtml b/dom/base/test/chrome/file_document-element-inserted-inner.xhtml new file mode 100644 index 0000000000..2088e2789a --- /dev/null +++ b/dom/base/test/chrome/file_document-element-inserted-inner.xhtml @@ -0,0 +1 @@ +<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'></window>
\ No newline at end of file diff --git a/dom/base/test/chrome/file_document-element-inserted.xhtml b/dom/base/test/chrome/file_document-element-inserted.xhtml new file mode 100644 index 0000000000..d67df13df7 --- /dev/null +++ b/dom/base/test/chrome/file_document-element-inserted.xhtml @@ -0,0 +1,3 @@ +<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'> + <iframe src='file_document-element-inserted-inner.xhtml'></iframe> +</window>
\ No newline at end of file diff --git a/dom/base/test/chrome/file_title.xhtml b/dom/base/test/chrome/file_title.xhtml new file mode 100644 index 0000000000..d1b04418aa --- /dev/null +++ b/dom/base/test/chrome/file_title.xhtml @@ -0,0 +1 @@ +<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' title='Test'/> diff --git a/dom/base/test/chrome/fileconstructor_file.png b/dom/base/test/chrome/fileconstructor_file.png Binary files differnew file mode 100644 index 0000000000..51e8aaf38c --- /dev/null +++ b/dom/base/test/chrome/fileconstructor_file.png diff --git a/dom/base/test/chrome/frame_custom_element_content.html b/dom/base/test/chrome/frame_custom_element_content.html new file mode 100644 index 0000000000..aa1d75863d --- /dev/null +++ b/dom/base/test/chrome/frame_custom_element_content.html @@ -0,0 +1,5 @@ +<html> +<body> +<x-bar></x-bar> +</body> +</html> diff --git a/dom/base/test/chrome/nochrome_bug1346936.html b/dom/base/test/chrome/nochrome_bug1346936.html new file mode 100644 index 0000000000..158b20c884 --- /dev/null +++ b/dom/base/test/chrome/nochrome_bug1346936.html @@ -0,0 +1,3 @@ +<!DOCTYPE HTML> +<html> +</html> diff --git a/dom/base/test/chrome/nochrome_bug1346936.js b/dom/base/test/chrome/nochrome_bug1346936.js new file mode 100644 index 0000000000..a84113e1e1 --- /dev/null +++ b/dom/base/test/chrome/nochrome_bug1346936.js @@ -0,0 +1,4 @@ +//# sourceMappingURL=bar.js.map + +// Define a single function to prevent script source from being gc'd +function foo() {} diff --git a/dom/base/test/chrome/nochrome_bug1346936.js^headers^ b/dom/base/test/chrome/nochrome_bug1346936.js^headers^ new file mode 100644 index 0000000000..812264590d --- /dev/null +++ b/dom/base/test/chrome/nochrome_bug1346936.js^headers^ @@ -0,0 +1 @@ +SourceMap: foo.js.map diff --git a/dom/base/test/chrome/nochrome_bug765993.html b/dom/base/test/chrome/nochrome_bug765993.html new file mode 100644 index 0000000000..158b20c884 --- /dev/null +++ b/dom/base/test/chrome/nochrome_bug765993.html @@ -0,0 +1,3 @@ +<!DOCTYPE HTML> +<html> +</html> diff --git a/dom/base/test/chrome/nochrome_bug765993.js b/dom/base/test/chrome/nochrome_bug765993.js new file mode 100644 index 0000000000..a84113e1e1 --- /dev/null +++ b/dom/base/test/chrome/nochrome_bug765993.js @@ -0,0 +1,4 @@ +//# sourceMappingURL=bar.js.map + +// Define a single function to prevent script source from being gc'd +function foo() {} diff --git a/dom/base/test/chrome/nochrome_bug765993.js^headers^ b/dom/base/test/chrome/nochrome_bug765993.js^headers^ new file mode 100644 index 0000000000..8efacff3c8 --- /dev/null +++ b/dom/base/test/chrome/nochrome_bug765993.js^headers^ @@ -0,0 +1 @@ +X-SourceMap: foo.js.map diff --git a/dom/base/test/chrome/test_bug1063837.xhtml b/dom/base/test/chrome/test_bug1063837.xhtml new file mode 100644 index 0000000000..794cf1c72c --- /dev/null +++ b/dom/base/test/chrome/test_bug1063837.xhtml @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=206691 +--> +<window title="Mozilla Bug 1063837" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1063837" + target="_blank">Mozilla Bug 1063837</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 1063837 **/ + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(function() { + var xhr = new XMLHttpRequest(); + xhr.open("GET", location, false); + xhr.onload = function() { + ok(xhr.responseXML, "We should have response content!"); + var principal = xhr.responseXML.nodePrincipal; + ok(principal.schemeIs("moz-nullprincipal"), "The response document should have a null principal"); + SimpleTest.finish(); + } + xhr.send(); + }); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xhtml b/dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xhtml new file mode 100644 index 0000000000..769c10b370 --- /dev/null +++ b/dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xhtml @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1098074 +--> +<window title="Mozilla Bug 1098074" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="start();"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 1098074 **/ + SimpleTest.waitForExplicitFinish(); + SimpleTest.expectUncaughtException(); + + // Tell the test to expect exactly one console error with the given parameters, + // with SimpleTest.finish as a continuation function. + SimpleTest.monitorConsole(SimpleTest.finish, [{errorMessage: new RegExp('acopia')}]); + + var globalMM = Cc["@mozilla.org/globalmessagemanager;1"].getService(); + globalMM.addMessageListener("flimfniffle", function onMessage(msg) { + globalMM.removeMessageListener("flimfniffle", onMessage); + is(msg.data, "teufeltor", "correct message"); + + // Cleanup the monitor after we throw. + SimpleTest.executeSoon(SimpleTest.endMonitorConsole); + + // eslint-disable-next-line no-throw-literal + throw "acopia"; + }); + + function start() { + globalMM.loadFrameScript("data:,sendAsyncMessage('flimfniffle', 'teufeltor')", true); + } + + ]]> + </script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1098074" + target="_blank">Mozilla Bug 1098074</a> + </body> +</window> diff --git a/dom/base/test/chrome/test_bug1139964.xhtml b/dom/base/test/chrome/test_bug1139964.xhtml new file mode 100644 index 0000000000..8b1b36fa64 --- /dev/null +++ b/dom/base/test/chrome/test_bug1139964.xhtml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1139964 +--> +<window title="Mozilla Bug 1139964" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1139964" + target="_blank">Mozilla Bug 1139964</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 1139964 **/ + SimpleTest.waitForExplicitFinish(); + + function done() { + SimpleTest.finish(); + } + + addLoadEvent(function() { + window.openDialog("file_bug1139964.xhtml", "", "chrome,noopener", window); + }); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_bug120684.xhtml b/dom/base/test/chrome/test_bug120684.xhtml new file mode 100644 index 0000000000..08e9b28cfe --- /dev/null +++ b/dom/base/test/chrome/test_bug120684.xhtml @@ -0,0 +1,80 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=120684 +--> +<window title="Mozilla Bug 120684" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=120684" + target="_blank">Mozilla Bug 120684</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 120684 **/ + + var list = new ChromeNodeList(); + is(list.length, 0, "Length should be initially 0."); + + ok(NodeList.isInstance(list), "ChromeNodeList object should be an instance of NodeList."); + + try { + list.append(document); + ok(false, "should have throw!"); + } catch(ex) { + ok(true, "ChromeNodeList supports only nsIContent objects for now."); + } + + try { + list.remove(document); + ok(false, "should have throw!"); + } catch(ex) { + ok(true, "ChromeNodeList supports only nsIContent objects for now."); + } + is(list.length, 0, "Length should be 0."); + + list.append(document.documentElement); + is(list.length, 1, "Length should be 1."); + is(list[0], document.documentElement); + is(list[1], undefined); + + // Removing element which isn't in the list shouldn't do anything. + list.remove(document.createXULElement("foo")); + is(list.length, 1, "Length should be 1."); + is(list[0], document.documentElement); + + list.remove(document.documentElement); + is(list.length, 0, "Length should be 0."); + is(list[0], undefined); + + var e1 = document.createXULElement("foo"); + var e2 = document.createXULElement("foo"); + var e3 = document.createXULElement("foo"); + + list.append(e1); + list.append(e2); + list.append(e3); + + is(list[0], e1); + is(list[1], e2); + is(list[2], e3); + is(list.length, 3); + + list.remove(e2); + is(list[0], e1); + is(list[1], e3); + is(list[2], undefined); + is(list.length, 2); + + // A leak test. + list.expando = list; + + ]]> + </script> +</window> diff --git a/dom/base/test/chrome/test_bug1209621.xhtml b/dom/base/test/chrome/test_bug1209621.xhtml new file mode 100644 index 0000000000..947606b638 --- /dev/null +++ b/dom/base/test/chrome/test_bug1209621.xhtml @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1209621 +--> +<window title="Mozilla Bug 1209621" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1209621" + target="_blank">Mozilla Bug 1209621</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 1209621 **/ + + SimpleTest.waitForExplicitFinish(); + + function done() { + SimpleTest.finish(); + } + + addLoadEvent(function() { + window.openDialog("file_bug1209621.xhtml", "", "chrome,noopener", window); + }); + + ]]> + </script> +</window> diff --git a/dom/base/test/chrome/test_bug1339722.html b/dom/base/test/chrome/test_bug1339722.html new file mode 100644 index 0000000000..d8d95f1faa --- /dev/null +++ b/dom/base/test/chrome/test_bug1339722.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<html> + <!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1339722 +--> + <head> + <meta charset="utf-8" /> + <title>Test for Bug 1339722</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://global/skin" /> + <link + rel="stylesheet" + type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" + /> + <script type="application/javascript"> + /** + * Test for Bug 1339722 + * 1. Wait for "http-on-modify-request" or document-on-modify-request for the + * iframe load. + * 2. In the observer, access it's window proxy to trigger DOMWindowCreated. + * 3. In the event handler, delete the iframe so that the frameloader would be + * destroyed in the middle of ReallyStartLoading. + * 4. Verify that it doesn't crash. + **/ + + // This topic used to be http-on-useragent-request, but that got removed in + // bug 1513574. on-modify-request is called around the same time, and should + // behave similarly. + const TOPIC = "document-on-modify-request"; + let win; + const observe = (subject, topic, data) => { + info("Got " + topic); + Services.obs.removeObserver(observe, TOPIC); + + // Query window proxy so it triggers DOMWindowCreated. + let channel; + try { + // We need to QI nsIHttpChannel in order to load the interface's + // methods / attributes for later code that could assume we are dealing + // with a nsIHttpChannel. + channel = subject.QueryInterface(Ci.nsIHttpChannel); + } catch (e) { + channel = subject.QueryInterface(Ci.nsIIdentChannel); + } + win = channel.notificationCallbacks.getInterface(Ci.mozIDOMWindowProxy); + }; + + Services.obs.addObserver(observe, TOPIC); + + let docShell = SpecialPowers.wrap(window).docShell; + docShell.chromeEventHandler.addEventListener( + "DOMWindowCreated", + function handler(e) { + info("Got DOMWindowCreated"); + let iframe = document.getElementById("testFrame"); + is(e.target, iframe.contentDocument, "verify event target"); + + // Remove the iframe to cause frameloader destroy. + iframe.remove(); + setTimeout($ => { + ok(!document.getElementById("testFrame"), "verify iframe removed"); + SimpleTest.finish(); + }, 0); + }, + { once: true } + ); + + SimpleTest.waitForExplicitFinish(); + </script> + </head> + <body> + <a + target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1339722" + >Mozilla Bug 1339722</a + > + <p id="display"></p> + <div id="content" style="display: none;"></div> + <pre id="test"> + <div id="frameContainer"> + <iframe id="testFrame" src="http://www.example.com"></iframe> + </div> +</pre> + </body> +</html> diff --git a/dom/base/test/chrome/test_bug1346936.html b/dom/base/test/chrome/test_bug1346936.html new file mode 100644 index 0000000000..2c61c65237 --- /dev/null +++ b/dom/base/test/chrome/test_bug1346936.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1346936 +--> +<head> + <title>Test for Bug 1346936</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1346936">Mozilla Bug 1346936</a> +<style type="text/css"> +#link1 a { user-select:none; } +</style> +<div id="link1"><a href="http://www.mozilla.org/">link1</a></div> +<div id="link2"><a href="http://www.mozilla.org/">link2</a></div> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 1346936 **/ + +const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs"); +addDebuggerToGlobal(globalThis); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + var iframe = document.createElement("iframe"); + iframe.src = "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug1346936.html"; + iframe.onload = function() { + var script = iframe.contentWindow.document.createElement("script"); + script.src = "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug1346936.js"; + script.onload = function() { + var dbg = new Debugger(iframe.contentWindow); + ok(dbg, "Should be able to create debugger"); + + var scripts = dbg.findScripts({ + url: "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug1346936.js", + }); + ok(scripts.length, "Should be able to find script"); + + is(scripts[0].source.sourceMapURL, "foo.js.map"); + SimpleTest.finish(); + }; + + iframe.contentWindow.document.body.appendChild(script); + }; + + document.body.appendChild(iframe); +}; + +</script> +</pre> +</body> +</html> diff --git a/dom/base/test/chrome/test_bug206691.xhtml b/dom/base/test/chrome/test_bug206691.xhtml new file mode 100644 index 0000000000..16a27762ac --- /dev/null +++ b/dom/base/test/chrome/test_bug206691.xhtml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=206691 +--> +<window title="Mozilla Bug 206691" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=206691" + target="_blank">Mozilla Bug 206691</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 206691 **/ + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(function() { + var xhr = new XMLHttpRequest(); + xhr.open("GET", location, false); + xhr.send(); + ok(xhr.responseText, "We should have response content!"); + SimpleTest.finish(); + }); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_bug289714.xhtml b/dom/base/test/chrome/test_bug289714.xhtml new file mode 100644 index 0000000000..4b4cc6fb84 --- /dev/null +++ b/dom/base/test/chrome/test_bug289714.xhtml @@ -0,0 +1,33 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=289714 +--> +<window title="Mozilla Bug 289714" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=289714" + target="_blank">Mozilla Bug 289714</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /** Test for Bug 289714 **/ + + SimpleTest.waitForExplicitFinish(); + + let xhr = new XMLHttpRequest(); + xhr.responseType = "document"; + xhr.open("GET", "data:text/xml,<xml"); + ok(xhr.channel !== undefined, "System XHRs should be privileged"); + xhr.onload = () => { + ok(xhr.responseXML !== null, "System XHRs should yield <parsererrors>"); + SimpleTest.finish(); + }; + xhr.send(); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_bug339494.xhtml b/dom/base/test/chrome/test_bug339494.xhtml new file mode 100644 index 0000000000..203f6e644d --- /dev/null +++ b/dom/base/test/chrome/test_bug339494.xhtml @@ -0,0 +1,73 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=339494 +--> +<window title="Mozilla Bug 339494" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=339494">Mozilla Bug 339494</a> +<p id="display"></p> +<div id="content" style="display: none"> + <xul:hbox id="d"/> + <xul:hbox id="s"/> +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> + +/** Test for Bug 339494 **/ +SimpleTest.waitForExplicitFinish(); + +(async function runTests() { + function promiseFlushingMutationObserver() { + return new Promise(SimpleTest.executeSoon); + } + + await (async function() { + const d = document.getElementById("d"); + + d.setAttribute("hhh", "testvalue"); + await promiseFlushingMutationObserver(); + + const observer = new MutationObserver((aMutationList, aObserver) => { + ok(!d.hasAttribute("hhh"), "Value check 1. There should be no value"); + isnot(d.getAttribute("hhh"), "testvalue", "Value check 2"); + aObserver.disconnect(); + d.removeAttribute("hhh"); + ok(true, "Reachability. We shouldn't have crashed"); + }); + observer.observe(d, { attributes: true }); + d.removeAttribute("hhh"); + await promiseFlushingMutationObserver(); + })(); + + await (async function() { + const s = document.getElementById("s"); + + s.setAttribute("ggg", "testvalue"); + await promiseFlushingMutationObserver(); + + const observer = new MutationObserver((aMutationList, aObserver) => { + ok(s.hasAttribute("ggg"), "Value check 3. There should be a value"); + isnot(s.getAttribute("ggg"), "testvalue", "Value check 4"); + is(s.getAttribute("ggg"), "othervalue", "Value check 5"); + }); + observer.observe(s, { attributes: true }); + s.setAttribute("ggg", "othervalue"); + await promiseFlushingMutationObserver(); + observer.disconnect(); + })(); + + SimpleTest.finish(); +})(); +</script> + +</window> diff --git a/dom/base/test/chrome/test_bug357450.xhtml b/dom/base/test/chrome/test_bug357450.xhtml new file mode 100644 index 0000000000..7723364ecc --- /dev/null +++ b/dom/base/test/chrome/test_bug357450.xhtml @@ -0,0 +1,56 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=357450 +--> + +<window title="Mozilla Bug 357450" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <!-- This file is shared with non-chrome tests --> + <script type="text/javascript" src="file_bug357450.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> + +<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=357450" + target="_blank">Mozilla Bug 357450</a> + +<p id="display"></p> + +<div id="content" style="display: block"> + <a class="classtest" name="nametest">hmm</a> + <b class="foo">hmm</b> + <b id="test1" class="test1">hmm</b> + <b id="test2" class="test2">hmm</b> + <b id="int-class" class="1">hmm</b> + <div id="example"> + <p id="p1" class="aaa bbb"/> + <p id="p2" class="aaa ccc"/> + <p id="p3" class="bbb ccc"/> + </div> +</div> +<pre id="test"> +</pre> +</body> + +<svg xmlns="http://www.w3.org/2000/svg" + height="100" width="100" style="float:left"> + + <path d="M38,38c0-12,24-15,23-2c0,9-16,13-16,23v7h11v-4c0-9,17-12,17-27c-2-22-45-22-45,3zM45,70h11v11h-11z" fill="#371"/> + + <circle cx="50" cy="50" r="45" class="classtest" + fill="none" stroke="#371" stroke-width="10"/> + +</svg> + +<xul:label class="classtest" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + hmm +</xul:label> + + +</window> diff --git a/dom/base/test/chrome/test_bug380418.html b/dom/base/test/chrome/test_bug380418.html new file mode 100644 index 0000000000..eb3f8d3042 --- /dev/null +++ b/dom/base/test/chrome/test_bug380418.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=380418 --> +<head> + <title>Test for Bug 380418</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=380418">Mozilla Bug 380418</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + + var xhrPath = "http://mochi.test:8888" + + window.location.pathname.substring("/content".length); + + var request = new XMLHttpRequest(); + request.open("GET", xhrPath, false); + request.send(null); + + // Try reading headers in privileged context + is(request.getResponseHeader("Set-Cookie"), "test", "Reading Set-Cookie response header in privileged context"); + is(request.getResponseHeader("Set-Cookie2"), "test2", "Reading Set-Cookie2 response header in privileged context"); + is(request.getResponseHeader("X-Dummy"), "test", "Reading X-Dummy response header in privileged context"); + + ok(/\bSet-Cookie:/i.test(request.getAllResponseHeaders()), "Looking for Set-Cookie in all response headers in privileged context"); + ok(/\bSet-Cookie2:/i.test(request.getAllResponseHeaders()), "Looking for Set-Cookie2 in all response headers in privileged context"); + ok(/\bX-Dummy:/i.test(request.getAllResponseHeaders()), "Looking for X-Dummy in all response headers in privileged context"); + +</script> +</pre> +</body> +</html> diff --git a/dom/base/test/chrome/test_bug380418.html^headers^ b/dom/base/test/chrome/test_bug380418.html^headers^ new file mode 100644 index 0000000000..5f8d4969c0 --- /dev/null +++ b/dom/base/test/chrome/test_bug380418.html^headers^ @@ -0,0 +1,4 @@ +Set-Cookie: test +Set-Cookie2: test2 +X-Dummy: test +Cache-Control: max-age=0 diff --git a/dom/base/test/chrome/test_bug383430.html b/dom/base/test/chrome/test_bug383430.html new file mode 100644 index 0000000000..ce526ef281 --- /dev/null +++ b/dom/base/test/chrome/test_bug383430.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=383430 +--> +<head> + <title>Test for Bug 383430</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=383430">Mozilla Bug 383430</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 383430 **/ + +var req = new XMLHttpRequest(); +req.open("GET", window.location.href); +req.send(null); + +ok(req.channel.loadGroup != null, "loadGroup is not null"); + +req = new XMLHttpRequest(); +req.mozBackgroundRequest = true; +req.open("GET", window.location.href); +req.send(null); + +ok(req.channel.loadGroup == null, "loadGroup is null"); + +</script> +</pre> +</body> +</html> diff --git a/dom/base/test/chrome/test_bug418986-1.xhtml b/dom/base/test/chrome/test_bug418986-1.xhtml new file mode 100644 index 0000000000..7d3add900a --- /dev/null +++ b/dom/base/test/chrome/test_bug418986-1.xhtml @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=418986-1 +--> +<window title="Mozilla Bug 418986 (Part 1)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=418986-1" + target="_blank">Mozilla Bug 418986 (Part 1)</a> + + <script type="application/javascript" src="bug418986-1.js"></script> + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + window.onload = function() { + test(false); + }; + ]]></script> + </body> +</window> diff --git a/dom/base/test/chrome/test_bug421622.xhtml b/dom/base/test/chrome/test_bug421622.xhtml new file mode 100644 index 0000000000..236c42dd34 --- /dev/null +++ b/dom/base/test/chrome/test_bug421622.xhtml @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=421622 +--> +<window title="Mozilla Bug 421622" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=421622" + target="_blank">Mozilla Bug 421622</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 421622 **/ + const SJS_URL = "http://mochi.test:8888/tests/dom/base/test/chrome/bug421622-referer.sjs"; + const REFERER_URL = "http://www.mozilla.org/"; + + var req = new XMLHttpRequest(); + req.open("GET", SJS_URL, false); + req.setRequestHeader("Referer", REFERER_URL); + req.send(null); + + is(req.responseText, + "Referer: " + REFERER_URL, + "Referer header received by server does not match what was set"); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_bug429785.xhtml b/dom/base/test/chrome/test_bug429785.xhtml new file mode 100644 index 0000000000..fb51634fab --- /dev/null +++ b/dom/base/test/chrome/test_bug429785.xhtml @@ -0,0 +1,53 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=429785 +--> +<window title="Mozilla Bug 429785" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=429785" + target="_blank">Mozilla Bug 429785</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /** Test for Bug 429785 **/ + SimpleTest.waitForExplicitFinish(); + var errorLogged = false; + var listener = { + QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]), + observe(msg) { errorLogged = true; } + }; + + function step2() { + is(errorLogged, false, "Should be no errors"); + + Services.console.logStringMessage("This is a test"); + + setTimeout(step3, 0); + + } + + function step3() { + is(errorLogged, true, "Should see errors when they happen"); + Services.console.unregisterListener(listener); + SimpleTest.finish(); + } + + Services.console.registerListener(listener); + + var p = new DOMParser(); + p.parseFromString("<root/>", "application/xml"); + + // nsConsoleService notifies its listeners via async proxies, so we need + // to wait to see whether there was an error reported. + setTimeout(step2, 0); + + + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_bug430050.xhtml b/dom/base/test/chrome/test_bug430050.xhtml new file mode 100644 index 0000000000..d7d6cf656c --- /dev/null +++ b/dom/base/test/chrome/test_bug430050.xhtml @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=430050 +--> +<window title="Mozilla Bug 430050" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=430050" + target="_blank">Mozilla Bug 430050</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 430050 **/ + + function endTest() { + ok(document.getElementById('b').contentDocument.documentElement.textContent == + "succeeded", "Wrong URL loaded!"); + SimpleTest.finish(); + } + + function startTest() { + const observer = new MutationObserver((aMutationList, aObserver) => { + document.getElementById('b').setAttribute("src", + "data:text/plain,failed"); + const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + document.getElementById('b').loadURI(Services.io.newURI('data:text/plain,succeeded'), { + triggeringPrincipal: systemPrincipal + }); + document.getElementById('b').addEventListener("load", endTest); + }); + observer.observe(document.documentElement, { attributes: true }); + document.documentElement.setAttribute("foo", "bar"); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(startTest); + + ]]></script> + <browser flex="1" id="b"/> +</window> diff --git a/dom/base/test/chrome/test_bug467123.xhtml b/dom/base/test/chrome/test_bug467123.xhtml new file mode 100644 index 0000000000..0811aba051 --- /dev/null +++ b/dom/base/test/chrome/test_bug467123.xhtml @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=467123 +--> +<window title="Mozilla Bug 467123" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=467123" + target="_blank">Mozilla Bug 467123</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /** Test for Bug 467123 **/ + let url = Services.io.newURI(document.location.href); + let file = Cc["@mozilla.org/chrome/chrome-registry;1"] + .getService(Ci.nsIChromeRegistry) + .convertChromeURL(url) + .QueryInterface(Ci.nsIFileURL) + .file.parent; + file.append("clonedoc"); + Components.manager.addBootstrappedManifestLocation(file); + + var xhr = new XMLHttpRequest(); + xhr.open("GET", "chrome://clonedoc/content/doc.xml", false); + xhr.send(); + ok(xhr.responseXML, "We should have response document!"); + var e = null; + try { + var clone = xhr.responseXML.cloneNode(true); + } catch (ex) { + e = ex; + } + ok(!e, e); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_bug473284.xhtml b/dom/base/test/chrome/test_bug473284.xhtml new file mode 100644 index 0000000000..87c778a615 --- /dev/null +++ b/dom/base/test/chrome/test_bug473284.xhtml @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=473284 +--> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" +onload=" +var result = ''; +try { + document.commandDispatcher.advanceFocusIntoSubtree({}); + result += '1'; +} catch (ex) { + result += '.'; +} + +try { + document.commandDispatcher.advanceFocusIntoSubtree(document.documentElement); + result += '2'; +} catch (ex) { + result += '.'; +} + +try { + document.commandDispatcher.advanceFocusIntoSubtree(null); + result += '3'; +} catch (ex) { + result += '.'; +} + +try { + document.commandDispatcher.focusedElement = {}; + result += '4'; +} catch (ex) { + result += '.'; +} + +try { + document.commandDispatcher.focusedElement = document.documentElement; + result += '5'; +} catch (ex) { + result += '.'; +} + +try { + document.commandDispatcher.focusedElement = null; + result += '6'; +} catch (ex) { + result += '.'; +} + +try { + document.commandDispatcher.focusedWindow = {}; + result += 'a'; +} catch (ex) { + result += '.'; +} + +try { + document.commandDispatcher.focusedWindow = null; + result += 'b'; +} catch (ex) { + result += '.'; +} + +try { + document.commandDispatcher.focusedWindow = window; + result += 'c'; +} catch (ex) { + result += '.'; +} + +is(result, '.23.56.bc', 'The correct assignments throw.'); +"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=473284" + target="_blank">Mozilla Bug 473284</a> + </body> +</window> diff --git a/dom/base/test/chrome/test_bug549682.xhtml b/dom/base/test/chrome/test_bug549682.xhtml new file mode 100644 index 0000000000..0f1ecf646a --- /dev/null +++ b/dom/base/test/chrome/test_bug549682.xhtml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=549682 +--> +<window title="Mozilla Bug 549682" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=549682" + target="_blank">Mozilla Bug 549682</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 549682 **/ + SimpleTest.waitForExplicitFinish(); + + function done() { + SimpleTest.finish(); + } + + addLoadEvent(function() { + window.openDialog("file_bug549682.xhtml", "", "chrome,noopener", window); + }); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_bug571390.xhtml b/dom/base/test/chrome/test_bug571390.xhtml new file mode 100644 index 0000000000..ea1f357a5d --- /dev/null +++ b/dom/base/test/chrome/test_bug571390.xhtml @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=571390 +--> +<window title="Mozilla Bug 571390" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + class="foo bar"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=571390" + target="_blank">Mozilla Bug 571390</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 571390 **/ + + is(document.documentElement.classList.length, 2, "Should have 2 classes."); + ok(document.documentElement.classList.contains("foo"), "Should contain 'foo' class."); + ok(document.documentElement.classList.contains("bar"), "Should contain 'bar' class."); + ok(!document.documentElement.classList.contains("foobar"), "Shouldn't contain 'foobar' class."); + + document.documentElement.classList.add("foobar"); + is(document.documentElement.classList.length, 3, "Should have 3 classes."); + ok(document.documentElement.classList.contains("foo"), "Should contain 'foo' class."); + ok(document.documentElement.classList.contains("bar"), "Should contain 'bar' class."); + ok(document.documentElement.classList.contains("foobar"), "Should contain 'foobar' class."); + + document.documentElement.classList.remove("foobar"); + is(document.documentElement.classList.length, 2, "Should have 2 classes."); + ok(document.documentElement.classList.contains("foo"), "Should contain 'foo' class."); + ok(document.documentElement.classList.contains("bar"), "Should contain 'bar' class."); + ok(!document.documentElement.classList.contains("foobar"), "Shouldn't contain 'foobar' class."); + ]]> + </script> +</window> diff --git a/dom/base/test/chrome/test_bug616841.xhtml b/dom/base/test/chrome/test_bug616841.xhtml new file mode 100644 index 0000000000..f5907f0b0b --- /dev/null +++ b/dom/base/test/chrome/test_bug616841.xhtml @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=616841 +--> +<window title="Mozilla Bug 616841" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=616841" + target="_blank">Mozilla Bug 616841</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function done() { + SimpleTest.finish(); + } + + addLoadEvent(function() { + window.openDialog("file_bug616841.xhtml", "", "chrome,noopener", window); + }); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_bug635835.xhtml b/dom/base/test/chrome/test_bug635835.xhtml new file mode 100644 index 0000000000..69bb3ae68b --- /dev/null +++ b/dom/base/test/chrome/test_bug635835.xhtml @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=635835 +--> +<window title="Mozilla Bug 635835" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=635835" + target="_blank">Mozilla Bug 635835</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ +SimpleTest.waitForExplicitFinish(); +const SHOW_ALL = NodeFilter.SHOW_ALL; + +addLoadEvent(function() { + var walker = document.createTreeWalker(document, SHOW_ALL, null); + try { + walker.currentNode = {}; + walker.nextNode(); + } + catch (e) { + // do nothing - this is a crash test + } + ok(true, "Crash test passed"); + SimpleTest.finish(); +}); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_bug682305.html b/dom/base/test/chrome/test_bug682305.html new file mode 100644 index 0000000000..d500dc91d5 --- /dev/null +++ b/dom/base/test/chrome/test_bug682305.html @@ -0,0 +1,150 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=682305 +--> +<head> + <title>XMLHttpRequest send and channel implemented in JS</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=682305">Mozilla Bug 682305</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="application/javascript"> + +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +/* + * Register a custom nsIProtocolHandler service + * in order to be able to implement *and use* an + * nsIChannel component written in Javascript. + */ + +const { ComponentUtils } = ChromeUtils.importESModule( + "resource://gre/modules/ComponentUtils.sys.mjs" +); + +var contentSecManager = Cc["@mozilla.org/contentsecuritymanager;1"] + .getService(Ci.nsIContentSecurityManager); + +var PROTOCOL_SCHEME = "jsproto"; + + +function CustomChannel(uri, loadInfo) { + this.URI = this.originalURI = uri; + this.loadInfo = loadInfo; +} +CustomChannel.prototype = { + URI: null, + originalURI: null, + loadInfo: null, + contentCharset: "utf-8", + contentLength: 0, + contentType: "text/plain", + owner: Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal), + securityInfo: null, + notificationCallbacks: null, + loadFlags: 0, + loadGroup: null, + name: null, + status: Cr.NS_OK, + asyncOpen(listener) { + // throws an error if security checks fail + var outListener = contentSecManager.performSecurityCheck(this, listener); + let stream = this.open(); + try { + outListener.onStartRequest(this); + } catch (e) {} + try { + outListener.onDataAvailable(this, stream, 0, stream.available()); + } catch (e) {} + try { + outListener.onStopRequest(this, Cr.NS_OK); + } catch (e) {} + }, + open() { + // throws an error if security checks fail + contentSecManager.performSecurityCheck(this, null); + + let data = "bar"; + let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream); + stream.setData(data, data.length); + return stream; + }, + isPending() { + return false; + }, + cancel() { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + }, + suspend() { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + }, + resume() { + throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); + }, + QueryInterface: ChromeUtils.generateQI(["nsIChannel", "nsIRequest"]), +}; + + +function CustomProtocol() {} +CustomProtocol.prototype = { + get scheme() { + return PROTOCOL_SCHEME; + }, + allowPort: function allowPort() { + return false; + }, + newChannel: function newChannel(URI, loadInfo) { + return new CustomChannel(URI, loadInfo); + }, + QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference", "nsIProtocolHandler"]), +}; + +var gFactory = { + register() { + Services.io.registerProtocolHandler( + PROTOCOL_SCHEME, + new CustomProtocol(), + Ci.nsIProtocolHandler.URI_NORELATIVE | + Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE | + Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD, + -1 + ); + + this.unregister = function() { + Services.io.unregisterProtocolHandler(PROTOCOL_SCHEME); + delete this.unregister; + }; + }, +}; + +// Register the custom procotol handler +gFactory.register(); + +// Then, checks if XHR works with it +var xhr = new XMLHttpRequest(); +xhr.open("GET", PROTOCOL_SCHEME + ":foo", true); +xhr.onload = function() { + is(xhr.responseText, "bar", "protocol doesn't work"); + gFactory.unregister(); + SimpleTest.finish(); +}; +try { + xhr.send(null); +} catch (e) { + ok(false, e); +} +</script> +</pre> +</body> +</html> diff --git a/dom/base/test/chrome/test_bug683852.xhtml b/dom/base/test/chrome/test_bug683852.xhtml new file mode 100644 index 0000000000..1f9e0d9472 --- /dev/null +++ b/dom/base/test/chrome/test_bug683852.xhtml @@ -0,0 +1,87 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=683852 +--> +<window title="Mozilla Bug 683852" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gaktekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script> + <![CDATA[ + customElements.define("custom-element", class extends XULElement { + constructor() { + super(); + const template = document.getElementById("template"); + this.attachShadow({mode: "open"}) + .appendChild(template.content.cloneNode(true)); + } + }); + ]]> + </script> + <html:template id="template"><xul:box anonid="anon">Anonymous</xul:box></html:template> + <custom-element id="custom-element"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=683852" + target="_blank" id="link">Mozilla Bug 683852</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 683852 **/ + SimpleTest.waitForExplicitFinish(); + + const NS_HTML = "http://www.w3.org/1999/xhtml"; + + function startTest() { + is(document.contains(document), true, "Document should contain itself!"); + + let box = document.getElementById("custom-element"); + is(document.contains(box), true, "Document should contain element in it!"); + is(box.contains(box), true, "Element should contain itself.") + let anon = box.shadowRoot.querySelector("[anonid=anon]"); + is(document.contains(anon), false, "Document should not contain anonymous element in it!"); + is(box.contains(anon), false, "Element should not contain anonymous element in it!"); + is(anon.contains(anon), true, "Anonymous element should contain itself.") + is(document.documentElement.contains(box), true, "Element should contain element in it!"); + is(document.contains(document.createXULElement("foo")), false, "Document shouldn't contain element which is't in the document"); + is(document.contains(document.createTextNode("foo")), false, "Document shouldn't contain text node which is't in the document"); + + var link = document.getElementById("link"); + is(document.contains(link.firstChild), true, + "Document should contain a text node in it."); + is(link.contains(link.firstChild), true, + "Element should contain a text node in it."); + is(link.firstChild.contains(link), false, "text node shouldn't contain its parent."); + + is(document.contains(null), false, "Document shouldn't contain null."); + + var pi = document.createProcessingInstruction("adf", "asd"); + is(pi.contains(document), false, "Processing instruction shouldn't contain document"); + document.documentElement.appendChild(pi); + document.contains(pi, true, "Document should contain processing instruction"); + + var df = document.createRange().createContextualFragment(`<div xmlns="${NS_HTML}">foo</div>`); + is(df.contains(df.firstChild), true, "Document fragment should contain its child"); + is(df.contains(df.firstChild.firstChild), true, + "Document fragment should contain its descendant"); + is(df.contains(df), true, "Document fragment should contain itself."); + + var d = document.implementation.createHTMLDocument(""); + is(document.contains(d), false, + "Document shouldn't contain another document."); + is(document.contains(d.createElement("div")), false, + "Document shouldn't contain an element from another document."); + + SimpleTest.finish(); + } + + addLoadEvent(startTest); + ]]> + </script> +</window> diff --git a/dom/base/test/chrome/test_bug752226-3.xhtml b/dom/base/test/chrome/test_bug752226-3.xhtml new file mode 100644 index 0000000000..747fb29c4e --- /dev/null +++ b/dom/base/test/chrome/test_bug752226-3.xhtml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=752226 +--> +<window title="Mozilla Bug 752226" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=752226" + target="_blank">Mozilla Bug 752226</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 752226 **/ + try { + new File(null); + } catch (e) {} + ok(true, "Didn't crash"); + ]]> + </script> +</window> diff --git a/dom/base/test/chrome/test_bug752226-4.xhtml b/dom/base/test/chrome/test_bug752226-4.xhtml new file mode 100644 index 0000000000..242e231a2e --- /dev/null +++ b/dom/base/test/chrome/test_bug752226-4.xhtml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=752226 +--> +<window title="Mozilla Bug 752226" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=752226" + target="_blank">Mozilla Bug 752226</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 752226 **/ + try { + new Cu.Sandbox("about:blank", null); + } catch (e) {} + ok(true, "Didn't crash"); + ]]> + </script> +</window> diff --git a/dom/base/test/chrome/test_bug765993.html b/dom/base/test/chrome/test_bug765993.html new file mode 100644 index 0000000000..3325c3713d --- /dev/null +++ b/dom/base/test/chrome/test_bug765993.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=765993 +--> +<head> + <title>Test for Bug 765993</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=765993">Mozilla Bug 765993</a> +<style type="text/css"> +#link1 a { user-select:none; } +</style> +<div id="link1"><a href="http://www.mozilla.org/">link1</a></div> +<div id="link2"><a href="http://www.mozilla.org/">link2</a></div> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 765993 **/ + +const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs"); +addDebuggerToGlobal(globalThis); + +window.onload = function() { + SimpleTest.waitForExplicitFinish(); + + var iframe = document.createElement("iframe"); + iframe.src = "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug765993.html"; + iframe.onload = function() { + var script = iframe.contentWindow.document.createElement("script"); + script.src = "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug765993.js"; + script.onload = function() { + var dbg = new Debugger(iframe.contentWindow); + ok(dbg, "Should be able to create debugger"); + + var scripts = dbg.findScripts({ + url: "http://mochi.test:8888/tests/dom/base/test/chrome/nochrome_bug765993.js", + }); + ok(scripts.length, "Should be able to find script"); + + is(scripts[0].source.sourceMapURL, "foo.js.map"); + SimpleTest.finish(); + }; + + iframe.contentWindow.document.body.appendChild(script); + }; + + document.body.appendChild(iframe); +}; + +</script> +</pre> +</body> +</html> diff --git a/dom/base/test/chrome/test_bug780199.xhtml b/dom/base/test/chrome/test_bug780199.xhtml new file mode 100644 index 0000000000..e27afb72fa --- /dev/null +++ b/dom/base/test/chrome/test_bug780199.xhtml @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=780199 +--> +<window title="Mozilla Bug 780199" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="test()"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=780199" + target="_blank">Mozilla Bug 780199</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 780199 **/ + + SimpleTest.waitForExplicitFinish(); + + var b; + + function callback(r) { + is(r[0].type, "attributes"); + is(r[0].oldValue, b.getAttribute("src")); + setTimeout(continueTest, 500); + } + + function continueTest() { + // Check that a new page wasn't loaded. + is(b.contentDocument.documentElement.textContent, "testvalue"); + SimpleTest.finish(); + } + + function test() { + b = document.getElementById("b"); + var m = new MutationObserver(callback); + m.observe(b, { attributes: true, attributeOldValue: true }); + b.contentDocument.documentElement.textContent = "testvalue"; + b.setAttribute("src", b.getAttribute("src")); + } + + ]]> + </script> + <browser id="b" src="data:text/plain,initial"/> +</window> diff --git a/dom/base/test/chrome/test_bug780529.xhtml b/dom/base/test/chrome/test_bug780529.xhtml new file mode 100644 index 0000000000..bf8b8b2981 --- /dev/null +++ b/dom/base/test/chrome/test_bug780529.xhtml @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=780529 +--> +<window title="Mozilla Bug 780529" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=780529" + target="_blank">Mozilla Bug 780529</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 780529 **/ +var req = new XMLHttpRequest(); +req.open("GET", "", true); +// Have to call send() to get the XHR hooked up as the notification callbacks +req.send(); +var callbacks = req.channel.notificationCallbacks; +var sink = callbacks.getInterface(Ci.nsIChannelEventSink); +ok(sink instanceof Ci.nsIChannelEventSink, + "Should be a channel event sink") +ok("asyncOnChannelRedirect" in sink, + "Should have the right methods for an event sink"); + +let sinkReq = sink.QueryInterface(Ci.nsIInterfaceRequestor); +isnot(sinkReq, callbacks, "Sink should not be the XHR object"); + ]]> + </script> +</window> diff --git a/dom/base/test/chrome/test_bug800386.xhtml b/dom/base/test/chrome/test_bug800386.xhtml new file mode 100644 index 0000000000..c176bd9b8e --- /dev/null +++ b/dom/base/test/chrome/test_bug800386.xhtml @@ -0,0 +1,65 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=800386 +--> +<window title="Mozilla Bug 800386" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=800386" + target="_blank">Mozilla Bug 800386</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 800386 **/ + SimpleTest.waitForExplicitFinish(); + + var triedForwarding = false; + var forwardFailed = false; + + var xhr = new XMLHttpRequest; + var xhr2 = new XMLHttpRequest; + + var eventSink = xhr.getInterface(Ci.nsIProgressEventSink); + isnot(eventSink, null, "Should get event sink directly!"); + + // Now jump through some hoops to get us a getInterface call from C++ + + var requestor = { + getInterface(aIID) { + if (aIID.equals(Ci.nsIProgressEventSink)) { + triedForwarding = true; + try { + return xhr2.getInterface(aIID); + } catch (e) { + forwardFailed = true; + } + } + throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); + }, + + QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]) + }; + + // HTTP URI so that we get progress callbacks + xhr.open("GET", "http://mochi.test:8888/", false); + xhr.channel.notificationCallbacks = requestor; + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + ok(triedForwarding, + "Should have had an attempt to treat us as a progress event sink"); + ok(!forwardFailed, + "Should have been able to forward getInterface on to the XHR"); + SimpleTest.finish(); + } + } + xhr.send(); + ]]> + </script> +</window> diff --git a/dom/base/test/chrome/test_bug816340.xhtml b/dom/base/test/chrome/test_bug816340.xhtml new file mode 100644 index 0000000000..3e45fd7245 --- /dev/null +++ b/dom/base/test/chrome/test_bug816340.xhtml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=816340 +--> +<window title="Mozilla Bug 816340" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=816340" + target="_blank">Mozilla Bug 816340</a> + </body> + + <!-- test code goes here --> + <script> + SimpleTest.waitForExplicitFinish(); + + function done() { + SimpleTest.finish(); + } + + SpecialPowers.pushPrefEnv({ + set: [["dom.forms.fieldset_disable_only_descendants.enabled", true]] + }).then(() => { + window.openDialog("file_bug816340.xhtml", "", "chrome,noopener", window); + }); + </script> +</window> diff --git a/dom/base/test/chrome/test_bug884693.xhtml b/dom/base/test/chrome/test_bug884693.xhtml new file mode 100644 index 0000000000..7d89ba25bc --- /dev/null +++ b/dom/base/test/chrome/test_bug884693.xhtml @@ -0,0 +1,79 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=884693 +--> +<window title="Mozilla Bug 884693" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=884693" + target="_blank">Mozilla Bug 884693</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + const SERVER_URL = "http://mochi.test:8888/tests/dom/base/test/chrome/bug884693.sjs"; + const INVALID_XML = "InvalidXML"; + const XML_WITHOUT_ROOT = "<?xml version='1.0'?>"; + + function runTest(status, statusText, body, expectedResponse, expectedMessages) + { + return new Promise((resolve, reject) => { + Services.console.reset(); + + let xhr = new XMLHttpRequest(); + + xhr.onload = () => { + is(xhr.responseText, expectedResponse, "Correct responseText returned"); + + let messages = Services.console.getMessageArray() || []; + // broadcastlisteners can happen and cause false alarm. + messages = messages.filter(msg => + !(msg instanceof Ci.nsIScriptError && + msg.category.includes("chrome javascript") && + msg.message.includes("Unknown event"))); + + if (messages.length) { + info(`Got console messages ${messages}`); + } + is(messages.length, expectedMessages.length, "Got expected message count"); + messages = messages.map(m => m.message).join(","); + for(let message of expectedMessages) { + ok(messages.includes(message), "Got expected message: " + message); + } + + resolve(); + }; + + xhr.onerror = e => { + reject(e); + }; + + xhr.open("GET", `${SERVER_URL}?${status}&${statusText}&${body}`); + xhr.send(); + }); + } + + SimpleTest.waitForExplicitFinish(); + runTest(201, "Created", "", "", []). + then(() => { return runTest(201, "Created", INVALID_XML, INVALID_XML, []); }). + then(() => { return runTest(202, "Accepted", "", "", []); }). + then(() => { return runTest(202, "Accepted", INVALID_XML, INVALID_XML, []); }). + then(() => { return runTest(204, "No Content", "", "", []); }). + then(() => { return runTest(204, "No Content", INVALID_XML, "", []); }). + then(() => { return runTest(205, "Reset Content", "", "", []); }). + then(() => { return runTest(205, "Reset Content", INVALID_XML, "", []); }). + then(() => { return runTest(304, "Not modified", "", "", []); }). + then(() => { return runTest(304, "Not modified", INVALID_XML, "", []); }). + then(() => { return runTest(200, "OK", "", "", []); }). + then(() => { return runTest(200, "OK", XML_WITHOUT_ROOT, XML_WITHOUT_ROOT, ["no root element found"]); }). + then(() => { return runTest(200, "OK", INVALID_XML, INVALID_XML, ["syntax error"]); }). + then(SimpleTest.finish); + + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_bug914381.html b/dom/base/test/chrome/test_bug914381.html new file mode 100644 index 0000000000..eb82ffd0f7 --- /dev/null +++ b/dom/base/test/chrome/test_bug914381.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=650776 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 914381</title> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=914381">Mozilla Bug 914381</a> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +<script type="application/javascript"> +function createFileWithData(fileData) { + var testFile = Services.dirsvc.get("ProfD", Ci.nsIFile); + testFile.append("testBug914381"); + + var outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream); + outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate + 0o666, 0); + outStream.write(fileData, fileData.length); + outStream.close(); + + return testFile; +} + +/** Test for Bug 914381. File's created in JS using an nsIFile should allow mozGetFullPathInternal calls to succeed **/ +var file = createFileWithData("Test bug 914381"); + +SpecialPowers.pushPrefEnv({ set: [["dom.file.createInChild", true]]}) +.then(() => { + return File.createFromNsIFile(file); +}) +.then(f => { + is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js"); + is(f.mozFullPath, file.path, "mozFullPath returns path if created with nsIFile"); +}) +.then(() => { + return File.createFromFileName(file.path); +}) +.then(f => { + is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js"); + is(f.mozFullPath, "", "mozFullPath returns blank if created with a string"); +}) +.then(() => { + SimpleTest.finish(); +}); + +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/dom/base/test/chrome/test_bug990812.xhtml b/dom/base/test/chrome/test_bug990812.xhtml new file mode 100644 index 0000000000..410d6d2367 --- /dev/null +++ b/dom/base/test/chrome/test_bug990812.xhtml @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=990812 +--> +<window title="Mozilla Bug 990812" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=990812" + target="_blank">Mozilla Bug 990812</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + var tests = [ + "file_bug990812-1.xhtml", + "file_bug990812-2.xhtml", + "file_bug990812-3.xhtml", + "file_bug990812-4.xhtml", + "file_bug990812-5.xhtml", + ]; + + function next() { + if (tests.length) { + var file = tests.shift(); + info("-- running " + file); + window.openDialog(file, "_blank", "chrome,noopener", window); + } else { + SimpleTest.finish(); + } + } + + addLoadEvent(next); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_chromeOuterWindowID.xhtml b/dom/base/test/chrome/test_chromeOuterWindowID.xhtml new file mode 100644 index 0000000000..1feb7c7c74 --- /dev/null +++ b/dom/base/test/chrome/test_chromeOuterWindowID.xhtml @@ -0,0 +1,138 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1474662 +Test that the chromeOuterWindowID on the MessageManager interface +works, and that it properly updates when swapping frameloaders between +windows. +--> +<window title="Mozilla Bug 1474662" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1474662" + target="_blank">Mozilla Bug 1474662</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + const {BrowserTestUtils} = ChromeUtils.importESModule( + "resource://testing-common/BrowserTestUtils.sys.mjs" + ); + + const BROWSER_DOC = "window_chromeOuterWindowID.xhtml"; + const TEST_PAGE = "http://example.com"; + const TEST_PAGE_2 = "http://example.com/browser"; + + function getOuterWindowID(win) { + return win.docShell.outerWindowID; + } + + /** + * Takes two <xul:browser>'s that should be in the same document, and + * ensures that their frame script environments know the correct value + * of the host window's outerWindowID. + */ + async function ensureExpectedChromeOuterWindowIDs(browser1, browser2) { + is(browser1.ownerDocument, browser2.ownerDocument, + "Both browsers should belong to the same document."); + let winID = getOuterWindowID(browser1.ownerGlobal); + + let getChildRootOuterId = browser => { + try { + return docShell.browserChild?.chromeOuterWindowID; + } catch(ex) { } + + // Not a remote tab + return content.top.windowRoot.ownerGlobal.docShell.outerWindowID; + }; + + let browser1ID = await SpecialPowers.spawn(browser1, [], getChildRootOuterId); + let browser2ID = await SpecialPowers.spawn(browser2, [], getChildRootOuterId); + + is(browser1ID, winID, + "Browser 1 frame script environment should have the correct chromeOuterWindowID"); + is(browser2ID, winID, + "Browser 1 frame script environment should have the correct chromeOuterWindowID"); + } + + /** + * Opens up a BROWSER_DOC test window, and points each browser to a particular + * page. + * + * @param num (Number) + * An identifier number for this window. Mainly used as a suffix for the + * returned values. + * @param page (String) + * A URL to load in each <xul:browser> + * @returns Promise + * The Promise resolves with an object with the following properties: + * + * win<num>: a reference to the opened window + * remote<num>: a reference to the remote browser in the window + * nonRemote<num>: a reference to the non-remote browser in the window + */ + async function prepareWindow(num, page) { + let win = window.browsingContext.topChromeWindow.open(BROWSER_DOC, "bug1474662-" + num, "chrome,width=200,height=200"); + await BrowserTestUtils.waitForEvent(win, "load"); + let remote = win.document.getElementById("remote"); + let nonRemote = win.document.getElementById("non-remote"); + + ok(remote && remote.isRemoteBrowser, + "Should have found a remote browser in test window " + num); + ok(nonRemote && !nonRemote.isRemoteBrowser, + "Should have found a non-remote browser in test window " + num); + + BrowserTestUtils.startLoadingURIString(remote, page); + await BrowserTestUtils.browserLoaded(remote); + BrowserTestUtils.startLoadingURIString(nonRemote, page); + await BrowserTestUtils.browserLoaded(nonRemote); + + let result = {}; + result["win" + num] = win; + result["remote" + num] = remote; + result["nonRemote" + num] = nonRemote; + return result; + } + + add_task(async () => { + let { win1, remote1, nonRemote1 } = await prepareWindow(1, TEST_PAGE); + let { win2, remote2, nonRemote2 } = await prepareWindow(2, TEST_PAGE_2); + + let win1ID = getOuterWindowID(win1); + let win2ID = getOuterWindowID(win2); + + // Quick sanity-test here - if something has gone horribly wrong + // and the windows have the same IDs, then the rest of this test + // is meaningless. + isnot(win1ID, win2ID, + "The windows should definitely have different IDs."); + + await ensureExpectedChromeOuterWindowIDs(remote1, nonRemote1); + await ensureExpectedChromeOuterWindowIDs(remote2, nonRemote2); + + // Swap the frameloaders! This doesn't swap the <browser> elements though, + // so what's expected is that the IDs should remain the same within + // the browsers despite the underlying content changing. + remote1.swapFrameLoaders(remote2); + nonRemote1.swapFrameLoaders(nonRemote2); + + await ensureExpectedChromeOuterWindowIDs(remote1, nonRemote1); + await ensureExpectedChromeOuterWindowIDs(remote2, nonRemote2); + + // Now swap them back. + remote1.swapFrameLoaders(remote2); + nonRemote1.swapFrameLoaders(nonRemote2); + + await ensureExpectedChromeOuterWindowIDs(remote1, nonRemote1); + await ensureExpectedChromeOuterWindowIDs(remote2, nonRemote2); + + await BrowserTestUtils.closeWindow(win1); + await BrowserTestUtils.closeWindow(win2); + }); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_custom_element_content.xhtml b/dom/base/test/chrome/test_custom_element_content.xhtml new file mode 100644 index 0000000000..7778bc350e --- /dev/null +++ b/dom/base/test/chrome/test_custom_element_content.xhtml @@ -0,0 +1,55 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1130028 +--> +<window title="Mozilla Bug 1130028" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1130028" + target="_blank">Mozilla Bug 1130028</a> + <iframe onload="startTests()" id="frame" src="http://example.com/chrome/dom/base/test/chrome/frame_custom_element_content.html"></iframe> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 1130028 **/ + var connectedCallbackCount = 0; + + function startTests() { + var frame = $("frame"); + + class XFoo extends frame.contentWindow.HTMLElement {}; + frame.contentWindow.customElements.define("x-foo", XFoo); + var elem = new XFoo(); + is(elem.tagName, "X-FOO", "Constructor should create an x-foo element."); + + class XBar extends frame.contentWindow.HTMLElement { + constructor() { + super(); + this.magicNumber = 42; + } + + connectedCallback() { + connectedCallbackCount++; + // Callback should be called only once by element created in content. + is(connectedCallbackCount, 1, "Connected callback called, should be called once in test."); + is(this.magicNumber, 42, "Callback should be able to see the custom prototype."); + } + }; + + frame.contentWindow.customElements.define("x-bar", XBar); + is(connectedCallbackCount, 1, "Connected callback should be called by element created in content."); + + var element = frame.contentDocument.createElement("x-bar"); + is(element.magicNumber, 42, "Should be able to see the custom prototype on created element."); + } + + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_custom_element_ep.xhtml b/dom/base/test/chrome/test_custom_element_ep.xhtml new file mode 100644 index 0000000000..28b4e876f5 --- /dev/null +++ b/dom/base/test/chrome/test_custom_element_ep.xhtml @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1130028 +--> +<window title="Mozilla Bug 1130028" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1130028" + target="_blank">Mozilla Bug 1130028</a> + <iframe onload="startTests()" id="frame" src="http://example.com/chrome/dom/base/test/chrome/frame_custom_element_content.html"></iframe> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 1130028 **/ + SimpleTest.waitForExplicitFinish(); + + function finishTest(canSeePrototype) { + ok(true, "connectedCallback called when reigsterElement was called with an extended principal."); + ok(canSeePrototype, "connectedCallback should be able to see custom prototype."); + SimpleTest.finish(); + } + + function startTests() { + var frame = $("frame"); + + // Create a sandbox with an extended principal then run a script that registers a custom element in the sandbox. + var sandbox = Cu.Sandbox([frame.contentWindow], { sandboxPrototype: frame.contentWindow }); + sandbox.finishTest = finishTest; + Services.scriptloader.loadSubScript("chrome://mochitests/content/chrome/dom/base/test/chrome/custom_element_ep.js", sandbox); + } + + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_document-element-inserted.xhtml b/dom/base/test/chrome/test_document-element-inserted.xhtml new file mode 100644 index 0000000000..5fd35e364d --- /dev/null +++ b/dom/base/test/chrome/test_document-element-inserted.xhtml @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1411707 +--> +<window title="Mozilla Bug 1411707" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1411707" + target="_blank">Mozilla Bug 1411707</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + const OUTER_URL = "chrome://mochitests/content/chrome/dom/base/test/chrome/file_document-element-inserted.xhtml"; + const INNER_URL = "chrome://mochitests/content/chrome/dom/base/test/chrome/file_document-element-inserted-inner.xhtml"; + + async function waitForEvent(url) { + return new Promise(resolve => { + SpecialPowers.addObserver(function inserted(document) { + is(document.documentURI, url, "Correct URL"); + is(document.readyState, "loading", "Correct readyState"); + SpecialPowers.removeObserver(inserted, "document-element-inserted"); + resolve(); + }, "document-element-inserted"); + }) + } + + // Load a XUL document that also has an iframe to a subdocument, and + // expect both events to fire with the docs in the correct state. + async function testEvents() { + info(`Waiting for events after loading ${OUTER_URL}`); + let win = window.browsingContext.topChromeWindow.openDialog(OUTER_URL, "_blank", "chrome,dialog=no,all"); + await waitForEvent(OUTER_URL); + await waitForEvent(INNER_URL); + win.close(); + } + + (async function() { + // Test the same document twice to make to make sure we are + // firing properly when loading the protype document. + await testEvents(); + await testEvents(); + SimpleTest.finish(); + })(); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_domparsing.xhtml b/dom/base/test/chrome/test_domparsing.xhtml new file mode 100644 index 0000000000..48a7d43b6a --- /dev/null +++ b/dom/base/test/chrome/test_domparsing.xhtml @@ -0,0 +1,145 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window title="Test for the Mozilla extesion of the DOM Parsing and Serialization API" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=816410" + target="_blank">Mozilla Bug 816410</a> + <xul:browser id="test1" type="content" src="data:text/html,content" /> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ +"use strict"; +/** Test for Bug 816410 **/ + +function throws(fn, type, message) { + try { + fn(); + ok(false, message); + } catch (e) { + if (type) { + is(e.name, type, message); + } else { + ok(true, message); + } + } +} + +add_task(function dom_parser_extra_args() { + // DOMParser constructor should not throw for extra arguments + new DOMParser(undefined); + new DOMParser(null); + new DOMParser(false); + new DOMParser(0); + new DOMParser(""); + new DOMParser({}); +}); + +add_task(function xml_serializer_extra_args() { + // XMLSerializer constructor should not throw for extra arguments + new XMLSerializer(undefined); + new XMLSerializer(null); + new XMLSerializer(false); + new XMLSerializer(0); + new XMLSerializer(""); + new XMLSerializer({}); +}); + +add_task(function chrome_window() { + runTest(window, true); +}); + +add_task(function content_window() { + runTest(document.getElementById("test1").contentWindow, false); +}); + +function runTest(win, expectSystem) { + let parser = new win.DOMParser(); + let serializer = new win.XMLSerializer(); + let principal = win.document.nodePrincipal; + is(principal.isSystemPrincipal, expectSystem, + `expected ${expectSystem ? "system" : "content"} principal`); + + is(typeof parser.parseFromString, "function", "parseFromString should exist"); + is(typeof parser.parseFromBuffer, "function", "parseFromBuffer should exist"); + is(typeof parser.parseFromStream, "function", "parseFromStream should exist"); + is(typeof parser.parseFromSafeString, "function", "parseFromSafeString should exist"); + + is(typeof serializer.serializeToString, "function", "serializeToString should exist"); + is(typeof serializer.serializeToStream, "function", "serializeToStream should exist"); + + let tests = [ + {input: "<html></html>", type: "text/html", + expected: '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body></body></html>'}, + {input: "<xml></xml>", type: "text/xml", expected: "<xml/>"}, + {input: "<xml></xml>", type: "application/xml", expected: "<xml/>"}, + {input: "<html></html>", type: "application/xhtml+xml", expected: "<html/>"}, + {input: "<svg></svg>", type: "image/svg+xml", expected: "<svg/>"}, + ]; + for (let t of tests) { + const fromNormalString = parser.parseFromString(t.input, t.type); + if (principal.isSystemPrincipal) { + ok(fromNormalString.nodePrincipal.isNullPrincipal, + "system principal DOMParser produces a null principal document"); + } else { + ok(fromNormalString.nodePrincipal === principal, + "content principal DOMParser produces a document with an object-identical principal"); + } + + const fromSafeString = parser.parseFromSafeString(t.input, t.type); + ok(fromSafeString.nodePrincipal === principal, + "DOMParser with parseFromSafeString always produces a document with an object-identical principal"); + + is(serializer.serializeToString(parser.parseFromString(t.input, t.type)), t.expected, + "parseFromString test for " + t.type); + + for (let charset of [null, "UTF-8"]) { + let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); + pipe.init(false, false, 0, 0xffffffff, null); + let ostream = pipe.outputStream; + serializer.serializeToStream(parser.parseFromString(t.input, t.type), ostream, charset); + let istream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + istream.init(pipe.inputStream); + let data = istream.read(0xffffffff); + is(data, t.expected, + "serializeToStream test for " + t.type + ", charset=" + charset); + } + + if (t.type === "text/html") { + // parseFromBuffer and parseFromStream don't support "text/html". + continue; + } + + let array = Array.from(t.input, function(c) { return c.charCodeAt(c); }); + let inputs = [ + {array, name: "parseFromBuffer (array)"}, + {array: new Uint8Array(array), name: "parseFromBuffer (Uint8Array)"}, + ]; + for (let input of inputs) { + let a = input.array; + is(serializer.serializeToString(parser.parseFromBuffer(a, t.type)), t.expected, + input.name + " test for " + t.type); + } + + let istream = Cc["@mozilla.org/io/string-input-stream;1"]. + createInstance(Ci.nsIStringInputStream); + for (let charset of [null, "UTF-8"]) { + istream.setData(t.input, -1); + is(serializer.serializeToString(parser.parseFromStream(istream, charset, t.input.length, t.type)), + t.expected, "parseFromStream test for " + t.type + ", charset=" + charset); + } + } + throws(function() { + parser.parseFromString("<xml></xml>", "foo/bar"); + }, "TypeError", "parseFromString should throw for the unknown type"); +} + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_fileconstructor.xhtml b/dom/base/test/chrome/test_fileconstructor.xhtml new file mode 100644 index 0000000000..8884eb169b --- /dev/null +++ b/dom/base/test/chrome/test_fileconstructor.xhtml @@ -0,0 +1,86 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=607114.xul +--> +<window title="Mozilla Bug 607114" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=607114"> + Mozilla Bug 607114</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 607114 **/ + +// eslint-disable-next-line mozilla/no-addtask-setup +add_task(async function setup() { + await SpecialPowers.pushPrefEnv({ set: [[ "dom.file.createInChild", true ]]}); +}); + +add_task(async function test() { + var file = SpecialPowers.Services.dirsvc.get("CurWorkD", Ci.nsIFile); + // man I wish this were simpler ... + file.append("chrome"); + file.append("dom"); + file.append("base"); + file.append("test"); + file.append("chrome"); + file.append("fileconstructor_file.png"); + + let domFile = await File.createFromFileName(file.path); + + ok(File.isInstance(domFile), "File() should return a File"); + is(domFile.type, "image/png", "File should be a PNG"); + is(domFile.size, 95, "File has size 95 (and more importantly we can read it)"); + + domFile = await File.createFromNsIFile(file); + ok(File.isInstance(domFile), "File() should return a File for an nsIFile"); + is(domFile.type, "image/png", "File should be a PNG"); + is(domFile.size, 95, "File has size 95 (and more importantly we can read it)"); + + try { + await File.createFromFileName( + "i/sure/hope/this/does/not/exist/anywhere.txt" + ); + ok(false, "Attempt to construct a non-existent file should fail."); + } catch (ex) { + is( + ex.result, + Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH, + "Constructing a non-existing file should fail with the correct error code" + ); + } + let dir = SpecialPowers.Services.dirsvc.get("CurWorkD", Ci.nsIFile); + try { + await File.createFromNsIFile(dir); + ok(false, "Attempt to construct a file from a directory should fail."); + } catch (ex) { + is( + ex.result, + Cr.NS_ERROR_FILE_IS_DIRECTORY, + "Constructing a file from a directory should fail with the correct error code" + ); + } +}) +]]> +</script> + +</window> diff --git a/dom/base/test/chrome/test_getElementsWithGrid.html b/dom/base/test/chrome/test_getElementsWithGrid.html new file mode 100644 index 0000000000..3554acb2e9 --- /dev/null +++ b/dom/base/test/chrome/test_getElementsWithGrid.html @@ -0,0 +1,121 @@ +<!doctype html> +<html id="root" class="g"> +<head> +<meta charset="utf-8"> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +<style> +.no-match { + display: block; +} +.g { + display: grid; +} +.s { + display: subgrid; +} +.gi { + display: inline-grid; +} +</style> + +<script> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +function testTargetsAreInElements(targets, elements) { + let c = 0; + for (let target of targets) { + if (c >= elements.length) { + ok(false, "We shouldn't have more targets than elements found."); + break; + } + let element = elements[c]; + let isMatching = (target.id == element.id); + let test_function = (target.todo ? todo : ok); + + test_function(isMatching, "Should find " + target.message + "."); + + // Only move to the next element in the elements if this one was a match. + // This handles the case of an unexpected element showing up, and prevents + // cascading errors in that case. If we've instead screwed up the target + // list, then we will get cascading errors. + if (isMatching) { + ++c; + } + } + + // Make sure we don't have any extra elements after going through all the targets. + is(c, elements.length, "We shouldn't have more elements than we have targets."); +} + +function runTests() { + // Part 1: Look for all the grid elements starting from the document root. + let elementsFromRoot = document.documentElement.getElementsWithGrid(); + + // Check that the expected elements were returned. + // Targets are provided in order we expect them to appear. + // Has to end in a non-todo element in order for testing logic to work. + let targetsFromRoot = [ + {id: "root", message: "root with display:grid"}, + {id: "a", message: "'plain' grid container with display:grid"}, + {id: "b", message: "display:subgrid inside display:grid (to be fixed in Bug 1240834)", todo: true}, + {id: "c", message: "'plain' grid container with display:inline-grid"}, + {id: "d", message: "display:subgrid inside display:inline-grid (to be fixed in Bug 1240834)", todo: true}, + {id: "e", message: "grid container with visibility:hidden"}, + {id: "f", message: "grid container inside an element"}, + {id: "g", message: "overflow:scroll grid container"}, + {id: "h", message: "button as a grid container"}, + {id: "i", message: "fieldset as a grid container"}, + {id: "k1", message: "grid container containing a grid container"}, + {id: "k2", message: "grid container inside a grid container"}, + ]; + is(elementsFromRoot.length, 10, "Found expected number of elements within document root."); + testTargetsAreInElements(targetsFromRoot, elementsFromRoot); + + + // Part 2: Look for all the grid elements starting from a non-root element. + let elementsFromNonRoot = document.getElementById("a_non_root_element").getElementsWithGrid(); + + let targetsFromNonRoot = [ + {id: "f", message: "grid container inside an element (from non-root element)"}, + ]; + is(elementsFromNonRoot.length, 1, "Found expected number of elements from non-root element."); + testTargetsAreInElements(targetsFromNonRoot, elementsFromNonRoot); + + SimpleTest.finish(); +} +</script> +</head> +<body onLoad="runTests();"> + +<div id="a" class="g"> + <div class="no-match"></div> + <div id="b" class="s"></div> +</div> + +<div class="no-match"></div> + +<div id="c" class="gi"> + <div id="d" class="s"></div> +</div> + +<div id="e" class="g" style="visibility:hidden"></div> + +<div id="a_non_root_element"><div id="f" class="g"></div></div> + +<div class="no-match"></div> + +<div id="g" style="overflow:scroll" class="g"></div> + +<button id="h" class="g"></button> + +<fieldset id="i" class="g"></fieldset> + +<div id="a_display_none_element" style="display:none"><div id="j" class="g"></div></div> + +<div id="k1" class="g"><div id="k2" class="g"></div></div> + +</body> +</html> diff --git a/dom/base/test/chrome/test_input_value_set_preserve_undo.xhtml b/dom/base/test/chrome/test_input_value_set_preserve_undo.xhtml new file mode 100644 index 0000000000..80465202ab --- /dev/null +++ b/dom/base/test/chrome/test_input_value_set_preserve_undo.xhtml @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<window title="Bug 1676785" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> +<html:body> + <xul:hbox> + <html:input id="xul" /> + </xul:hbox> + <html:div> + <html:input id="non-xul" /> + </html:div> +</html:body> +<script class="testbody"> +SimpleTest.waitForExplicitFinish(); + +function shouldPreserveHistory(input, preserve) { + input.focus(); + input.value = "abc"; + input.value = "def"; + let ctrl = navigator.platform.indexOf("Mac") == 0 ? { metaKey: true } : { ctrlKey: true }; + synthesizeKey("z", ctrl); + (preserve ? is : isnot)(input.value, "abc", `Expected ${input.id} to ${preserve ? "" : "not "}preserve undo history when setting .value`); +} + +window.onload = function() { + shouldPreserveHistory(document.getElementById("xul"), true); + shouldPreserveHistory(document.getElementById("non-xul"), false); + + SimpleTest.finish(); +} +</script> +</window> diff --git a/dom/base/test/chrome/test_nsITextInputProcessor.xhtml b/dom/base/test/chrome/test_nsITextInputProcessor.xhtml new file mode 100644 index 0000000000..3343c28560 --- /dev/null +++ b/dom/base/test/chrome/test_nsITextInputProcessor.xhtml @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Testing nsITextInputProcessor behavior" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); +window.openDialog("window_nsITextInputProcessor.xhtml", "_blank", + "chrome,width=600,height=600,noopener", window); + +]]> +</script> +</window> diff --git a/dom/base/test/chrome/test_nsITextInputProcessorCallback_at_changing_default_value_of_textarea.html b/dom/base/test/chrome/test_nsITextInputProcessorCallback_at_changing_default_value_of_textarea.html new file mode 100644 index 0000000000..179ffee086 --- /dev/null +++ b/dom/base/test/chrome/test_nsITextInputProcessorCallback_at_changing_default_value_of_textarea.html @@ -0,0 +1,107 @@ +<!doctype html> +<html> +<head> +<meta charset="utf-8"> +<title>Text change notifications at updating default value of non-dirty textarea</title> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> +<script> +"use strict"; + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(async () => { + const textarea = document.createElement("textarea"); + document.body.appendChild(textarea); + textarea.focus(); + + function stringifyTextChangeNotification(aNotification) { + if (!aNotification) { + return "{}"; + } + return `{ offset: ${aNotification.offset}, removedLength: ${aNotification.removedLength}, addedLength: ${aNotification.addedLength} }`; + } + + const tip = + Cc["@mozilla.org/text-input-processor;1"]. + createInstance(Ci.nsITextInputProcessor); + let notifications = []; + function callback(aTIP, aNotification) { + switch (aNotification.type) { + case "request-to-commit": + aTIP.commitComposition(); + break; + case "request-to-cancel": + aTIP.cancelComposition(); + break; + case "notify-text-change": + notifications.push(aNotification); + break; + } + return true; + } + + tip.beginInputTransactionForTests(window, callback); + + notifications = []; + textarea.appendChild(document.createTextNode("abc")); + await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); + is( + stringifyTextChangeNotification(notifications[0]), + stringifyTextChangeNotification({ offset: 0, removedLength: 0, addedLength: "abc".length }), + "Adding text node to empty <textarea> should notify IME of a text change" + ); + + notifications = []; + textarea.firstChild.remove(); + await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); + is( + stringifyTextChangeNotification(notifications[0]), + stringifyTextChangeNotification({ offset: 0, removedLength: "abc".length, addedLength: 0 }), + "Removing text node from <textarea> should notify IME of a text change" + ); + + // Update default value during reframes + notifications = []; + textarea.setAttribute("dir", "rtl"); + textarea.prepend("abc", document.createElement("marquee")); + await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); + is( + stringifyTextChangeNotification(notifications[0]), + stringifyTextChangeNotification({ offset: 0, removedLength: 0, addedLength: "abc".length }), + "Adding text node to empty <textarea> during reframes should notify IME of a text change" + ); + + notifications = []; + textarea.removeAttribute("dir"); + textarea.innerHTML = ""; + await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); + is( + stringifyTextChangeNotification(notifications[0]), + stringifyTextChangeNotification({ offset: 0, removedLength: "abc".length, addedLength: 0 }), + "Removing text node from <textarea> during reframes should notify IME of a text change" + ); + + // Make the textarea dirty + textarea.value = "X"; + await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); + + notifications = []; + textarea.appendChild(document.createTextNode("abc")); + await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); + is( + textarea.value, + "X", + "The value should not be updated by adding text node into the dirty <textarea>" + ); + is( + stringifyTextChangeNotification(notifications[0]), + stringifyTextChangeNotification(undefined), + "Adding text node to empty but dirty <textarea> should not notify IME of a text change" + ); + + SimpleTest.finish(); +}); +</script> +</head> +<body></body> +</html> diff --git a/dom/base/test/chrome/test_permission_hasValidTransientUserActivation.xhtml b/dom/base/test/chrome/test_permission_hasValidTransientUserActivation.xhtml new file mode 100644 index 0000000000..89da945588 --- /dev/null +++ b/dom/base/test/chrome/test_permission_hasValidTransientUserActivation.xhtml @@ -0,0 +1,93 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + Tests that the hasValidTransientUserGestureActivation attribute on permission requests is set correctly. +--> +<window title="hasValidTransientUserGestureActivation test" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <iframe id="frame" src="https://example.com/chrome/dom/base/test/chrome/dummy.html" /> + </body> + + <script type="application/javascript"> + <![CDATA[ + const {Integration} = ChromeUtils.importESModule( + "resource://gre/modules/Integration.sys.mjs" + ); + + SimpleTest.waitForExplicitFinish(); + + let frame = document.getElementById("frame"); + + function checkPermissionRequest(permission, hasValidTransientUserGestureActivation) { + return new Promise(function(resolve) { + let TestIntegration = (base) => ({ + __proto__: base, + createPermissionPrompt(type, request) { + is(type, permission, `Has correct permission type ${permission}.`); + is(request.hasValidTransientUserGestureActivation, hasValidTransientUserGestureActivation, + "The hasValidTransientUserGestureActivation attribute is set correctly."); + Integration.contentPermission.unregister(TestIntegration); + resolve(); + return { prompt() {} }; + }, + }); + Integration.contentPermission.register(TestIntegration); + }); + } + + async function runTest() { + await SpecialPowers.setBoolPref("dom.webnotifications.allowcrossoriginiframe", true); + + // Test programmatic request for persistent storage. + let request = checkPermissionRequest("persistent-storage", false); + navigator.storage.persist(); + await request; + + // Test user-initiated request for persistent storage. + request = checkPermissionRequest("persistent-storage", true); + document.notifyUserGestureActivation(); + navigator.storage.persist(); + await request; + content.document.clearUserGestureActivation(); + + // Test programmatic request for geolocation. + request = checkPermissionRequest("geolocation", false); + navigator.geolocation.getCurrentPosition(() => {}); + await request; + + // Test user-initiated request for geolocation. + request = checkPermissionRequest("geolocation", true); + document.notifyUserGestureActivation(); + navigator.geolocation.getCurrentPosition(() => {}); + await request; + document.clearUserGestureActivation(); + + // Notifications need to be tested in an HTTPS frame, because + // chrome:// URLs are whitelisted. + let frameWin = frame.contentWindow; + + // Test programmatic request for notifications. + request = checkPermissionRequest("desktop-notification", false); + frameWin.Notification.requestPermission(); + await request; + + // Test user-initiated request for notifications. + request = checkPermissionRequest("desktop-notification", true); + frameWin.document.notifyUserGestureActivation(); + frameWin.Notification.requestPermission(); + await request; + frameWin.document.clearUserGestureActivation(); + + await SpecialPowers.clearUserPref("dom.webnotifications.allowcrossoriginiframe"); + } + + frame.addEventListener("load", function() { + runTest().then(() => SimpleTest.finish()); + }); + ]]> + </script> +</window> diff --git a/dom/base/test/chrome/test_range_getClientRectsAndTexts.html b/dom/base/test/chrome/test_range_getClientRectsAndTexts.html new file mode 100644 index 0000000000..10eddaf522 --- /dev/null +++ b/dom/base/test/chrome/test_range_getClientRectsAndTexts.html @@ -0,0 +1,74 @@ +<html> +<head> +<meta charset="utf-8"> +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +<script> +"use strict"; + +SimpleTest.waitForExplicitFinish(); + +function runTests() { + let range = document.createRange(); + + let attempts = [ + {startNode: "one", start: 0, endNode: "one", end: 0, textList: [], message: "Empty rect"}, + {startNode: "one", start: 2, endNode: "one", end: 6, textList: ["l on"], message: "Single line"}, + {startNode: "implicit", start: 6, endNode: "implicit", end: 12, textList: ["it bre"], message: "Implicit break"}, + {startNode: "two.a", start: 1, endNode: "two.b", end: 2, textList: ["wo", "", "li"], message: "Two lines"}, + {startNode: "embed.a", start: 7, endNode: "embed.b", end: 2, textList: ["th ", "simple nested", " ", "te"], message: "Simple nested"}, + {startNode: "deep.a", start: 2, endNode: "deep.b", end: 2, textList: ["ne with ", "complex, more deeply nested", " ", "te"], message: "Complex nested"}, + {startNode: "image.a", start: 7, endNode: "image.b", end: 2, textList: ["th inline ", "", " ", "im"], message: "Inline image"}, + {startNode: "hyphen1", start: 0, endNode: "hyphen1", end: 3, textList: ["a\u00AD", "b"], message: "Shy hyphen (active)"}, + {startNode: "hyphen2", start: 0, endNode: "hyphen2", end: 3, textList: ["c\u00ADd"], message: "Shy hyphen (inactive)"}, + {startNode: "hyphen2", start: 0, endNode: "hyphen2", end: 2, textList: ["c\u00AD"], message: "Shy hyphen (inactive, trailing)"}, + {startNode: "hyphen2", start: 1, endNode: "hyphen2", end: 3, textList: ["\u00ADd"], message: "Shy hyphen (inactive, leading)"}, + {startNode: "uc", start: 0, endNode: "uc", end: 2, textList: ["EF"], message: "UC transform"}, + {startNode: "pre", start: 0, endNode: "pre", end: 3, textList: ["g\n", "h"], message: "pre with break"}, + ]; + + for (let attempt of attempts) { + range.setStart(document.getElementById(attempt.startNode).childNodes[0], attempt.start); + range.setEnd(document.getElementById(attempt.endNode).childNodes[0], attempt.end); + + let result = range.getClientRectsAndTexts(); + + is(result.textList.length, attempt.textList.length, attempt.message + " range has expected number of texts."); + let i = 0; + for (let text of result.textList) { + is(text, attempt.textList[i], attempt.message + " matched text index " + i + "."); + i++; + } + } + + SimpleTest.finish(); +} +</script> +</head> +<body onLoad="runTests()"> + +<div id="one">All on one line</div> + +<div id="implicit">Implicit +break in one line</div> + +<div id="two.a">Two<br/ +><span id="two.b">lines</span></div> + +<div id="embed.a">Line with <span>simple nested</span> <span id="embed.b">text</span></div> + +<div id="deep.a">Line with <span>complex, <span>more <span>deeply <span>nested</span></span></span></span> <span id="deep.b">text</span></div> + +<div id="image.a">Line with inline <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC" width="20" height="20"/> <span id="image.b">image</span></div> + +<div id="hyphen1" style="width:0">a­b</div> + +<div id="hyphen2" style="width:100px">c­d</div> + +<div id="uc" style="text-transform:uppercase">ef</div> + +<pre id="pre">g +h</pre> + +</body> +</html>
\ No newline at end of file diff --git a/dom/base/test/chrome/test_swapFrameLoaders.xhtml b/dom/base/test/chrome/test_swapFrameLoaders.xhtml new file mode 100644 index 0000000000..4ea11a1a62 --- /dev/null +++ b/dom/base/test/chrome/test_swapFrameLoaders.xhtml @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1242644 +Test swapFrameLoaders with different frame types and remoteness +--> +<window title="Mozilla Bug 1242644" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1242644" + target="_blank">Mozilla Bug 1242644</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + window.openDialog("window_swapFrameLoaders.xhtml", "bug1242644", + "chrome,width=600,height=600,noopener", window); + ]]></script> +</window> diff --git a/dom/base/test/chrome/test_title.xhtml b/dom/base/test/chrome/test_title.xhtml new file mode 100644 index 0000000000..33b3fd0220 --- /dev/null +++ b/dom/base/test/chrome/test_title.xhtml @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=481777 +--> +<window title="Mozilla Bug 481777" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=481777" + target="_blank">Mozilla Bug 481777</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 481777 **/ + + SimpleTest.waitForExplicitFinish(); + window.openDialog("title_window.xhtml", "bug481777", + "chrome,width=100,height=100,noopener", window); + ]]> + </script> +</window> diff --git a/dom/base/test/chrome/test_windowroot.xhtml b/dom/base/test/chrome/test_windowroot.xhtml new file mode 100644 index 0000000000..6f1c29095b --- /dev/null +++ b/dom/base/test/chrome/test_windowroot.xhtml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Test window.windowRoot" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + var root = window.windowRoot; + ok(WindowRoot.isInstance(root), "windowRoot should be a WindowRoot"); + ]]></script> +</window> diff --git a/dom/base/test/chrome/title_window.xhtml b/dom/base/test/chrome/title_window.xhtml new file mode 100644 index 0000000000..f48cdaaaf1 --- /dev/null +++ b/dom/base/test/chrome/title_window.xhtml @@ -0,0 +1,197 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<window title="Mozilla Bug 481777 subwindow" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="runTests()"> + + <iframe type="content" id="html1" src="data:text/html,<html><head><title id='t'>Test</title></head></html>"/> + <iframe type="content" id="html2" src="data:text/html,<html><head><title id='t'>Test</title><title>Foo</title></head></html>"/> + <iframe type="content" id="html3" src="data:text/html,<html></html>"/> + <iframe type="content" id="xhtml1" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'><body><title id='t'>Test</title></body></html>"/> + <iframe type="content" id="xhtml2" src="data:text/xml,<title xmlns='http://www.w3.org/1999/xhtml'>Test</title>"/> + <iframe type="content" id="xhtml3" src="data:text/xml,<title xmlns='http://www.w3.org/1999/xhtml'>Te<div>bogus</div>st</title>"/> + <iframe type="content" id="xhtml4" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'/>"/> + <iframe type="content" id="xhtml5" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'><head/></html>"/> + <iframe type="content" id="xhtml6" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'><head><style/></head></html>"/> + <iframe id="xul1" src="file_title.xhtml"/> + <iframe id="xul2" src="file_title.xhtml"/> + <iframe type="content" id="svg1" src="data:text/xml,<svg xmlns='http://www.w3.org/2000/svg'><title id='t'>Test</title></svg>"/> + <iframe type="content" id="svg2" src="data:text/xml,<svg xmlns='http://www.w3.org/2000/svg'><title id='t'>Test</title></svg>"/> + + <script type="application/javascript"> + <![CDATA[ + var imports = [ "SimpleTest", "is", "isnot", "ok" ]; + for (var name of imports) { + window[name] = window.arguments[0][name]; + } + + function testStatics() { + function testStatic(id, expect, description) { + is(document.getElementById(id).contentDocument.title, expect, description); + } + + testStatic("html1", "Test", "HTML <title>"); + testStatic("html2", "Test", "choose the first HTML <title>"); + testStatic("html3", "", "No title"); + testStatic("xhtml1", "Test", "XHTML <title> in body"); + testStatic("xhtml2", "Test", "XHTML <title> as root element"); + testStatic("xhtml3", "Test", "XHTML <title> containing an element"); + testStatic("xul1", "Test", "XUL <window> title attribute"); + testStatic("svg1", "Test", "SVG <title>"); + + // This one does nothing and won't fire an event + document.getElementById("xhtml4").contentDocument.title = "Hello"; + is(document.getElementById("xhtml4").contentDocument.title, "", "Setting 'title' does nothing with no <head>"); + } + + function testDynamics() { + var inProgress = {}; + var inProgressDoc = {}; + var inProgressWin = {}; + function testDynamic(id, expect, description, op, checkDOM) { + inProgress[description] = true; + inProgressDoc[description] = true; + inProgressWin[description] = true; + var frame = document.getElementById(id); + + function listener(ev) { + inProgress[description] = false; + is(frame.contentDocument.title, expect, "'title': " + description); + is(frame.contentDocument, ev.target, "Unexpected target: " + description); + if (typeof(checkDOM) != "undefined") { + checkDOM(frame.contentDocument, "DOM: " + description); + } + } + + function listener2(ev) { + inProgressDoc[description] = false; + } + function listener3(ev) { + inProgressWin[description] = false; + } + frame.addEventListener("DOMTitleChanged", listener); + frame.contentDocument.addEventListener("DOMTitleChanged", listener2); + frame.contentWindow.addEventListener("DOMTitleChanged", listener3); + + op(frame.contentDocument); + } + + var dynamicTests = [ + [ "html1", "Hello", "Setting HTML <title> text contents", + function(doc){ + var t = doc.getElementById("t"); t.textContent = "Hello"; + } ], + [ "html2", "Foo", "Removing HTML <title>", + function(doc){ + var t = doc.getElementById("t"); t.remove(); + } ], + [ "html3", "Hello", "Appending HTML <title> element to root element", + function(doc){ + var t = doc.createElement("title"); t.textContent = "Hello"; doc.documentElement.appendChild(t); + } ], + [ "xhtml3", "Hello", "Setting 'title' clears existing <title> contents", + function(doc){ + doc.title = "Hello"; + }, + function(doc, desc) { + is(doc.documentElement.firstChild.data, "Hello", desc); + is(doc.documentElement.firstChild.nextSibling, null, desc); + } ], + [ "xhtml5", "Hello", "Setting 'title' works with a <head>", + function(doc){ + doc.title = "Hello"; + }, + function(doc, desc) { + var head = doc.documentElement.firstChild; + var title = head.firstChild; + is(title.tagName.toLowerCase(), "title", desc); + is(title.firstChild.data, "Hello", desc); + is(title.firstChild.nextSibling, null, desc); + is(title.nextSibling, null, desc); + } ], + [ "xhtml6", "Hello", "Setting 'title' appends to <head>", + function(doc){ + doc.title = "Hello"; + }, + function(doc, desc) { + var head = doc.documentElement.firstChild; + is(head.firstChild.tagName.toLowerCase(), "style", desc); + var title = head.firstChild.nextSibling; + is(title.tagName.toLowerCase(), "title", desc); + is(title.firstChild.data, "Hello", desc); + is(title.firstChild.nextSibling, null, desc); + is(title.nextSibling, null, desc); + } ], + [ "xul1", "Hello", "Setting XUL <window> title attribute", + function(doc){ + doc.documentElement.setAttribute("title", "Hello"); + } ], + [ "xul2", "Hello", "Setting 'title' in XUL", + function(doc){ + doc.title = "Hello"; + }, + function(doc, desc) { + is(doc.documentElement.getAttribute("title"), "Hello", desc); + is(doc.documentElement.firstChild, null, desc); + } ], + [ "svg1", "Hello", "Setting SVG <title> text contents", + function(doc){ + var t = doc.getElementById("t"); t.textContent = "Hello"; + } ], + [ "svg2", "", "Removing SVG <title>", + function(doc){ + var t = doc.getElementById("t"); t.remove(); + } ] ]; + + var titleWindow = window; + + function runIndividualTest(i) { + if (i == dynamicTests.length) { + // Closing the window will nuke the global properties, since this + // function is not really running on this window... or something + // like that. Thanks, executeSoon! + var tester = SimpleTest; + window.close(); + tester.finish(); + } else { + var parameters = dynamicTests[i]; + var testElementId = parameters[0]; + var testExpectedValue = parameters[1]; + var testDescription = parameters[2]; + var testOp = parameters[3]; + var testCheckDOM = parameters[4]; + + function checkTest() { + ok(!inProgress[testDescription], + testDescription + ": DOMTitleChange not fired"); + ok(inProgressDoc[testDescription], + testDescription + ": DOMTitleChange fired on content document"); + ok(inProgressWin[testDescription], + testDescription + ": DOMTitleChange fired on content window"); + // Run the next test in the context of the parent XUL window. + titleWindow.setTimeout(runIndividualTest, 0, i+1); + } + function spinEventLoopOp(doc) { + // Perform the test's operations. + testOp(doc); + // Spin the associated window's event loop to ensure we + // drain any asynchronous changes and fire associated + // events. + doc.defaultView.setTimeout(checkTest, 0); + } + + testDynamic(testElementId, testExpectedValue, testDescription, + spinEventLoopOp, testCheckDOM); + } + } + + window.setTimeout(runIndividualTest, 0, 0); + } + + function runTests() { + testStatics(); + testDynamics(); + } + ]]> + </script> +</window> diff --git a/dom/base/test/chrome/window_chromeOuterWindowID.xhtml b/dom/base/test/chrome/window_chromeOuterWindowID.xhtml new file mode 100644 index 0000000000..268409c1bf --- /dev/null +++ b/dom/base/test/chrome/window_chromeOuterWindowID.xhtml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1474662 +Test that the chromeOuterWindowID on the MessageManager interface +works, and that it properly updates when swapping frameloaders between +windows. +--> +<window title="Mozilla Bug 1242644" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <browser id="non-remote" nodefaultsrc="true" type="content" flex="1"/> + <browser id="remote" remote="true" nodefaultsrc="true" type="content" flex="1"/> +</window>
\ No newline at end of file diff --git a/dom/base/test/chrome/window_nsITextInputProcessor.xhtml b/dom/base/test/chrome/window_nsITextInputProcessor.xhtml new file mode 100644 index 0000000000..c8ce6ee5e7 --- /dev/null +++ b/dom/base/test/chrome/window_nsITextInputProcessor.xhtml @@ -0,0 +1,4874 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Testing nsITextInputProcessor behavior" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onunload="onunload();"> +<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> +<body xmlns="http://www.w3.org/1999/xhtml"> +<div id="display"> +<input id="input" type="text"/><input id="anotherInput" type="text"/><br/> +<textarea></textarea> +<iframe id="iframe" width="300" height="150" + src="data:text/html,<textarea id='textarea' cols='20' rows='4'></textarea>"></iframe><br/> +<div contenteditable=""><br/></div> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var SimpleTest = window.arguments[0].SimpleTest; + +SimpleTest.waitForFocus(runTests, window); + +function getHTMLEditor(aWindow) { + return SpecialPowers.wrap(aWindow).docShell.editingSession?.getEditorForWindow(aWindow); +} + +function ok(aCondition, aMessage) +{ + SimpleTest.ok(aCondition, aMessage); +} + +function is(aLeft, aRight, aMessage) +{ + SimpleTest.is(aLeft, aRight, aMessage); +} + +function isnot(aLeft, aRight, aMessage) +{ + SimpleTest.isnot(aLeft, aRight, aMessage); +} + +function todo_is(aLeft, aRight, aMessage) +{ + SimpleTest.todo_is(aLeft, aRight, aMessage); +} + +function info(aMessage) { + SimpleTest.info(aMessage); +} + +function finish() +{ + window.close(); +} + +function onunload() +{ + SimpleTest.finish(); +} + +function checkInputEvent(aEvent, aCancelable, aIsComposing, aInputType, aData, aDescription) { + if (aEvent.type !== "input" && aEvent.type !== "beforeinput") { + throw new Error(`${aDescription}"${aEvent.type}" is not InputEvent`); + } + ok(InputEvent.isInstance(aEvent), `${aDescription}"${aEvent.type}" event should be dispatched with InputEvent interface`); + is(aEvent.cancelable, aCancelable, `${aDescription}"${aEvent.type}" event should ${aCancelable ? "be" : "not be"} cancelable`); + is(aEvent.bubbles, true, `${aDescription}"${aEvent.type}" event should always bubble`); + is(aEvent.isComposing, aIsComposing, `${aDescription}isComposing of "${aEvent.type}" event should be ${aIsComposing}`); + is(aEvent.inputType, aInputType, `${aDescription}inputType of "${aEvent.type}" event should be "${aInputType}"`); + is(aEvent.data, aData, `${aDescription}data of "${aEvent.type}" event should be "${aData}"`); + is(aEvent.dataTransfer, null, `${aDescription}dataTransfer of "${aEvent.type}" event should be null`); + is(aEvent.getTargetRanges().length, 0, `${aDescription}getTargetRanges() of "${aEvent.type}" event should return empty array`); +} + +const kIsMac = (navigator.platform.indexOf("Mac") == 0); + +const iframe = document.getElementById("iframe"); +let childWindow = iframe.contentWindow; +let textareaInFrame; +let input = document.getElementById("input"); +const textarea = document.querySelector("textarea"); +const otherWindow = window.arguments[0]; +const otherDocument = otherWindow.document; +const inputInChildWindow = otherDocument.getElementById("input"); +const contenteditable = document.querySelector("div[contenteditable]"); +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const kLF = navigator.platform.startsWith("Win") && false ? "\r\n" : "\n"; +function getNativeText(aXPText) +{ + if (kLF == "\n") { + return aXPText; + } + return aXPText.replace(/\n/g, kLF); +} + +function createTIP() +{ + return Cc["@mozilla.org/text-input-processor;1"]. + createInstance(Ci.nsITextInputProcessor); +} + +function runBeginInputTransactionMethodTests() +{ + var description = "runBeginInputTransactionMethodTests: "; + input.value = ""; + input.focus(); + + var simpleCallback = function (aTIP, aNotification) + { + switch (aNotification.type) { + case "request-to-commit": + aTIP.commitComposition(); + break; + case "request-to-cancel": + aTIP.cancelComposition(); + break; + } + return true; + }; + + var TIP1 = createTIP(); + var TIP2 = createTIP(); + isnot(TIP1, TIP2, + description + "TIP instances should be different"); + + // beginInputTransaction() and beginInputTransactionForTests() can take ownership if there is no composition. + ok(TIP1.beginInputTransaction(window, simpleCallback), + description + "TIP1.beginInputTransaction(window) should succeed because there is no composition"); + ok(TIP1.beginInputTransactionForTests(window), + description + "TIP1.beginInputTransactionForTests(window) should succeed because there is no composition"); + ok(TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2.beginInputTransaction(window) should succeed because there is no composition"); + ok(TIP2.beginInputTransactionForTests(window), + description + "TIP2.beginInputTransactionForTests(window) should succeed because there is no composition"); + + // Start composition with TIP1, then, other TIPs cannot take ownership during a composition. + ok(TIP1.beginInputTransactionForTests(window), + description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition"); + var composingStr = "foo"; + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + ok(TIP1.flushPendingComposition(), + description + "TIP1.flushPendingComposition() should return true becuase it should be valid composition"); + is(input.value, composingStr, + description + "The input element should have composing string"); + + // Composing nsITextInputProcessor instance shouldn't allow initialize it again. + try { + TIP1.beginInputTransaction(window, simpleCallback); + ok(false, + "TIP1.beginInputTransaction(window) should cause throwing an exception because it's composing with different purpose"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(window) should cause throwing an exception including NS_ERROR_ALREADY_INITIALIZED because it's composing for tests"); + } + try { + TIP1.beginInputTransactionForTests(otherWindow); + ok(false, + "TIP1.beginInputTransactionForTests(otherWindow) should cause throwing an exception because it's composing on different window"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow) should cause throwing an exception including NS_ERROR_ALREADY_INITIALIZED because it's composing on this window"); + } + ok(TIP1.beginInputTransactionForTests(window), + description + "TIP1.beginInputTransactionForTests(window) should succeed because TextEventDispatcher was initialized with same purpose"); + ok(TIP1.beginInputTransactionForTests(childWindow), + description + "TIP1.beginInputTransactionForTests(childWindow) should succeed because TextEventDispatcher was initialized with same purpose and is shared by window and childWindow"); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2.beginInputTransaction(window) should not succeed because there is composition synthesized by TIP1"); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2.beginInputTransactionForTests(window) should not succeed because there is composition synthesized by TIP1"); + ok(!TIP2.beginInputTransaction(childWindow, simpleCallback), + description + "TIP2.beginInputTransaction(childWindow) should not succeed because there is composition synthesized by TIP1"); + ok(!TIP2.beginInputTransactionForTests(childWindow), + description + "TIP2.beginInputTransactionForTests(childWindow) should not succeed because there is composition synthesized by TIP1"); + ok(TIP2.beginInputTransaction(otherWindow, simpleCallback), + description + "TIP2.beginInputTransaction(otherWindow) should succeed because there is composition synthesized by TIP1 but it's in other window"); + ok(TIP2.beginInputTransactionForTests(otherWindow), + description + "TIP2.beginInputTransactionForTests(otherWindow) should succeed because there is composition synthesized by TIP1 but it's in other window"); + + // Let's confirm that the composing string is NOT committed by above tests. + TIP1.commitComposition(); + is(input.value, composingStr, + description + "TIP1.commitString() without specifying commit string should commit current composition with the last composing string"); + ok(!TIP1.hasComposition, + description + "TIP1.commitString() without specifying commit string should've end composition"); + + ok(TIP1.beginInputTransaction(window, simpleCallback), + description + "TIP1.beginInputTransaction() should succeed because there is no composition #2"); + ok(TIP1.beginInputTransactionForTests(window), + description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition #2"); + ok(TIP2.beginInputTransactionForTests(window), + description + "TIP2.beginInputTransactionForTests() should succeed because the composition was already committed #2"); + + // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during startComposition(). + var events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + // eslint-disable-next-line no-caller + input.removeEventListener(aEvent.type, arguments.callee); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.startComposition();"); + }); + TIP1.beginInputTransaction(window, simpleCallback); + TIP1.startComposition(); + is(events.length, 1, + description + "compositionstart event should be fired by TIP1.startComposition()"); + TIP1.cancelComposition(); + + // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during flushPendingComposition(). + events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during a call of TIP1.flushPendingComposition();"); + }, {once: true}); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from compositionupdate event handler during a call of TIP1.flushPendingComposition();"); + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.flushPendingComposition();"); + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.flushPendingComposition();"); + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.flushPendingComposition();"); + }, {once: true}); + TIP1.beginInputTransaction(window, simpleCallback); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + is(events.length, 5, + description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()"); + is(events[0].type, "compositionstart", + description + "events[0] should be compositionstart"); + is(events[1].type, "compositionupdate", + description + "events[1] should be compositionupdate"); + is(events[2].type, "text", + description + "events[2] should be text"); + is(events[3].type, "beforeinput", + description + "events[3] should be beforeinput"); + checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description); + is(events[4].type, "input", + description + "events[4] should be input"); + checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description); + TIP1.cancelComposition(); + + // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitComposition(). + events = []; + TIP1.beginInputTransaction(window, simpleCallback); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.commitComposition();"); + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from compositionend event handler during a call of TIP1.commitComposition();"); + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.commitComposition();"); + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.commitComposition();"); + }, {once: true}); + TIP1.commitComposition(); + is(events.length, 4, + description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()"); + is(events[0].type, "text", + description + "events[0] should be text"); + is(events[1].type, "beforeinput", + description + "events[1] should be beforeinput"); + checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description); + is(events[2].type, "compositionend", + description + "events[2] should be compositionend"); + is(events[3].type, "input", + description + "events[3] should be input"); + checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description); + + // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar"). + events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction during text event handler TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction during compositionend event handler TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction during beforeinput event handler TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction during input event handler TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + TIP1.beginInputTransaction(window, simpleCallback); + TIP1.commitCompositionWith("bar"); + is(events.length, 6, + description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")"); + is(events[0].type, "compositionstart", + description + "events[0] should be compositionstart"); + is(events[1].type, "compositionupdate", + description + "events[1] should be compositionupdate"); + is(events[2].type, "text", + description + "events[2] should be text"); + is(events[3].type, "beforeinput", + description + "events[3] should be beforeinput"); + checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description); + is(events[4].type, "compositionend", + description + "events[4] should be compositionend"); + is(events[5].type, "input", + description + "events[5] should be input"); + checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description); + + // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during cancelComposition(). + events = []; + TIP1.beginInputTransaction(window, simpleCallback); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from compositionupdate event handler during a call of TIP1.cancelComposition();"); + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from text event handler during a call of TIP1.cancelComposition();"); + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from compositionend event handler during a call of TIP1.cancelComposition();"); + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.cancelComposition();"); + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.cancelComposition();"); + }, {once: true}); + TIP1.cancelComposition(); + is(events.length, 5, + description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()"); + is(events[0].type, "compositionupdate", + description + "events[0] should be compositionupdate"); + is(events[1].type, "text", + description + "events[1] should be text"); + is(events[2].type, "beforeinput", + description + "events[2] should be beforeinput"); + checkInputEvent(events[2], false, true, "insertCompositionText", "", description); + is(events[3].type, "compositionend", + description + "events[3] should be compositionend"); + is(events[4].type, "input", + description + "events[4] should be input"); + checkInputEvent(events[4], false, false, "insertCompositionText", "", description); + + // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during keydown() and keyup(). + events = []; + TIP1.beginInputTransaction(window, simpleCallback); + input.addEventListener("keydown", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from keydown event handler during a call of TIP1.keydown();"); + }, {once: true}); + input.addEventListener("keypress", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from keypress event handler during a call of TIP1.keydown();"); + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from beforeinput event handler during a call of TIP1.keydown();"); + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from input event handler during a call of TIP1.keydown();"); + }, {once: true}); + input.addEventListener("keyup", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransaction(window, simpleCallback), + description + "TIP2 shouldn't be able to begin input transaction from keyup event handler during a call of TIP1.keyup();"); + }, {once: true}); + var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); + TIP1.keydown(keyA); + TIP1.keyup(keyA); + is(events.length, 5, + description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()"); + is(events[0].type, "keydown", + description + "events[0] should be keydown"); + is(events[1].type, "keypress", + description + "events[1] should be keypress"); + is(events[2].type, "beforeinput", + description + "events[2] should be beforeinput"); + checkInputEvent(events[2], true, false, "insertText", "a", description); + is(events[3].type, "input", + description + "events[3] should be input"); + checkInputEvent(events[3], false, false, "insertText", "a", description); + is(events[4].type, "keyup", + description + "events[4] should be keyup"); + + // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during startComposition(). + events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + // eslint-disable-next-line no-caller + input.removeEventListener(aEvent.type, arguments.callee); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.startComposition();"); + }); + TIP1.beginInputTransactionForTests(window); + TIP1.startComposition(); + is(events.length, 1, + description + "compositionstart event should be fired by TIP1.startComposition()"); + TIP1.cancelComposition(); + + // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during flushPendingComposition(). + events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during a call of TIP1.flushPendingComposition();"); + }, {once: true}); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from compositionupdate event handler during a call of TIP1.flushPendingComposition();"); + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.flushPendingComposition();"); + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.flushPendingComposition();"); + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.flushPendingComposition();"); + }, {once: true}); + TIP1.beginInputTransactionForTests(window); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + is(events.length, 5, + description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()"); + is(events[0].type, "compositionstart", + description + "events[0] should be compositionstart"); + is(events[1].type, "compositionupdate", + description + "events[1] should be compositionupdate"); + is(events[2].type, "text", + description + "events[2] should be text"); + is(events[3].type, "beforeinput", + description + "events[3] should be beforeinput"); + checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description); + is(events[4].type, "input", + description + "events[4] should be input"); + checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description); + TIP1.cancelComposition(); + + // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitComposition(). + events = []; + TIP1.beginInputTransactionForTests(window, simpleCallback); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.commitComposition();"); + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from compositionend event handler during a call of TIP1.commitComposition();"); + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.commitComposition();"); + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.commitComposition();"); + }, {once: true}); + TIP1.commitComposition(); + is(events.length, 4, + description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()"); + is(events[0].type, "text", + description + "events[0] should be text"); + is(events[1].type, "beforeinput", + description + "events[1] should be beforeinput"); + checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description); + is(events[2].type, "compositionend", + description + "events[2] should be compositionend"); + is(events[3].type, "input", + description + "events[3] should be input"); + checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description); + + // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar"). + events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests during text event handler TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests during compositionend event handler TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests during beforeinput event handler TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests during input event handler TIP1.commitCompositionWith(\"bar\");"); + }, {once: true}); + TIP1.beginInputTransactionForTests(window); + TIP1.commitCompositionWith("bar"); + is(events.length, 6, + description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")"); + is(events[0].type, "compositionstart", + description + "events[0] should be compositionstart"); + is(events[1].type, "compositionupdate", + description + "events[1] should be compositionupdate"); + is(events[2].type, "text", + description + "events[2] should be text"); + is(events[3].type, "beforeinput", + description + "events[3] should be beforeinput"); + checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description); + is(events[4].type, "compositionend", + description + "events[4] should be compositionend"); + is(events[5].type, "input", + description + "events[5] should be input"); + checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description); + + // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during cancelComposition(). + events = []; + TIP1.beginInputTransactionForTests(window, simpleCallback); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from compositionupdate event handler during a call of TIP1.cancelComposition();"); + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from text event handler during a call of TIP1.cancelComposition();"); + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from compositionend event handler during a call of TIP1.cancelComposition();"); + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.cancelComposition();"); + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.cancelComposition();"); + }, {once: true}); + TIP1.cancelComposition(); + is(events.length, 5, + description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()"); + is(events[0].type, "compositionupdate", + description + "events[0] should be compositionupdate"); + is(events[1].type, "text", + description + "events[1] should be text"); + is(events[2].type, "beforeinput", + description + "events[2] should be beforeinput"); + checkInputEvent(events[2], false, true, "insertCompositionText", "", description); + is(events[3].type, "compositionend", + description + "events[3] should be compositionend"); + is(events[4].type, "input", + description + "events[4] should be input"); + checkInputEvent(events[4], false, false, "insertCompositionText", "", description); + + // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during keydown() and keyup(). + events = []; + TIP1.beginInputTransactionForTests(window); + input.addEventListener("keydown", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests for tests from keydown event handler during a call of TIP1.keydown();"); + }, {once: true}); + input.addEventListener("keypress", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from keypress event handler during a call of TIP1.keydown();"); + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from beforeinput event handler during a call of TIP1.keydown();"); + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from input event handler during a call of TIP1.keydown();"); + }, {once: true}); + input.addEventListener("keyup", function (aEvent) { + events.push(aEvent); + ok(!TIP2.beginInputTransactionForTests(window), + description + "TIP2 shouldn't be able to begin input transaction for tests from keyup event handler during a call of TIP1.keyup();"); + }, {once: true}); + keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); + TIP1.keydown(keyA); + TIP1.keyup(keyA); + is(events.length, 5, + description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()"); + is(events[0].type, "keydown", + description + "events[0] should be keydown"); + is(events[1].type, "keypress", + description + "events[1] should be keypress"); + is(events[2].type, "beforeinput", + description + "events[2] should be beforeinput"); + checkInputEvent(events[2], true, false, "insertText", "a", description); + is(events[3].type, "input", + description + "events[3] should be input"); + checkInputEvent(events[3], false, false, "insertText", "a", description); + is(events[4].type, "keyup", + description + "events[4] should be keyup"); + + // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition(). + events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during startComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during startComposition()"); + } + }, {once: true}); + TIP1.beginInputTransaction(window, simpleCallback); + TIP1.startComposition(); + is(events.length, 1, + description + "compositionstart event should be fired by TIP1.startComposition()"); + TIP1.cancelComposition(); + + // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during flushPendingComposition(). + events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during flushPendingComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()"); + } + }, {once: true}); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during flushPendingComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()"); + } + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during flushPendingComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()"); + } + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during flushPendingComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()"); + } + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during flushPendingComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()"); + } + }, {once: true}); + TIP1.beginInputTransaction(window, simpleCallback); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + is(events.length, 5, + description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()"); + is(events[0].type, "compositionstart", + description + "events[0] should be compositionstart"); + is(events[1].type, "compositionupdate", + description + "events[1] should be compositionupdate"); + is(events[2].type, "text", + description + "events[2] should be text"); + is(events[3].type, "beforeinput", + description + "events[3] should be beforeinput"); + checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description); + is(events[4].type, "input", + description + "events[4] should be input"); + checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description); + TIP1.cancelComposition(); + + // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition(). + events = []; + TIP1.beginInputTransaction(window, simpleCallback); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()"); + } + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()"); + } + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()"); + } + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()"); + } + }, {once: true}); + TIP1.commitComposition(); + is(events.length, 4, + description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()"); + is(events[0].type, "text", + description + "events[0] should be text"); + is(events[1].type, "beforeinput", + description + "events[1] should be beforeinput"); + checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description); + is(events[2].type, "compositionend", + description + "events[2] should be compositionend"); + is(events[3].type, "input", + description + "events[3] should be input"); + checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description); + + // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");. + events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + TIP1.beginInputTransaction(window, simpleCallback); + TIP1.commitCompositionWith("bar"); + is(events.length, 6, + description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")"); + is(events[0].type, "compositionstart", + description + "events[0] should be compositionstart"); + is(events[1].type, "compositionupdate", + description + "events[1] should be compositionupdate"); + is(events[2].type, "text", + description + "events[2] should be text"); + is(events[3].type, "beforeinput", + description + "events[3] should be beforeinput"); + checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description); + is(events[4].type, "compositionend", + description + "events[4] should be compositionend"); + is(events[5].type, "input", + description + "events[5] should be input"); + checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description); + + // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();. + events = []; + TIP1.beginInputTransaction(window, simpleCallback); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during cancelComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()"); + } + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should throw an exception during cancelComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()"); + } + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during cancelComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()"); + } + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during cancelComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()"); + } + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during cancelComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()"); + } + }, {once: true}); + TIP1.cancelComposition(); + is(events.length, 5, + description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()"); + is(events[0].type, "compositionupdate", + description + "events[0] should be compositionupdate"); + is(events[1].type, "text", + description + "events[1] should be text"); + is(events[2].type, "beforeinput", + description + "events[2] should be beforeinput"); + checkInputEvent(events[2], false, true, "insertCompositionText", "", description); + is(events[3].type, "compositionend", + description + "events[3] should be compositionend"); + is(events[4].type, "input", + description + "events[4] should be input"); + checkInputEvent(events[4], false, false, "insertCompositionText", "", description); + + // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();. + events = []; + TIP1.beginInputTransaction(window, simpleCallback); + input.addEventListener("keydown", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keydown\" should throw an exception during keydown()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keydown\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()"); + } + }, {once: true}); + input.addEventListener("keypress", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keypress\" should throw an exception during keydown()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keypress\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()"); + } + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during keydown()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()"); + } + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should throw an exception during keydown()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()"); + } + }, {once: true}); + input.addEventListener("keyup", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransaction(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keyup\" should throw an exception during keyup()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransaction(otherWindow, simpleCallback) called from \"keyup\" should cause NS_ERROR_ALREADY_INITIALIZED during keyup()"); + } + }, {once: true}); + keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); + TIP1.keydown(keyA); + TIP1.keyup(keyA); + is(events.length, 5, + description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()"); + is(events[0].type, "keydown", + description + "events[0] should be keydown"); + is(events[1].type, "keypress", + description + "events[1] should be keypress"); + is(events[2].type, "beforeinput", + description + "events[2] should be beforeinput"); + checkInputEvent(events[2], true, false, "insertText", "a", description); + is(events[3].type, "input", + description + "events[3] should be input"); + checkInputEvent(events[3], false, false, "insertText", "a", description); + is(events[4].type, "keyup", + description + "events[4] should be keyup"); + + // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition(). + events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during startComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during startComposition()"); + } + }, {once: true}); + TIP1.beginInputTransactionForTests(window, simpleCallback); + TIP1.startComposition(); + is(events.length, 1, + description + "compositionstart event should be fired by TIP1.startComposition()"); + TIP1.cancelComposition(); + + // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during flushPendingComposition(). + events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during flushPendingComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()"); + } + }, {once: true}); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during flushPendingComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()"); + } + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during flushPendingComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()"); + } + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during flushPendingComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()"); + } + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during flushPendingComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during flushPendingComposition()"); + } + }, {once: true}); + TIP1.beginInputTransactionForTests(window, simpleCallback); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + is(events.length, 5, + description + "compositionstart, compositionupdate, text, beforeinput and input events should be fired by TIP1.flushPendingComposition()"); + is(events[0].type, "compositionstart", + description + "events[0] should be compositionstart"); + is(events[1].type, "compositionupdate", + description + "events[1] should be compositionupdate"); + is(events[2].type, "text", + description + "events[2] should be text"); + is(events[3].type, "beforeinput", + description + "events[3] should be beforeinput"); + checkInputEvent(events[3], false, true, "insertCompositionText", composingStr, description); + is(events[4].type, "input", + description + "events[4] should be input"); + checkInputEvent(events[4], false, true, "insertCompositionText", composingStr, description); + TIP1.cancelComposition(); + + // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition(). + events = []; + TIP1.beginInputTransactionForTests(window, simpleCallback); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()"); + } + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()"); + } + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()"); + } + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitComposition()"); + } + }, {once: true}); + TIP1.commitComposition(); + is(events.length, 4, + description + "text, beforeinput, compositionend and input events should be fired by TIP1.commitComposition()"); + is(events[0].type, "text", + description + "events[0] should be text"); + is(events[1].type, "beforeinput", + description + "events[1] should be beforeinput"); + checkInputEvent(events[1], false, true, "insertCompositionText", composingStr, description); + is(events[2].type, "compositionend", + description + "events[2] should be compositionend"); + is(events[3].type, "input", + description + "events[3] should be input"); + checkInputEvent(events[3], false, false, "insertCompositionText", composingStr, description); + + // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");. + events = []; + input.addEventListener("compositionstart", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionstart\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during commitCompositionWith(\"bar\")"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during commitCompositionWith(\"bar\")"); + } + }, {once: true}); + TIP1.beginInputTransactionForTests(window, simpleCallback); + TIP1.commitCompositionWith("bar"); + is(events.length, 6, + description + "compositionstart, compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.commitCompositionWith(\"bar\")"); + is(events[0].type, "compositionstart", + description + "events[0] should be compositionstart"); + is(events[1].type, "compositionupdate", + description + "events[1] should be compositionupdate"); + is(events[2].type, "text", + description + "events[2] should be text"); + is(events[3].type, "beforeinput", + description + "events[3] should be beforeinput"); + checkInputEvent(events[3], false, true, "insertCompositionText", "bar", description); + is(events[4].type, "compositionend", + description + "events[4] should be compositionend"); + is(events[5].type, "input", + description + "events[5] should be input"); + checkInputEvent(events[5], false, false, "insertCompositionText", "bar", description); + + // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();. + events = []; + TIP1.beginInputTransactionForTests(window, simpleCallback); + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition(); + input.addEventListener("compositionupdate", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should throw an exception during cancelComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionupdate\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()"); + } + }, {once: true}); + input.addEventListener("text", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should throw an exception during cancelComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"text\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()"); + } + }, {once: true}); + input.addEventListener("compositionend", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should throw an exception during cancelComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"compositionend\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()"); + } + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during cancelComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()"); + } + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during cancelComposition()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during cancelComposition()"); + } + }, {once: true}); + TIP1.cancelComposition(); + is(events.length, 5, + description + "compositionupdate, text, beforeinput, compositionend and input events should be fired by TIP1.cancelComposition()"); + is(events[0].type, "compositionupdate", + description + "events[0] should be compositionupdate"); + is(events[1].type, "text", + description + "events[1] should be text"); + is(events[2].type, "beforeinput", + description + "events[2] should be beforeinput"); + checkInputEvent(events[2], false, true, "insertCompositionText", "", description); + is(events[3].type, "compositionend", + description + "events[3] should be compositionend"); + is(events[4].type, "input", + description + "events[4] should be input"); + checkInputEvent(events[4], false, false, "insertCompositionText", "", description); + + // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();. + events = []; + TIP1.beginInputTransactionForTests(window, simpleCallback); + input.addEventListener("keydown", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keydown\" should throw an exception during keydown()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keydown\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()"); + } + }, {once: true}); + input.addEventListener("keypress", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keypress\" should throw an exception during keydown()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keypress\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()"); + } + }, {once: true}); + input.addEventListener("beforeinput", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should throw an exception during keydown()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"beforeinput\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()"); + } + }, {once: true}); + input.addEventListener("input", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should throw an exception during keydown()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"input\" should cause NS_ERROR_ALREADY_INITIALIZED during keydown()"); + } + }, {once: true}); + input.addEventListener("keyup", function (aEvent) { + events.push(aEvent); + try { + TIP1.beginInputTransactionForTests(otherWindow, simpleCallback); + ok(false, + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keyup\" should throw an exception during keyup()"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ALREADY_INITIALIZED"), + description + "TIP1.beginInputTransactionForTests(otherWindow, simpleCallback) called from \"keyup\" should cause NS_ERROR_ALREADY_INITIALIZED during keyup()"); + } + }, {once: true}); + keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); + TIP1.keydown(keyA); + TIP1.keyup(keyA); + is(events.length, 5, + description + "keydown, keypress, beforeinput, input, keyup events should be fired by TIP1.keydown() and TIP1.keyup()"); + is(events[0].type, "keydown", + description + "events[0] should be keydown"); + is(events[1].type, "keypress", + description + "events[1] should be keypress"); + is(events[2].type, "beforeinput", + description + "events[2] should be beforeinput"); + checkInputEvent(events[2], true, false, "insertText", "a", description); + is(events[3].type, "input", + description + "events[3] should be input"); + checkInputEvent(events[3], false, false, "insertText", "a", description); + is(events[4].type, "keyup", + description + "events[4] should be keyup"); + + // Let's check if startComposition() throws an exception after ownership is stolen. + input.value = ""; + ok(TIP1.beginInputTransactionForTests(window), + description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition"); + ok(TIP2.beginInputTransactionForTests(window), + description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition"); + try { + TIP1.startComposition(); + ok(false, + description + "TIP1.startComposition() should cause throwing an exception because TIP2 took the ownership"); + TIP1.cancelComposition(); + } catch (e) { + ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"), + description + "TIP1.startComposition() should cause throwing an exception including NS_ERROR_NOT_INITIALIZED"); + } finally { + is(input.value, "", + description + "The input element should not have commit string"); + } + + // Let's check if flushPendingComposition() throws an exception after ownership is stolen. + ok(TIP1.beginInputTransactionForTests(window), + description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition"); + ok(TIP2.beginInputTransactionForTests(window), + description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition"); + input.value = ""; + try { + TIP1.setPendingCompositionString(composingStr); + TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE); + TIP1.flushPendingComposition() + ok(false, + description + "TIP1.flushPendingComposition() should cause throwing an exception because TIP2 took the ownership"); + TIP1.cancelComposition(); + } catch (e) { + ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"), + description + "TIP1.flushPendingComposition() should cause throwing an exception including NS_ERROR_NOT_INITIALIZED"); + } finally { + is(input.value, "", + description + "The input element should not have commit string"); + } + + // Let's check if commitCompositionWith("bar") throws an exception after ownership is stolen. + ok(TIP1.beginInputTransactionForTests(window), + description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition"); + ok(TIP2.beginInputTransactionForTests(window), + description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition"); + input.value = ""; + try { + TIP1.commitCompositionWith("bar"); + ok(false, + description + "TIP1.commitCompositionWith(\"bar\") should cause throwing an exception because TIP2 took the ownership"); + } catch (e) { + ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"), + description + "TIP1.commitCompositionWith(\"bar\") should cause throwing an exception including NS_ERROR_NOT_INITIALIZED"); + } finally { + is(input.value, "", + description + "The input element should not have commit string"); + } + + // Let's check if keydown() throws an exception after ownership is stolen. + ok(TIP1.beginInputTransactionForTests(window), + description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition"); + ok(TIP2.beginInputTransactionForTests(window), + description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition"); + input.value = ""; + try { + let keyF = new KeyboardEvent("", { key: "f", code: "KeyF", keyCode: KeyboardEvent.DOM_VK_F }); + TIP1.keydown(keyF); + ok(false, + description + "TIP1.keydown(keyF) should cause throwing an exception because TIP2 took the ownership"); + } catch (e) { + ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"), + description + "TIP1.keydown(keyF) should cause throwing an exception including NS_ERROR_NOT_INITIALIZED"); + } finally { + is(input.value, "", + description + "The input element should not be modified"); + } + + // Let's check if keyup() throws an exception after ownership is stolen. + ok(TIP1.beginInputTransactionForTests(window), + description + "TIP1.beginInputTransactionForTests() should succeed because there is no composition"); + ok(TIP2.beginInputTransactionForTests(window), + description + "TIP2.beginInputTransactionForTests() should succeed because there is no composition"); + input.value = ""; + try { + let keyF = new KeyboardEvent("", { key: "f", code: "KeyF", keyCode: KeyboardEvent.DOM_VK_F }); + TIP1.keyup(keyF); + ok(false, + description + "TIP1.keyup(keyF) should cause throwing an exception because TIP2 took the ownership"); + } catch (e) { + ok(e.message.includes("NS_ERROR_NOT_INITIALIZED"), + description + "TIP1.keyup(keyF) should cause throwing an exception including NS_ERROR_NOT_INITIALIZED"); + } finally { + is(input.value, "", + description + "The input element should not be modified"); + } + + // aCallback of nsITextInputProcessor.beginInputTransaction() must not be omitted. + try { + TIP1.beginInputTransaction(window); + ok(false, + description + "TIP1.beginInputTransaction(window) should be failed since aCallback is omitted"); + } catch (e) { + ok(e.message.includes("Not enough arguments"), + description + "TIP1.beginInputTransaction(window) should cause throwing an exception including \"Not enough arguments\" since aCallback is omitted"); + } + + // aCallback of nsITextInputProcessor.beginInputTransaction() must not be undefined. + try { + TIP1.beginInputTransaction(window, undefined); + ok(false, + description + "TIP1.beginInputTransaction(window, undefined) should be failed since aCallback is undefined"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "TIP1.beginInputTransaction(window, undefined) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE since aCallback is undefined"); + } + + // aCallback of nsITextInputProcessor.beginInputTransaction() must not be null. + try { + TIP1.beginInputTransaction(window, null); + ok(false, + description + "TIP1.beginInputTransaction(window, null) should be failed since aCallback is null"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "TIP1.beginInputTransaction(window, null) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE since aCallback is null"); + } +} + +function runReleaseTests() +{ + var description = "runReleaseTests(): "; + + var TIP = createTIP(); + ok(TIP.beginInputTransactionForTests(window), + description + "TIP.beginInputTransactionForTests() should succeed"); + + input.value = ""; + input.focus(); + + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + is(input.value, "foo", + description + "the input should have composition string"); + + // Release the TIP + TIP = null; + // Needs to run GC forcibly for testing this. + Cu.forceGC(); + + is(input.value, "", + description + "the input should be empty because the composition should be canceled"); + + TIP = createTIP(); + ok(TIP.beginInputTransactionForTests(window), + description + "TIP.beginInputTransactionForTests() should succeed #2"); +} + +function runCompositionTests() +{ + var description = "runCompositionTests(): "; + + var TIP = createTIP(); + ok(TIP.beginInputTransactionForTests(window), + description + "TIP.beginInputTransactionForTests() should succeed"); + + var events; + + function reset() + { + events = []; + } + + function handler(aEvent) + { + events.push({ "type": aEvent.type, "data": aEvent.data }); + } + + window.addEventListener("compositionstart", handler); + window.addEventListener("compositionupdate", handler); + window.addEventListener("compositionend", handler); + + input.value = ""; + input.focus(); + + // nsITextInputProcessor.startComposition() + reset(); + TIP.startComposition(); + is(events.length, 1, + description + "startComposition() should cause only compositionstart"); + is(events[0].type, "compositionstart", + description + "startComposition() should cause only compositionstart"); + is(input.value, "", + description + "startComposition() shouldn't modify the focused editor"); + + // Setting composition string "foo" as a raw clause + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + + reset(); + TIP.flushPendingComposition(); + is(events.length, 1, + description + "flushPendingComposition() after startComposition() should cause compositionupdate"); + is(events[0].type, "compositionupdate", + description + "flushPendingComposition() after startComposition() should cause compositionupdate"); + is(events[0].data, "foo", + description + "compositionupdate caused by flushPendingComposition() should have new composition string in its data"); + is(input.value, "foo", + description + "modifying composition string should cause modifying the focused editor"); + + // Changing the raw clause to a selected clause + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_SELECTED_CLAUSE); + + reset(); + TIP.flushPendingComposition(); + is(events.length, 0, + description + "flushPendingComposition() changing only clause information shouldn't cause compositionupdate"); + is(input.value, "foo", + description + "modifying composition clause shouldn't cause modifying the focused editor"); + + // Separating the selected clause to two clauses + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE); + TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE); + TIP.setCaretInPendingComposition(2); + + reset(); + TIP.flushPendingComposition(); + is(events.length, 0, + description + "flushPendingComposition() separating a clause information shouldn't cause compositionupdate"); + is(input.value, "foo", + description + "separating composition clause shouldn't cause modifying the focused editor"); + + // Modifying the composition string + TIP.setPendingCompositionString("FOo"); + TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE); + TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE); + TIP.setCaretInPendingComposition(2); + + reset(); + TIP.flushPendingComposition(); + is(events.length, 1, + description + "flushPendingComposition() causing modifying composition string should cause compositionupdate"); + is(events[0].type, "compositionupdate", + description + "flushPendingComposition() causing modifying composition string should cause compositionupdate"); + is(events[0].data, "FOo", + description + "compositionupdate caused by flushPendingComposition() should have new composition string in its data"); + is(input.value, "FOo", + description + "modifying composition clause shouldn't cause modifying the focused editor"); + + // Committing the composition string + reset(); + TIP.commitComposition(); + is(events.length, 1, + description + "commitComposition() should cause compositionend but shouldn't cause compositionupdate"); + is(events[0].type, "compositionend", + description + "commitComposition() should cause compositionend"); + is(events[0].data, "FOo", + description + "compositionend caused by commitComposition() should have the committed string in its data"); + is(input.value, "FOo", + description + "commitComposition() shouldn't cause modifying the focused editor"); + + // Starting new composition without a call of startComposition() + TIP.setPendingCompositionString("bar"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + + reset(); + TIP.flushPendingComposition(); + is(events.length, 2, + description + "flushPendingComposition() without a call of startComposition() should cause both compositionstart and compositionupdate"); + is(events[0].type, "compositionstart", + description + "flushPendingComposition() without a call of startComposition() should cause compositionstart"); + is(events[1].type, "compositionupdate", + description + "flushPendingComposition() without a call of startComposition() should cause compositionupdate after compositionstart"); + is(events[1].data, "bar", + description + "compositionupdate caused by flushPendingComposition() without a call of startComposition() should have the composition string in its data"); + is(input.value, "FOobar", + description + "new composition string should cause appending composition string to the focused editor"); + + // Canceling the composition + reset(); + TIP.cancelComposition(); + is(events.length, 2, + description + "cancelComposition() should cause both compositionupdate and compositionend"); + is(events[0].type, "compositionupdate", + description + "cancelComposition() should cause compositionupdate"); + is(events[0].data, "", + description + "compositionupdate caused by cancelComposition() should have empty string in its data"); + is(events[1].type, "compositionend", + description + "cancelComposition() should cause compositionend after compositionupdate"); + is(events[1].data, "", + description + "compositionend caused by cancelComposition() should have empty string in its data"); + is(input.value, "FOo", + description + "canceled composition string should be removed from the focused editor"); + + // Starting composition explicitly and canceling it + reset(); + TIP.startComposition(); + TIP.cancelComposition(); + is(events.length, 2, + description + "canceling composition immediately after startComposition() should cause compositionstart and compositionend"); + is(events[0].type, "compositionstart", + description + "canceling composition immediately after startComposition() should cause compositionstart first"); + is(events[1].type, "compositionend", + description + "canceling composition immediately after startComposition() should cause compositionend after compositionstart"); + is(events[1].data, "", + description + "compositionend caused by canceling composition should have empty string in its data"); + is(input.value, "FOo", + description + "canceling composition shouldn't modify the focused editor"); + + // Create composition for next test. + TIP.setPendingCompositionString("bar"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.flushPendingComposition(); + is(input.value, "FOobar", + description + "The focused editor should have new composition string \"bar\""); + + // Allow to set empty composition string + reset(); + TIP.flushPendingComposition(); + is(events.length, 1, + description + "making composition string empty should cause only compositionupdate"); + is(events[0].type, "compositionupdate", + description + "making composition string empty should cause compositionupdate"); + is(events[0].data, "", + description + "compositionupdate caused by making composition string empty should have empty string in its data"); + + // Allow to insert new composition string without compositionend/compositionstart + TIP.setPendingCompositionString("buzz"); + TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE); + + reset(); + TIP.flushPendingComposition(); + is(events.length, 1, + description + "modifying composition string from empty string should cause only compositionupdate"); + is(events[0].type, "compositionupdate", + description + "modifying composition string from empty string should cause compositionupdate"); + is(events[0].data, "buzz", + description + "compositionupdate caused by modifying composition string from empty string should have new composition string in its data"); + is(input.value, "FOobuzz", + description + "new composition string should be appended to the focused editor"); + + // Committing with different string + reset(); + TIP.commitCompositionWith("bar"); + is(events.length, 2, + description + "committing with different string should cause compositionupdate and compositionend"); + is(events[0].type, "compositionupdate", + description + "committing with different string should cause compositionupdate first"); + is(events[0].data, "bar", + description + "compositionupdate caused by committing with different string should have the committing string in its data"); + is(events[1].type, "compositionend", + description + "committing with different string should cause compositionend after compositionupdate"); + is(events[1].data, "bar", + description + "compositionend caused by committing with different string should have the committing string in its data"); + is(input.value, "FOobar", + description + "new committed string should be appended to the focused editor"); + + // Appending new composition string + TIP.setPendingCompositionString("buzz"); + TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE); + TIP.flushPendingComposition(); + is(input.value, "FOobarbuzz", + description + "new composition string should be appended to the focused editor"); + + // Committing with same string + reset(); + TIP.commitCompositionWith("buzz"); + is(events.length, 1, + description + "committing with same string should cause only compositionend"); + is(events[0].type, "compositionend", + description + "committing with same string should cause compositionend"); + is(events[0].data, "buzz", + description + "compositionend caused by committing with same string should have the committing string in its data"); + is(input.value, "FOobarbuzz", + description + "new committed string should be appended to the focused editor"); + + // Inserting commit string directly + reset(); + TIP.commitCompositionWith("boo!"); + is(events.length, 3, + description + "committing text directly should cause compositionstart, compositionupdate and compositionend"); + is(events[0].type, "compositionstart", + description + "committing text directly should cause compositionstart first"); + is(events[1].type, "compositionupdate", + description + "committing text directly should cause compositionupdate after compositionstart"); + is(events[1].data, "boo!", + description + "compositionupdate caused by committing text directly should have the committing text in its data"); + is(events[2].type, "compositionend", + description + "committing text directly should cause compositionend after compositionupdate"); + is(events[2].data, "boo!", + description + "compositionend caused by committing text directly should have the committing text in its data"); + is(input.value, "FOobarbuzzboo!", + description + "committing text directly should append the committing text to the focused editor"); + + window.removeEventListener("compositionstart", handler); + window.removeEventListener("compositionupdate", handler); + window.removeEventListener("compositionend", handler); +} + +function runCompositionWithKeyEventTests() +{ + var description = "runCompositionWithKeyEventTests(): "; + + var TIP = createTIP(); + ok(TIP.beginInputTransactionForTests(window), + description + "TIP.beginInputTransactionForTests() should succeed"); + + var events; + + function reset() + { + events = []; + } + + function handler(aEvent) + { + events.push(aEvent); + } + + window.addEventListener("compositionstart", handler); + window.addEventListener("compositionupdate", handler); + window.addEventListener("compositionend", handler); + window.addEventListener("keydown", handler); + window.addEventListener("keypress", handler); + window.addEventListener("keyup", handler); + + input.value = ""; + input.focus(); + + var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); + var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }); + var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE }); + var convertKeyEvent = new KeyboardEvent("", { key: "Convert", code: "Convert", keyCode: KeyboardEvent.DOM_VK_CONVERT }); + var backspaceKeyEvent = new KeyboardEvent("", { key: "Backspace", code: "Backspace", keyCode: KeyboardEvent.DOM_VK_BACK_SPACE }); + + Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false); + + // nsITextInputProcessor.startComposition() + reset(); + TIP.startComposition(printableKeyEvent); + is(events.length, 2, + description + "startComposition(printableKeyEvent) should cause keydown and compositionstart"); + is(events[0].type, "keydown", + description + "startComposition(printableKeyEvent) should cause keydown"); + is(events[1].type, "compositionstart", + description + "startComposition(printableKeyEvent) should cause compositionstart"); + is(input.value, "", + description + "startComposition(printableKeyEvent) shouldn't modify the focused editor"); + + // Setting composition string "foo" as a raw clause + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + + reset(); + TIP.flushPendingComposition(printableKeyEvent); + is(events.length, 1, + description + "flushPendingComposition(KeyupInternal) after startComposition() should cause compositionupdate"); + is(events[0].type, "compositionupdate", + description + "flushPendingComposition(KeyupInternal) after startComposition() should cause compositionupdate"); + is(events[0].data, "foo", + description + "compositionupdate caused by flushPendingComposition(KeyupInternal) should have new composition string in its data"); + is(input.value, "foo", + description + "modifying composition string should cause modifying the focused editor"); + + // Changing the raw clause to a selected clause + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_SELECTED_CLAUSE); + + reset(); + TIP.flushPendingComposition(convertKeyEvent); + is(events.length, 0, + description + "flushPendingComposition(convertKeyEvent) changing only clause information shouldn't cause compositionupdate"); + is(input.value, "foo", + description + "modifying composition clause shouldn't cause modifying the focused editor"); + + // Separating the selected clause to two clauses + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE); + TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE); + TIP.setCaretInPendingComposition(2); + + reset(); + TIP.flushPendingComposition(convertKeyEvent); + is(events.length, 0, + description + "flushPendingComposition(convertKeyEvent) separating a clause information shouldn't cause compositionupdate"); + is(input.value, "foo", + description + "separating composition clause shouldn't cause modifying the focused editor"); + + // Modifying the composition string + TIP.setPendingCompositionString("FOo"); + TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE); + TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE); + TIP.setCaretInPendingComposition(2); + + reset(); + TIP.flushPendingComposition(convertKeyEvent); + is(events.length, 1, + description + "flushPendingComposition(convertKeyEvent) causing modifying composition string should cause compositionupdate"); + is(events[0].type, "compositionupdate", + description + "flushPendingComposition(convertKeyEvent) causing modifying composition string should cause compositionupdate"); + is(events[0].data, "FOo", + description + "compositionupdate caused by flushPendingComposition(convertKeyEvent) should have new composition string in its data"); + is(input.value, "FOo", + description + "modifying composition clause shouldn't cause modifying the focused editor"); + + // Committing the composition string + reset(); + TIP.commitComposition(enterKeyEvent); + is(events.length, 2, + description + "commitComposition(enterKeyEvent) should cause compositionend and keyup but shoudn't cause compositionupdate"); + is(events[0].type, "compositionend", + description + "commitComposition(enterKeyEvent) should cause compositionend"); + is(events[0].data, "FOo", + description + "compositionend caused by commitComposition(enterKeyEvent) should have the committed string in its data"); + is(events[1].type, "keyup", + description + "commitComposition(enterKeyEvent) should cause keyup"); + is(input.value, "FOo", + description + "commitComposition(enterKeyEvent) shouldn't cause modifying the focused editor"); + + // Starting new composition without a call of startComposition() + TIP.setPendingCompositionString("bar"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + + reset(); + TIP.flushPendingComposition(printableKeyEvent); + is(events.length, 3, + description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause both compositionstart and compositionupdate"); + is(events[0].type, "keydown", + description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause keydown"); + is(events[1].type, "compositionstart", + description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause compositionstart"); + is(events[2].type, "compositionupdate", + description + "flushPendingComposition(printableKeyEvent) without a call of startComposition() should cause compositionupdate after compositionstart"); + is(events[2].data, "bar", + description + "compositionupdate caused by flushPendingComposition(printableKeyEvent) without a call of startComposition() should have the composition string in its data"); + is(input.value, "FOobar", + description + "new composition string should cause appending composition string to the focused editor"); + + // Canceling the composition + reset(); + TIP.cancelComposition(escKeyEvent); + is(events.length, 3, + description + "cancelComposition(escKeyEvent) should cause both compositionupdate and compositionend"); + is(events[0].type, "compositionupdate", + description + "cancelComposition(escKeyEvent) should cause compositionupdate"); + is(events[0].data, "", + description + "compositionupdate caused by cancelComposition(escKeyEvent) should have empty string in its data"); + is(events[1].type, "compositionend", + description + "cancelComposition(escKeyEvent) should cause compositionend after compositionupdate"); + is(events[1].data, "", + description + "compositionend caused by cancelComposition(escKeyEvent) should have empty string in its data"); + is(events[2].type, "keyup", + description + "cancelComposition(escKeyEvent) should cause keyup after compositionend"); + is(input.value, "FOo", + description + "canceled composition string should be removed from the focused editor"); + + // Starting composition explicitly and canceling it + reset(); + TIP.startComposition(printableKeyEvent); + TIP.cancelComposition(escKeyEvent); + is(events.length, 4, + description + "canceling composition immediately after startComposition() should cause keydown, compositionstart, compositionend and keyup"); + is(events[0].type, "keydown", + description + "canceling composition immediately after startComposition() should cause keydown first"); + is(events[1].type, "compositionstart", + description + "canceling composition immediately after startComposition() should cause compositionstart after keydown"); + is(events[2].type, "compositionend", + description + "canceling composition immediately after startComposition() should cause compositionend after compositionstart"); + is(events[2].data, "", + description + "compositionend caused by canceling composition should have empty string in its data"); + is(events[3].type, "keyup", + description + "canceling composition immediately after startComposition() should cause keyup after compositionend"); + is(input.value, "FOo", + description + "canceling composition shouldn't modify the focused editor"); + + // Create composition for next test. + TIP.setPendingCompositionString("bar"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.flushPendingComposition(); + is(input.value, "FOobar", + description + "The focused editor should have new composition string \"bar\""); + + // Allow to set empty composition string + reset(); + TIP.flushPendingComposition(backspaceKeyEvent); + is(events.length, 1, + description + "making composition string empty should cause only compositionupdate"); + is(events[0].type, "compositionupdate", + description + "making composition string empty should cause compositionupdate"); + is(events[0].data, "", + description + "compositionupdate caused by making composition string empty should have empty string in its data"); + + // Allow to insert new composition string without compositionend/compositionstart + TIP.setPendingCompositionString("buzz"); + TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE); + + reset(); + TIP.flushPendingComposition(printableKeyEvent); + is(events.length, 1, + description + "modifying composition string from empty string should cause only compositionupdate"); + is(events[0].type, "compositionupdate", + description + "modifying composition string from empty string should cause compositionupdate"); + is(events[0].data, "buzz", + description + "compositionupdate caused by modifying composition string from empty string should have new composition string in its data"); + is(input.value, "FOobuzz", + description + "new composition string should be appended to the focused editor"); + + // Committing with different string + reset(); + TIP.commitCompositionWith("bar", printableKeyEvent); + is(events.length, 3, + description + "committing with different string should cause compositionupdate and compositionend"); + is(events[0].type, "compositionupdate", + description + "committing with different string should cause compositionupdate first"); + is(events[0].data, "bar", + description + "compositionupdate caused by committing with different string should have the committing string in its data"); + is(events[1].type, "compositionend", + description + "committing with different string should cause compositionend after compositionupdate"); + is(events[1].data, "bar", + description + "compositionend caused by committing with different string should have the committing string in its data"); + is(events[2].type, "keyup", + description + "committing with different string should cause keyup after compositionend"); + is(input.value, "FOobar", + description + "new committed string should be appended to the focused editor"); + + // Appending new composition string + TIP.setPendingCompositionString("buzz"); + TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE); + TIP.flushPendingComposition(); + is(input.value, "FOobarbuzz", + description + "new composition string should be appended to the focused editor"); + + // Committing with same string + reset(); + TIP.commitCompositionWith("buzz", enterKeyEvent); + is(events.length, 2, + description + "committing with same string should cause only compositionend"); + is(events[0].type, "compositionend", + description + "committing with same string should cause compositionend"); + is(events[0].data, "buzz", + description + "compositionend caused by committing with same string should have the committing string in its data"); + is(events[1].type, "keyup", + description + "committing with same string should cause keyup after compositionend"); + is(input.value, "FOobarbuzz", + description + "new committed string should be appended to the focused editor"); + + // Inserting commit string directly + reset(); + TIP.commitCompositionWith("boo!", printableKeyEvent); + is(events.length, 5, + description + "committing text directly should cause compositionstart, compositionupdate and compositionend"); + is(events[0].type, "keydown", + description + "committing text directly should cause keydown first"); + is(events[1].type, "compositionstart", + description + "committing text directly should cause compositionstart after keydown"); + is(events[2].type, "compositionupdate", + description + "committing text directly should cause compositionupdate after compositionstart"); + is(events[2].data, "boo!", + description + "compositionupdate caused by committing text directly should have the committing text in its data"); + is(events[3].type, "compositionend", + description + "committing text directly should cause compositionend after compositionupdate"); + is(events[3].data, "boo!", + description + "compositionend caused by committing text directly should have the committing text in its data"); + is(events[4].type, "keyup", + description + "committing text directly should cause keyup after compositionend"); + is(input.value, "FOobarbuzzboo!", + description + "committing text directly should append the committing text to the focused editor"); + + Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true); + + // Even if "dom.keyboardevent.dispatch_during_composition" is true, keypress event shouldn't be fired during composition + reset(); + TIP.startComposition(printableKeyEvent); + is(events.length, 3, + description + "TIP.startComposition(printableKeyEvent) should cause keydown, compositionstart and keyup (keypress event shouldn't be fired during composition)"); + is(events[0].type, "keydown", + description + "TIP.startComposition(printableKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)"); + is(events[1].type, "compositionstart", + description + "TIP.startComposition(printableKeyEvent) should cause compositionstart (keypress event shouldn't be fired during composition)"); + is(events[2].type, "keyup", + description + "TIP.startComposition(printableKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)"); + + // TIP.flushPendingComposition(printableKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + + reset(); + TIP.flushPendingComposition(printableKeyEvent); + is(events.length, 3, + description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown, compositionupdate and keyup (keypress event shouldn't be fired during composition)"); + is(events[0].type, "keydown", + description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)"); + is(events[1].type, "compositionupdate", + description + "TIP.flushPendingComposition(printableKeyEvent) should cause compositionupdate (keypress event shouldn't be fired during composition)"); + is(events[2].type, "keyup", + description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)"); + + // TIP.commitComposition(enterKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true + reset(); + TIP.commitComposition(enterKeyEvent); + is(events.length, 3, + description + "TIP.commitComposition(enterKeyEvent) should cause keydown, compositionend and keyup (keypress event shouldn't be fired during composition)"); + is(events[0].type, "keydown", + description + "TIP.commitComposition(enterKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)"); + is(events[1].type, "compositionend", + description + "TIP.commitComposition(enterKeyEvent) should cause compositionend (keypress event shouldn't be fired during composition)"); + is(events[2].type, "keyup", + description + "TIP.commitComposition(enterKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)"); + + // TIP.cancelComposition(escKeyEvent) should cause keydown and keyup events if "dom.keyboardevent.dispatch_during_composition" is true + TIP.startComposition(); + reset(); + TIP.cancelComposition(escKeyEvent); + is(events.length, 3, + description + "TIP.cancelComposition(escKeyEvent) should cause keydown, compositionend and keyup (keypress event shouldn't be fired during composition)"); + is(events[0].type, "keydown", + description + "TIP.cancelComposition(escKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)"); + is(events[1].type, "compositionend", + description + "TIP.cancelComposition(escKeyEvent) should cause compositionend (keypress event shouldn't be fired during composition)"); + is(events[2].type, "keyup", + description + "TIP.cancelComposition(escKeyEvent) should cause keyup (keypress event shouldn't be fired during composition)"); + + var printableKeydownEvent = new KeyboardEvent("keydown", { key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B }); + var enterKeydownEvent = new KeyboardEvent("keydown", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }); + var escKeydownEvent = new KeyboardEvent("keydown", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE }); + + // TIP.startComposition(printableKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true + reset(); + TIP.startComposition(printableKeydownEvent); + is(events.length, 2, + description + "TIP.startComposition(printableKeydownEvent) should cause keydown and compositionstart (keyup event shouldn't be fired)"); + is(events[0].type, "keydown", + description + "TIP.startComposition(printableKeydownEvent) should cause keydown (keyup event shouldn't be fired)"); + is(events[1].type, "compositionstart", + description + "TIP.startComposition(printableKeydownEvent) should cause compositionstart (keyup event shouldn't be fired)"); + + // TIP.flushPendingComposition(printableKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + + reset(); + TIP.flushPendingComposition(printableKeydownEvent); + is(events.length, 2, + description + "TIP.flushPendingComposition(printableKeydownEvent) should cause keydown and compositionupdate (keyup event shouldn't be fired)"); + is(events[0].type, "keydown", + description + "TIP.flushPendingComposition(printableKeydownEvent) should cause keydown (keyup event shouldn't be fired)"); + is(events[1].type, "compositionupdate", + description + "TIP.flushPendingComposition(printableKeydownEvent) should cause compositionupdate (keyup event shouldn't be fired)"); + + // TIP.commitComposition(enterKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true + reset(); + TIP.commitComposition(enterKeydownEvent); + is(events.length, 2, + description + "TIP.commitComposition(enterKeydownEvent) should cause keydown and compositionend (keyup event shouldn't be fired)"); + is(events[0].type, "keydown", + description + "TIP.commitComposition(enterKeydownEvent) should cause keydown (keyup event shouldn't be fired)"); + is(events[1].type, "compositionend", + description + "TIP.commitComposition(enterKeydownEvent) should cause compositionend (keyup event shouldn't be fired)"); + + // TIP.cancelComposition(escKeydownEvent) shouldn't cause keyup event even if "dom.keyboardevent.dispatch_during_composition" is true + TIP.startComposition(); + reset(); + TIP.cancelComposition(escKeydownEvent); + is(events.length, 2, + description + "TIP.cancelComposition(escKeydownEvent) should cause keydown and compositionend (keyup event shouldn't be fired)"); + is(events[0].type, "keydown", + description + "TIP.cancelComposition(escKeydownEvent) should cause keydown (keyup event shouldn't be fired)"); + is(events[1].type, "compositionend", + description + "TIP.cancelComposition(escKeydownEvent) should cause compositionend (keyup event shouldn't be fired)"); + + Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition"); + + window.removeEventListener("compositionstart", handler); + window.removeEventListener("compositionupdate", handler); + window.removeEventListener("compositionend", handler); + window.removeEventListener("keydown", handler); + window.removeEventListener("keypress", handler); + window.removeEventListener("keyup", handler); +} + +function runConsumingKeydownBeforeCompositionTests() +{ + var description = "runConsumingKeydownBeforeCompositionTests(): "; + + var TIP = createTIP(); + ok(TIP.beginInputTransactionForTests(window), + description + "TIP.beginInputTransactionForTests() should succeed"); + + var events; + + function reset() + { + events = []; + } + + function handler(aEvent) + { + events.push(aEvent); + if (aEvent.type == "keydown") { + aEvent.preventDefault(); + } + } + + window.addEventListener("compositionstart", handler); + window.addEventListener("compositionupdate", handler); + window.addEventListener("compositionend", handler); + window.addEventListener("keydown", handler); + window.addEventListener("keypress", handler); + window.addEventListener("keyup", handler); + + input.value = ""; + input.focus(); + + var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); + var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }); + var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE }); + + Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false); + + // If keydown before compositionstart is consumed, composition shouldn't be started. + reset(); + ok(!TIP.startComposition(printableKeyEvent), + description + "TIP.startComposition(printableKeyEvent) should return false because it's keydown is consumed"); + is(events.length, 2, + description + "TIP.startComposition(printableKeyEvent) should cause only keydown and keyup events"); + is(events[0].type, "keydown", + description + "TIP.startComposition(printableKeyEvent) should cause keydown event first"); + is(events[1].type, "keyup", + description + "TIP.startComposition(printableKeyEvent) should cause keyup event after keydown"); + ok(!TIP.hasComposition, + description + "TIP.startComposition(printableKeyEvent) shouldn't cause composition"); + is(input.value, "", + description + "TIP.startComposition(printableKeyEvent) shouldn't cause inserting text"); + + // If keydown before compositionstart caused by flushPendingComposition(printableKeyEvent) is consumed, composition shouldn't be started. + reset(); + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + ok(!TIP.flushPendingComposition(printableKeyEvent), + description + "TIP.flushPendingComposition(printableKeyEvent) should return false because it's keydown is consumed"); + is(events.length, 2, + description + "TIP.flushPendingComposition(printableKeyEvent) should cause only keydown and keyup events"); + is(events[0].type, "keydown", + description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown event first"); + is(events[1].type, "keyup", + description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup event after keydown"); + ok(!TIP.hasComposition, + description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause composition"); + is(input.value, "", + description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause inserting text"); + + // If keydown before compositionstart is consumed, composition shouldn't be started. + reset(); + ok(!TIP.commitCompositionWith("foo", printableKeyEvent), + description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should return false because it's keydown is consumed"); + is(events.length, 2, + description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should cause only keydown and keyup events"); + is(events[0].type, "keydown", + description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should cause keydown event first"); + is(events[1].type, "keyup", + description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should cause keyup event after keydown"); + ok(!TIP.hasComposition, + description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) shouldn't cause composition"); + is(input.value, "", + description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) shouldn't cause inserting text"); + + Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true); + + // If composition is already started, TIP.flushPendingComposition(printableKeyEvent) shouldn't be canceled. + TIP.startComposition(); + ok(TIP.hasComposition, + description + "Before TIP.flushPendingComposition(printableKeyEvent), composition should've been created"); + reset(); + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + ok(TIP.flushPendingComposition(printableKeyEvent), + description + "TIP.flushPendingComposition(printableKeyEvent) should return true even if preceding keydown is consumed because there was a composition already"); + is(events.length, 3, + description + "TIP.flushPendingComposition(printableKeyEvent) should cause only keydown and keyup events"); + is(events[0].type, "keydown", + description + "TIP.flushPendingComposition(printableKeyEvent) should cause keydown event first"); + is(events[1].type, "compositionupdate", + description + "TIP.flushPendingComposition(printableKeyEvent) should cause compositionupdate event after keydown"); + is(events[2].type, "keyup", + description + "TIP.flushPendingComposition(printableKeyEvent) should cause keyup event after compositionupdate"); + ok(TIP.hasComposition, + description + "TIP.flushPendingComposition(printableKeyEvent) shouldn't cause canceling composition"); + is(input.value, "foo", + description + "TIP.flushPendingComposition(printableKeyEvent) should cause inserting text even if preceding keydown is consumed because there was a composition already"); + + // If composition is already started, TIP.commitComposition(enterKeyEvent) shouldn't be canceled. + reset(); + TIP.commitComposition(enterKeyEvent); + is(events.length, 3, + description + "TIP.commitComposition(enterKeyEvent) should cause keydown, compositionend and keyup events"); + is(events[0].type, "keydown", + description + "TIP.commitComposition(enterKeyEvent) should cause keydown event first"); + is(events[1].type, "compositionend", + description + "TIP.commitComposition(enterKeyEvent) should cause compositionend event after keydown"); + is(events[2].type, "keyup", + description + "TIP.commitComposition(enterKeyEvent) should cause keyup event after compositionend"); + ok(!TIP.hasComposition, + description + "TIP.commitComposition(enterKeyEvent) should cause committing composition even if preceding keydown is consumed because there was a composition already"); + is(input.value, "foo", + description + "TIP.commitComposition(enterKeyEvent) should commit composition even if preceding keydown is consumed because there was a composition already"); + + // cancelComposition() should work even if preceding keydown event is consumed. + input.value = ""; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + ok(TIP.hasComposition, + description + "Before TIP.cancelComposition(escKeyEvent), composition should've been created"); + is(input.value, "foo", + description + "Before TIP.cancelComposition(escKeyEvent) should have composition string"); + reset(); + TIP.cancelComposition(escKeyEvent); + is(events.length, 4, + description + "TIP.cancelComposition(escKeyEvent) should cause keydown, compositionupdate, compositionend and keyup events even if preceding keydown is consumed because there was a composition already"); + is(events[0].type, "keydown", + description + "TIP.cancelComposition(escKeyEvent) should cause keydown event first"); + is(events[1].type, "compositionupdate", + description + "TIP.cancelComposition(escKeyEvent) should cause compositionupdate event after keydown"); + is(events[2].type, "compositionend", + description + "TIP.cancelComposition(escKeyEvent) should cause compositionend event after compositionupdate"); + is(events[3].type, "keyup", + description + "TIP.cancelComposition(escKeyEvent) should cause keyup event after compositionend"); + ok(!TIP.hasComposition, + description + "TIP.cancelComposition(escKeyEvent) should cause canceling composition even if preceding keydown is consumed because there was a composition already"); + is(input.value, "", + description + "TIP.cancelComposition(escKeyEvent) should cancel composition even if preceding keydown is consumed because there was a composition already"); + + Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition"); + + window.removeEventListener("compositionstart", handler); + window.removeEventListener("compositionupdate", handler); + window.removeEventListener("compositionend", handler); + window.removeEventListener("keydown", handler); + window.removeEventListener("keypress", handler); + window.removeEventListener("keyup", handler); +} + +async function runKeyTests() +{ + var description = "runKeyTests(): "; + const kModifiers = + [ "Alt", "AltGraph", "CapsLock", "Control", "Fn", "FnLock", "Meta", "NumLock", + "ScrollLock", "Shift", "Symbol", "SymbolLock", "OS" ]; + + var TIP = createTIP(); + ok(TIP.beginInputTransactionForTests(window), + description + "TIP.beginInputTransactionForTests() should succeed"); + + var events; + var doPreventDefaults; + + function reset() + { + events = []; + doPreventDefaults = []; + } + + function handler(aEvent) + { + events.push(aEvent); + if (doPreventDefaults.includes(aEvent.type)) { + aEvent.preventDefault(); + } + } + + function checkKeyAttrs(aMethodDescription, aEvent, aExpectedData) + { + var desc = description + aMethodDescription + ", type=\"" + aEvent.type + "\", key=\"" + aEvent.key + "\", code=\"" + aEvent.code + "\": "; + var defaultValues = { + key: "Unidentified", code: "", keyCode: 0, charCode: 0, + location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD, repeat: false, isComposing: false, + shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, + defaultPrevented: false + }; + function expectedValue(aAttr) + { + return aExpectedData[aAttr] !== undefined ? aExpectedData[aAttr] : defaultValues[aAttr]; + } + is(aEvent.type, aExpectedData.type, + desc + " should cause keydown event"); + if (aEvent.type != aExpectedData.type) { + return; + } + is(aEvent.defaultPrevented, expectedValue("defaultPrevented"), + desc + ".defaultPrevented is wrong"); + is(aEvent.key, expectedValue("key"), + desc + ".key is wrong"); + is(aEvent.code, expectedValue("code"), + desc + ".code is wrong"); + is(aEvent.location, expectedValue("location"), + desc + ".location is wrong"); + is(aEvent.repeat, expectedValue("repeat"), + desc + ".repeat is wrong"); + is(aEvent.isComposing, expectedValue("isComposing"), + desc + ".isComposing is wrong"); + is(aEvent.keyCode, expectedValue("keyCode"), + desc + ".keyCode is wrong"); + is(aEvent.charCode, expectedValue("charCode"), + desc + ".charCode is wrong"); + is(aEvent.shiftKey, expectedValue("shiftKey"), + desc + ".shiftKey is wrong"); + is(aEvent.ctrlKey, expectedValue("ctrlKey"), + desc + ".ctrlKey is wrong"); + is(aEvent.altKey, expectedValue("altKey"), + desc + ".altKey is wrong"); + is(aEvent.metaKey, expectedValue("metaKey"), + desc + ".metaKey is wrong"); + for (var i = 0; i < kModifiers.length; i++) { + is(aEvent.getModifierState(kModifiers[i]), aExpectedData[kModifiers[i]] !== undefined ? aExpectedData[kModifiers[i]] : false, + desc + ".getModifierState(\"" + kModifiers[i] + "\") is wrong"); + } + } + + window.addEventListener("keydown", handler); + window.addEventListener("keypress", handler); + window.addEventListener("keyup", handler); + + input.value = ""; + input.focus(); + + + // Printable key test: + // Emulates pressing 'a' key. + var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); + + reset(); + var doDefaultKeydown = TIP.keydown(keyA); + + is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED, + description + "TIP.keydown(keyA) should return 0x02 because the keypress event should be consumed by the input element"); + is(events.length, 2, + description + "TIP.keydown(keyA) should cause keydown and keypress event"); + checkKeyAttrs("TIP.keydown(keyA)", events[0], + { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0 }); + checkKeyAttrs("TIP.keydown(keyA)", events[1], + { type: "keypress", key: "a", code: "KeyA", keyCode: 0, charCode: "a".charCodeAt(0), defaultPrevented: true }); + is(input.value, "a", + description + "input.value should be \"a\" which is inputted by TIP.keydown(keyA)"); + + // Emulates releasing 'a' key. + reset(); + var doDefaultKeyup = TIP.keyup(keyA); + ok(doDefaultKeyup, + description + "TIP.keyup(keyA) should return true"); + is(events.length, 1, + description + "TIP.keyup(keyA) should cause keyup event"); + checkKeyAttrs("TIP.keyup(keyA)", events[0], + { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0 }); + is(input.value, "a", + description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)"); + + + // Non-printable key test: + // Emulates pressing Enter key. + var keyEnter = new KeyboardEvent("", { key: "Enter", code: "Enter" }); + + reset(); + doDefaultKeydown = TIP.keydown(keyEnter); + + is(doDefaultKeydown, 0, + description + "TIP.keydown(keyEnter) should return 0"); + is(events.length, 2, + description + "TIP.keydown(keyEnter) should cause keydown and keypress event"); + checkKeyAttrs("TIP.keydown(keyEnter)", events[0], + { type: "keydown", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }); + checkKeyAttrs("TIP.keydown(keyEnter)", events[1], + { type: "keypress", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }); + is(input.value, "a", + description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)"); + + // Emulates releasing Enter key. + reset(); + doDefaultKeyup = TIP.keyup(keyEnter); + ok(doDefaultKeyup, + description + "TIP.keyup(keyEnter) should return true"); + is(events.length, 1, + description + "TIP.keyup(keyEnter) should cause keyup event"); + checkKeyAttrs("TIP.keyup(keyEnter)", events[0], + { type: "keyup", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }); + is(input.value, "a", + description + "input.value should stay \"a\" which was inputted by TIP.keydown(keyA)"); + + + // KEY_DEFAULT_PREVENTED should cause defaultPrevented = true and not cause keypress event + var keyB = new KeyboardEvent("", { key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B }); + + reset(); + doDefaultKeydown = TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED); + doDefaultKeyup = TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED); + + is(doDefaultKeydown, TIP.KEYDOWN_IS_CONSUMED, + description + "TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) should return 0x01 because it's marked as consumed at dispatching the event"); + ok(!doDefaultKeyup, + description + "TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED) should return false because it's marked as consumed at dispatching the event"); + is(events.length, 2, + description + "TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED) should cause keydown and keyup event"); + checkKeyAttrs("TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED)", events[0], + { type: "keydown", key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B, defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyB, TIP.KEY_DEFAULT_PREVENTED) and TIP.keyup(keyB, TIP.KEY_DEFAULT_PREVENTED)", events[1], + { type: "keyup", key: "b", code: "KeyB", keyCode: KeyboardEvent.DOM_VK_B, defaultPrevented: true }); + is(input.value, "a", + description + "input.value shouldn't be modified by default prevented key events"); + + // Assume that KeyX causes inputting text "abc" + input.value = ""; + var keyABC = new KeyboardEvent("", { key: "abc", code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A }); + + reset(); + doDefaultKeydown = TIP.keydown(keyABC); + doDefaultKeyup = TIP.keyup(keyABC); + + is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED, + description + "TIP.keydown(keyABC) should return false because the keypress events should be consumed by the input element"); + ok(doDefaultKeyup, + description + "TIP.keyup(keyABC) should return true"); + is(events.length, 5, + description + "TIP.keydown(keyABC) and TIP.keyup(keyABC) should cause keydown, keypress, keypress, keypress and keyup event"); + checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[0], + { type: "keydown", key: "abc", code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false }); + checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[1], + { type: "keypress", key: "abc".charAt(0), code: "KeyX", keyCode: 0, charCode: "abc".charCodeAt(0), defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[2], + { type: "keypress", key: "abc".charAt(1), code: "KeyX", keyCode: 0, charCode: "abc".charCodeAt(1), defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[3], + { type: "keypress", key: "abc".charAt(2), code: "KeyX", keyCode: 0, charCode: "abc".charCodeAt(2), defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyABC) and TIP.keyup(keyABC)", events[4], + { type: "keyup", key: "abc", code: "KeyX", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false }); + is(input.value, "abc", + description + "input.value should be \"abc\""); + + // Emulates pressing and releasing a key which introduces a surrogate pair. + async function test_press_and_release_surrogate_pair_key( + aTestPerSurrogateKeyPress, + aTestIllFormedUTF16KeyValue = false + ) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.event.keypress.dispatch_once_per_surrogate_pair", !aTestPerSurrogateKeyPress], + ["dom.event.keypress.key.allow_lone_surrogate", aTestIllFormedUTF16KeyValue], + ], + }); + + const settingDescription = + `aTestPerSurrogateKeyPress=${aTestPerSurrogateKeyPress}, aTestIllFormedUTF16KeyValue=${aTestIllFormedUTF16KeyValue}`; + const allowIllFormedUTF16 = aTestPerSurrogateKeyPress && aTestIllFormedUTF16KeyValue; + const keySurrogatePair = new KeyboardEvent("", { key: "\uD842\uDFB7", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); + + input.value = ""; + reset(); + doDefaultKeydown = TIP.keydown(keySurrogatePair); + doDefaultKeyup = TIP.keyup(keySurrogatePair); + + is( + doDefaultKeydown, + TIP.KEYPRESS_IS_CONSUMED, + `${ + description + }TIP.keydown(keySurrogatePair), ${ + settingDescription + }, should return 0x02 because the keypress event should be consumed by the input element` + ); + is( + doDefaultKeyup, + true, + `${description}TIP.keyup(keySurrogatePair) should return true` + ); + is( + events.length, + aTestPerSurrogateKeyPress ? 4 : 3, + `${description}TIP.keydown(keySurrogatePair), ${ + settingDescription + }, should cause keydown, keypress${ + aTestPerSurrogateKeyPress ? ", keypress" : "" + } and keyup event` + ); + checkKeyAttrs( + `${description}TIP.keydown(keySurrogatePair), ${settingDescription}`, + events[0], + { + type: "keydown", + key: "\uD842\uDFB7", + code: "KeyA", + keyCode: KeyboardEvent.DOM_VK_A, + charCode: 0, + } + ); + if (aTestPerSurrogateKeyPress) { + checkKeyAttrs( + `${description}TIP.keydown(keySurrogatePair), ${settingDescription}`, + events[1], + { + type: "keypress", + key: allowIllFormedUTF16 + ? "\uD842" + : "\uD842\uDFB7", // First keypress should have the surrogate pair + code: "KeyA", + keyCode: 0, + charCode: "\uD842\uDFB7".charCodeAt(0), + defaultPrevented: true, + } + ); + checkKeyAttrs( + `${description}TIP.keydown(keySurrogatePair), ${settingDescription}`, + events[2], + { + type: "keypress", + key: allowIllFormedUTF16 + ? "\uDFB7" + : "", // But the following keypress should have empty string, instead + code: "KeyA", + keyCode: 0, + charCode: "\uD842\uDFB7".charCodeAt(1), + defaultPrevented: true, + } + ); + } else { + checkKeyAttrs( + `${description}TIP.keydown(keySurrogatePair), ${settingDescription}`, + events[1], + { + type: "keypress", + key: "\uD842\uDFB7", + code: "KeyA", + keyCode: 0, + charCode: 0x20BB7, + defaultPrevented: true, + } + ); + } + checkKeyAttrs( + `${description}TIP.keyup(keySurrogatePair), ${settingDescription}`, + events[aTestPerSurrogateKeyPress ? 3 : 2], + { + type: "keyup", + key: "\uD842\uDFB7", + code: "KeyA", + keyCode: KeyboardEvent.DOM_VK_A, + charCode: 0, + } + ); + is( + input.value, + "\uD842\uDFB7", + `${description}${settingDescription}, input.value should be the surrogate pair` + ); + }; + + await test_press_and_release_surrogate_pair_key(true, true); + await test_press_and_release_surrogate_pair_key(true, false); + await test_press_and_release_surrogate_pair_key(false); + + // If KEY_FORCE_PRINTABLE_KEY is specified, registered key names can be a printable key which inputs the specified value. + input.value = ""; + var keyEnterPrintable = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }); + + reset(); + doDefaultKeydown = TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY); + doDefaultKeyup = TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY); + + is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED, + description + "TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) should return 0x02 because the keypress events should be consumed by the input element"); + ok(doDefaultKeyup, + description + "TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) should return true"); + is(events.length, 7, + description + "TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) should cause keydown, keypress, keypress, keypress, keypress, keypress and keyup event"); + checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[0], + { type: "keydown", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN, charCode: 0, defaultPrevented: false }); + checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[1], + { type: "keypress", key: "Enter".charAt(0), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(0), defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[2], + { type: "keypress", key: "Enter".charAt(1), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(1), defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[3], + { type: "keypress", key: "Enter".charAt(2), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(2), defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[4], + { type: "keypress", key: "Enter".charAt(3), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(3), defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[5], + { type: "keypress", key: "Enter".charAt(4), code: "Enter", keyCode: 0, charCode: "Enter".charCodeAt(4), defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY) and TIP.keyup(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)", events[6], + { type: "keyup", key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN, charCode: 0, defaultPrevented: false }); + is(input.value, "Enter", + description + "input.value should be \"Enter\""); + + // modifiers should be ignored. + var keyWithModifiers = new KeyboardEvent("", { key: "Escape", code: "Escape", shiftKey: true, ctrlKey: true, altKey: true, metaKey: true }); + + reset(); + doDefaultKeydown = TIP.keydown(keyWithModifiers); + doDefaultKeyup = TIP.keyup(keyWithModifiers); + + is(doDefaultKeydown, 0, + description + "TIP.keydown(keyWithModifiers) should return 0"); + ok(doDefaultKeyup, + description + "TIP.keyup(keyWithModifiers) should return true"); + is(events.length, 3, + description + "TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers) should cause keydown, keypress and keyup event"); + checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[0], + { type: "keydown", key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE }); + checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[1], + { type: "keypress", key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE }); + checkKeyAttrs("TIP.keydown(keyWithModifiers) and TIP.keyup(keyWithModifiers)", events[2], + { type: "keyup", key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE }); + is(input.value, "Enter", + description + "input.value should stay \"Enter\" which was inputted by TIP.keydown(keyEnterPrintable, TIP.KEY_FORCE_PRINTABLE_KEY)"); + + // Call preventDefault() at keydown + input.value = ""; + reset(); + doPreventDefaults = [ "keydown" ]; + doDefaultKeydown = TIP.keydown(keyA); + doDefaultKeyup = TIP.keyup(keyA); + + is(doDefaultKeydown, TIP.KEYDOWN_IS_CONSUMED, + description + "TIP.keydown(keyA) should return 0x01 because keydown event's preventDefault should be called"); + ok(doDefaultKeyup, + description + "TIP.keyup(keyA) should return true"); + is(events.length, 2, + description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown should cause keydown and keyup event"); + checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown", events[0], + { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keydown", events[1], + { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, defaultPrevented: false }); + is(input.value, "", + description + "input.value shouldn't be modified by TIP.keyup(keyA) if the keydown event is consumed"); + + // Call preventDefault() at keypress + reset(); + doPreventDefaults = [ "keypress" ]; + doDefaultKeydown = TIP.keydown(keyA); + doDefaultKeyup = TIP.keyup(keyA); + + is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED, + description + "TIP.keydown(keyA) should return 0x02 because keypress event's preventDefault should be called"); + ok(doDefaultKeyup, + description + "TIP.keyup(keyA) should return true"); + is(events.length, 3, + description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress should cause keydown, keypress and keyup event"); + checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[0], + { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false }); + checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[1], + { type: "keypress", key: "a", code: "KeyA", keyCode: 0, charCode: "a".charCodeAt(0), defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keypress", events[2], + { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false }); + is(input.value, "", + description + "input.value shouldn't be modified by TIP.keyup(keyA) if the keypress event is consumed"); + + // Call preventDefault() at keyup + input.value = ""; + reset(); + doPreventDefaults = [ "keyup" ]; + doDefaultKeydown = TIP.keydown(keyA); + doDefaultKeyup = TIP.keyup(keyA); + + is(doDefaultKeydown, TIP.KEYPRESS_IS_CONSUMED, + description + "TIP.keydown(keyA) should return 0x02 because the key event should be consumed by the input element"); + ok(!doDefaultKeyup, + description + "TIP.keyup(keyA) should return false because keyup event's preventDefault should be called"); + is(events.length, 3, + description + "TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup should cause keydown, keypress and keyup event"); + checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[0], + { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: false }); + checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[1], + { type: "keypress", key: "a", code: "KeyA", keyCode: 0, charCode: "a".charCodeAt(0), defaultPrevented: true }); + checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[2], + { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, defaultPrevented: true }); + is(input.value, "a", + description + "input.value should be \"a\" by TIP.keyup(keyA) even if the keyup event is consumed"); + + // key events during composition + try { + Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false); + + ok(TIP.startComposition(), "TIP.startComposition() should start composition"); + + input.value = ""; + reset(); + TIP.keydown(keyA); + is(events.length, 0, + description + "TIP.keydown(keyA) shouldn't cause key events during composition if it's disabled by the pref"); + reset(); + TIP.keyup(keyA); + is(events.length, 0, + description + "TIP.keyup(keyA) shouldn't cause key events during composition if it's disabled by the pref"); + + Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true); + reset(); + TIP.keydown(keyA); + is(events.length, 1, + description + "TIP.keydown(keyA) should cause keydown event even composition if it's enabled by the pref"); + checkKeyAttrs("TIP.keydown(keyA) during composition", events[0], + { type: "keydown", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, isComposing: true }); + reset(); + TIP.keyup(keyA); + is(events.length, 1, + description + "TIP.keyup(keyA) should cause keyup event even composition if it's enabled by the pref"); + checkKeyAttrs("TIP.keyup(keyA) during composition", events[0], + { type: "keyup", key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, isComposing: true }); + + } finally { + TIP.cancelComposition(); + Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition"); + } + + // Test .location computation + const kCodeToLocation = [ + { code: "BracketLeft", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "BracketRight", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Comma", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Digit0", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Digit1", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Digit2", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Digit3", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Digit4", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Digit5", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Digit6", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Digit7", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Digit8", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Digit9", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Equal", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Minus", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Period", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Slash", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "AltLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT }, + { code: "AltRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT }, + { code: "CapsLock", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "ContextMenu", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "ControlLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT }, + { code: "ControlRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT }, + { code: "Enter", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "MetaLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT }, + { code: "MetaRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT }, + { code: "ShiftLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT }, + { code: "ShiftRight", location: KeyboardEvent.DOM_KEY_LOCATION_RIGHT }, + { code: "Space", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Tab", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "ArrowDown", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "ArrowLeft", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "ArrowRight", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "ArrowUp", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "NumLock", location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD }, + { code: "Numpad0", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "Numpad1", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "Numpad2", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "Numpad3", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "Numpad4", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "Numpad5", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "Numpad6", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "Numpad7", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "Numpad8", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "Numpad9", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadAdd", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadBackspace", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadClear", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadClearEntry", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadComma", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadDecimal", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadDivide", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadEnter", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadEqual", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadMemoryAdd", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadMemoryClear", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadMemoryRecall", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadMemoryStore", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadMemorySubtract", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadMultiply", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadParenLeft", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadParenRight", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + { code: "NumpadSubtract", location: KeyboardEvent.DOM_KEY_LOCATION_NUMPAD }, + ]; + for (let i = 0; i < kCodeToLocation.length; i++) { + let keyEvent = new KeyboardEvent("", { code: kCodeToLocation[i].code }); + reset(); + doPreventDefaults = [ "keypress" ]; + // If the location isn't initialized or initialized with 0, it should be computed from the code value. + TIP.keydown(keyEvent); + TIP.keyup(keyEvent); + let longDesc = description + "testing computation of .location of \"" + kCodeToLocation[i].code + "\", "; + is(events.length, 3, + longDesc + "keydown, keypress and keyup events should be fired"); + for (let j = 0; j < events.length; j++) { + is(events[j].location, kCodeToLocation[i].location, + longDesc + " type=\"" + events[j].type + "\", location value is wrong"); + } + // However, if KEY_KEEP_KEY_LOCATION_STANDARD is specified, .location value should be kept as DOM_KEY_LOCATION_STANDARD (0). + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD); + TIP.keyup(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD); + longDesc = description + "testing if .location is forcibly set to DOM_KEY_LOCATION_STANDARD, "; + is(events.length, 3, + longDesc + "keydown, keypress and keyup events should be fired"); + for (let j = 0; j < events.length; j++) { + is(events[j].location, KeyboardEvent.DOM_KEY_LOCATION_STANDARD, + longDesc + " type=\"" + events[j].type + "\", location value is not 0"); + } + // If .location is initialized with non-zero value, the value shouldn't be computed again. + let keyEventWithLocation = new KeyboardEvent("", { code: kCodeToLocation[i].code, location: 0xFF }); + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(keyEventWithLocation); + TIP.keyup(keyEventWithLocation); + longDesc = description + "testing if .location is not computed for \"" + kCodeToLocation[i].location + "\", "; + is(events.length, 3, + longDesc + "keydown, keypress and keyup events should be fired"); + for (let j = 0; j < events.length; j++) { + is(events[j].location, 0xFF, + longDesc + " type=\"" + events[j].type + "\", location shouldn't be computed if it's initialized with non-zero value"); + } + } + + // Test .keyCode value computation + const kKeyToKeyCode = [ + { key: "Cancel", keyCode: KeyboardEvent.DOM_VK_CANCEL }, + { key: "Help", keyCode: KeyboardEvent.DOM_VK_HELP }, + { key: "Backspace", keyCode: KeyboardEvent.DOM_VK_BACK_SPACE }, + { key: "Tab", keyCode: KeyboardEvent.DOM_VK_TAB }, + { key: "Clear", keyCode: KeyboardEvent.DOM_VK_CLEAR }, + { key: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }, + { key: "Shift", keyCode: KeyboardEvent.DOM_VK_SHIFT, isModifier: true }, + { key: "Control", keyCode: KeyboardEvent.DOM_VK_CONTROL, isModifier: true }, + { key: "Alt", keyCode: KeyboardEvent.DOM_VK_ALT, isModifier: true }, + { key: "Pause", keyCode: KeyboardEvent.DOM_VK_PAUSE }, + { key: "CapsLock", keyCode: KeyboardEvent.DOM_VK_CAPS_LOCK, isModifier: true, isLockableModifier: true }, + { key: "Hiragana", keyCode: KeyboardEvent.DOM_VK_KANA }, + { key: "Katakana", keyCode: KeyboardEvent.DOM_VK_KANA }, + { key: "HiraganaKatakana", keyCode: KeyboardEvent.DOM_VK_KANA }, + { key: "KanaMode", keyCode: KeyboardEvent.DOM_VK_KANA }, + { key: "HangulMode", keyCode: KeyboardEvent.DOM_VK_HANGUL }, + { key: "Eisu", keyCode: KeyboardEvent.DOM_VK_EISU }, + { key: "JunjaMode", keyCode: KeyboardEvent.DOM_VK_JUNJA }, + { key: "FinalMode", keyCode: KeyboardEvent.DOM_VK_FINAL }, + { key: "HanjaMode", keyCode: KeyboardEvent.DOM_VK_HANJA }, + { key: "KanjiMode", keyCode: KeyboardEvent.DOM_VK_KANJI }, + { key: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE }, + { key: "Convert", keyCode: KeyboardEvent.DOM_VK_CONVERT }, + { key: "NonConvert", keyCode: KeyboardEvent.DOM_VK_NONCONVERT }, + { key: "Accept", keyCode: KeyboardEvent.DOM_VK_ACCEPT }, + { key: "ModeChange", keyCode: KeyboardEvent.DOM_VK_MODECHANGE }, + { key: "PageUp", keyCode: KeyboardEvent.DOM_VK_PAGE_UP }, + { key: "PageDown", keyCode: KeyboardEvent.DOM_VK_PAGE_DOWN }, + { key: "End", keyCode: KeyboardEvent.DOM_VK_END }, + { key: "Home", keyCode: KeyboardEvent.DOM_VK_HOME }, + { key: "ArrowLeft", keyCode: KeyboardEvent.DOM_VK_LEFT }, + { key: "ArrowUp", keyCode: KeyboardEvent.DOM_VK_UP }, + { key: "ArrowRight", keyCode: KeyboardEvent.DOM_VK_RIGHT }, + { key: "ArrowDown", keyCode: KeyboardEvent.DOM_VK_DOWN }, + { key: "Select", keyCode: KeyboardEvent.DOM_VK_SELECT }, + { key: "Print", keyCode: KeyboardEvent.DOM_VK_PRINT }, + { key: "Execute", keyCode: KeyboardEvent.DOM_VK_EXECUTE }, + { key: "PrintScreen", keyCode: KeyboardEvent.DOM_VK_PRINTSCREEN }, + { key: "Insert", keyCode: KeyboardEvent.DOM_VK_INSERT }, + { key: "Delete", keyCode: KeyboardEvent.DOM_VK_DELETE }, + { key: "ContextMenu", keyCode: KeyboardEvent.DOM_VK_CONTEXT_MENU }, + { key: "F1", keyCode: KeyboardEvent.DOM_VK_F1 }, + { key: "F2", keyCode: KeyboardEvent.DOM_VK_F2 }, + { key: "F3", keyCode: KeyboardEvent.DOM_VK_F3 }, + { key: "F4", keyCode: KeyboardEvent.DOM_VK_F4 }, + { key: "F5", keyCode: KeyboardEvent.DOM_VK_F5 }, + { key: "F6", keyCode: KeyboardEvent.DOM_VK_F6 }, + { key: "F7", keyCode: KeyboardEvent.DOM_VK_F7 }, + { key: "F8", keyCode: KeyboardEvent.DOM_VK_F8 }, + { key: "F9", keyCode: KeyboardEvent.DOM_VK_F9 }, + { key: "F10", keyCode: KeyboardEvent.DOM_VK_F10 }, + { key: "F11", keyCode: KeyboardEvent.DOM_VK_F11 }, + { key: "F12", keyCode: KeyboardEvent.DOM_VK_F12 }, + { key: "F13", keyCode: KeyboardEvent.DOM_VK_F13 }, + { key: "F14", keyCode: KeyboardEvent.DOM_VK_F14 }, + { key: "F15", keyCode: KeyboardEvent.DOM_VK_F15 }, + { key: "F16", keyCode: KeyboardEvent.DOM_VK_F16 }, + { key: "F17", keyCode: KeyboardEvent.DOM_VK_F17 }, + { key: "F18", keyCode: KeyboardEvent.DOM_VK_F18 }, + { key: "F19", keyCode: KeyboardEvent.DOM_VK_F19 }, + { key: "F20", keyCode: KeyboardEvent.DOM_VK_F20 }, + { key: "F21", keyCode: KeyboardEvent.DOM_VK_F21 }, + { key: "F22", keyCode: KeyboardEvent.DOM_VK_F22 }, + { key: "F23", keyCode: KeyboardEvent.DOM_VK_F23 }, + { key: "F24", keyCode: KeyboardEvent.DOM_VK_F24 }, + { key: "NumLock", keyCode: KeyboardEvent.DOM_VK_NUM_LOCK, isModifier: true, isLockableModifier: true }, + { key: "ScrollLock", keyCode: KeyboardEvent.DOM_VK_SCROLL_LOCK, isModifier: true, isLockableModifier: true }, + { key: "AudioVolumeMute", keyCode: KeyboardEvent.DOM_VK_VOLUME_MUTE }, + { key: "AudioVolumeDown", keyCode: KeyboardEvent.DOM_VK_VOLUME_DOWN }, + { key: "AudioVolumeUp", keyCode: KeyboardEvent.DOM_VK_VOLUME_UP }, + { key: "Meta", keyCode: kIsMac + ? KeyboardEvent.DOM_VK_META + : KeyboardEvent.DOM_VK_WIN, isModifier: true }, + { key: "AltGraph", keyCode: KeyboardEvent.DOM_VK_ALTGR, isModifier: true }, + { key: "Attn", keyCode: KeyboardEvent.DOM_VK_ATTN }, + { key: "CrSel", keyCode: KeyboardEvent.DOM_VK_CRSEL }, + { key: "ExSel", keyCode: KeyboardEvent.DOM_VK_EXSEL }, + { key: "EraseEof", keyCode: KeyboardEvent.DOM_VK_EREOF }, + { key: "Play", keyCode: KeyboardEvent.DOM_VK_PLAY }, + { key: "ZoomToggle", keyCode: KeyboardEvent.DOM_VK_ZOOM }, + { key: "ZoomIn", keyCode: KeyboardEvent.DOM_VK_ZOOM }, + { key: "ZoomOut", keyCode: KeyboardEvent.DOM_VK_ZOOM }, + { key: "Unidentified", keyCode: 0 }, + { key: "a", keyCode: 0, isPrintable: true }, + { key: "A", keyCode: 0, isPrintable: true }, + { key: " ", keyCode: 0, isPrintable: true }, + { key: "", keyCode: 0, isPrintable: true }, + ]; + + for (let i = 0; i < kKeyToKeyCode.length; i++) { + let keyEvent = new KeyboardEvent("", { key: kKeyToKeyCode[i].key }); + var causeKeypress = !kKeyToKeyCode[i].isModifier; + var baseFlags = kKeyToKeyCode[i].isPrintable ? 0 : TIP.KEY_NON_PRINTABLE_KEY; + reset(); + doPreventDefaults = [ "keypress" ]; + // If the keyCode isn't initialized or initialized with 0, it should be computed from the key value only when it's a printable key. + TIP.keydown(keyEvent, baseFlags); + TIP.keyup(keyEvent, baseFlags); + let longDesc = description + "testing computation of .keyCode of \"" + kKeyToKeyCode[i].key + "\", "; + is(events.length, causeKeypress ? 3 : 2, + longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired"); + for (let j = 0; j < events.length; j++) { + is(events[j].keyCode, events[j].type == "keypress" && kKeyToKeyCode[i].isPrintable ? 0 : kKeyToKeyCode[i].keyCode, + longDesc + " type=\"" + events[j].type + "\", keyCode value is wrong"); + } + // However, if KEY_KEEP_KEYCODE_ZERO is specified, .keyCode value should be kept as 0. + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO | baseFlags); + TIP.keyup(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO | baseFlags); + longDesc = description + "testing if .keyCode is forcibly set to KEY_KEEP_KEYCODE_ZERO, "; + is(events.length, causeKeypress ? 3 : 2, + longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired"); + for (let j = 0; j < events.length; j++) { + is(events[j].keyCode, 0, + longDesc + " type=\"" + events[j].type + "\", keyCode value is not 0"); + } + // If .keyCode is initialized with non-zero value, the value shouldn't be computed again. + let keyEventWithLocation = new KeyboardEvent("", { key: kKeyToKeyCode[i].key, keyCode: 0xFF }); + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(keyEventWithLocation, baseFlags); + TIP.keyup(keyEventWithLocation, baseFlags); + longDesc = description + "testing if .keyCode is not computed for \"" + kKeyToKeyCode[i].key + "\", "; + is(events.length, causeKeypress ? 3 : 2, + longDesc + "keydown" + (causeKeypress ? ", keypress" : "") + " and keyup events should be fired"); + for (let j = 0; j < events.length; j++) { + is(events[j].keyCode, events[j].type == "keypress" && kKeyToKeyCode[i].isPrintable ? 0 : 0xFF, + longDesc + " type=\"" + events[j].type + "\", keyCode shouldn't be computed if it's initialized with non-zero value"); + } + // Unlock lockable modifier if the key is a lockable modifier key. + if (kKeyToKeyCode[i].isLockableModifier) { + TIP.keydown(keyEvent, baseFlags); + TIP.keyup(keyEvent, baseFlags); + } + } + + // Modifier state tests + var sharedTIP = createTIP(); + ok(sharedTIP.beginInputTransactionForTests(otherWindow), + description + "sharedTIP.beginInputTransactionForTests(otherWindow) should return true"); + TIP.shareModifierStateOf(sharedTIP); + var independentTIP = createTIP(); + const kModifierKeys = [ + { key: "Alt", code: "AltLeft", isLockable: false }, + { key: "Alt", code: "AltRight", isLockable: false }, + { key: "AltGraph", code: "AltRight", isLockable: false }, + { key: "CapsLock", code: "CapsLock", isLockable: true }, + { key: "Control", code: "ControlLeft", isLockable: false }, + { key: "Control", code: "ControlRight", isLockable: false }, + { key: "Fn", code: "Fn", isLockable: false }, + { key: "FnLock", code: "", isLockable: true }, + { key: "Meta", code: "MetaLeft", isLockable: false }, + { key: "Meta", code: "MetaRight", isLockable: false }, + { key: "NumLock", code: "NumLock", isLockable: true }, + { key: "ScrollLock", code: "ScrollLock", isLockable: true }, + { key: "Shift", code: "ShiftLeft", isLockable: false }, + { key: "Shift", code: "ShiftRight", isLockable: false }, + { key: "Symbol", code: "", isLockable: false }, + { key: "SymbolLock", code: "", isLockable: true }, + ]; + + function checkModifiers(aTestDesc, aEvent, aType, aKey, aCode, aModifiers) + { + var desc = description + aTestDesc + ", type=\"" + aEvent.type + "\", key=\"" + aEvent.key + "\", code=\"" + aEvent.code + "\""; + is(aEvent.type, aType, + desc + ", .type value is wrong"); + if (aEvent.type != aType) { + return; + } + is(aEvent.key, aKey, + desc + ", .key value is wrong"); + is(aEvent.code, aCode, + desc + ", .code value is wrong"); + is(aEvent.altKey, aModifiers.includes("Alt"), + desc + ", .altKey value is wrong"); + is(aEvent.ctrlKey, aModifiers.includes("Control"), + desc + ", .ctrlKey value is wrong"); + is(aEvent.metaKey, aModifiers.includes("Meta"), + desc + ", .metaKey value is wrong"); + is(aEvent.shiftKey, aModifiers.includes("Shift"), + desc + ", .shiftKey value is wrong"); + /* eslint-disable-next-line no-shadow */ + for (var i = 0; i < kModifiers.length; i++) { + is(aEvent.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]), + desc + ", .getModifierState(\"" + kModifiers[i] + "\") returns wrong value"); + } + } + + function checkAllTIPModifiers(aTestDesc, aModifiers) + { + /* eslint-disable-next-line no-shadow */ + for (var i = 0; i < kModifiers.length; i++) { + is(TIP.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]), + aTestDesc + ", TIP.getModifierState(\"" + kModifiers[i] + "\") returns wrong value"); + is(sharedTIP.getModifierState(kModifiers[i]), TIP.getModifierState(kModifiers[i]), + aTestDesc + ", sharedTIP.getModifierState(\"" + kModifiers[i] + "\") returns different value from TIP"); + is(independentTIP.getModifierState(kModifiers[i]), false, + aTestDesc + ", independentTIP.getModifierState(\"" + kModifiers[i] + "\") should return false"); + } + } + + // First, all modifiers must be false. + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(keyA); + TIP.keyup(keyA); + + is(events.length, 3, + description + "TIP.keydown(keyA) and TIP.keyup(keyA) should cause keydown, keypress and keyup"); + checkModifiers("Before dispatching modifier key events", events[0], "keydown", "a", "KeyA", []); + checkModifiers("Before dispatching modifier key events", events[1], "keypress", "a", "KeyA", []); + checkModifiers("Before dispatching modifier key events", events[2], "keyup", "a", "KeyA", []); + + // Test each modifier keydown/keyup causes activating/inactivating the modifier state. + for (var i = 0; i < kModifierKeys.length; i++) { + reset(); + doPreventDefaults = [ "keypress" ]; + var modKey = new KeyboardEvent("", { key: kModifierKeys[i].key, code: kModifierKeys[i].code }); + let testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") and a printable key"; + if (!kModifierKeys[i].isLockable) { + TIP.keydown(modKey); + checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" keydown", [ kModifierKeys[i].key ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ kModifierKeys[i].key ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ kModifierKeys[i].key ]); + TIP.keyup(modKey); + checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" keyup", [ ]); + is(events.length, 5, + description + testDesc + " should cause 5 events"); + checkModifiers(testDesc, events[0], "keydown", kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[1], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[2], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[3], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[4], "keyup", kModifierKeys[i].key, kModifierKeys[i].code, [ ]); + + // KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT shouldn't cause key events of modifier keys, but should modify the modifier state. + reset(); + doPreventDefaults = [ "keypress" ]; + testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") with KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT and a printable key"; + TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + TIP.keydown(keyA); + TIP.keyup(keyA); + TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + TIP.keydown(keyA); + TIP.keyup(keyA); + is(events.length, 6, + description + testDesc + " should cause 6 events"); + checkModifiers(testDesc, events[0], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[1], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[2], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[3], "keydown", "a", "KeyA", [ ]); + checkModifiers(testDesc, events[4], "keypress", "a", "KeyA", [ ]); + checkModifiers(testDesc, events[5], "keyup", "a", "KeyA", [ ]); + } else { + TIP.keydown(modKey); + checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" first keydown", [ kModifierKeys[i].key ]); + TIP.keyup(modKey); + checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" first keyup", [ kModifierKeys[i].key ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ kModifierKeys[i].key ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ kModifierKeys[i].key ]); + TIP.keydown(modKey); + checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" second keydown", [ ]); + TIP.keyup(modKey); + checkAllTIPModifiers(testDesc + ", \"" + kModifierKeys[i].key + "\" second keyup", [ ]); + is(events.length, 7, + description + testDesc + " should cause 7 events"); + checkModifiers(testDesc, events[0], "keydown", kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[1], "keyup", kModifierKeys[i].key, kModifierKeys[i].code, [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[5], "keydown", kModifierKeys[i].key, kModifierKeys[i].code, [ ]); + checkModifiers(testDesc, events[6], "keyup", kModifierKeys[i].key, kModifierKeys[i].code, [ ]); + + // KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT shouldn't cause key events of modifier keys, but should modify the modifier state. + reset(); + doPreventDefaults = [ "keypress" ]; + testDesc = "A modifier key \"" + kModifierKeys[i].key + "\" (\"" + kModifierKeys[i].code + "\") with KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT and a printable key"; + TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + TIP.keydown(keyA); + TIP.keyup(keyA); + TIP.keydown(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + TIP.keyup(modKey, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + TIP.keydown(keyA); + TIP.keyup(keyA); + is(events.length, 6, + description + testDesc + " should cause 6 events"); + checkModifiers(testDesc, events[0], "keydown", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[1], "keypress", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[2], "keyup", "a", "KeyA", [ kModifierKeys[i].key ]); + checkModifiers(testDesc, events[3], "keydown", "a", "KeyA", [ ]); + checkModifiers(testDesc, events[4], "keypress", "a", "KeyA", [ ]); + checkModifiers(testDesc, events[5], "keyup", "a", "KeyA", [ ]); + } + } + + // Modifier state should be inactivated only when all pressed modifiers are released + var shiftLeft = new KeyboardEvent("", { key: "Shift", code: "ShiftLeft" }); + var shiftRight = new KeyboardEvent("", { key: "Shift", code: "ShiftRight" }); + var shiftVirtual = new KeyboardEvent("", { key: "Shift", code: "" }); + var altGrVirtual = new KeyboardEvent("", { key: "AltGraph", code: "" }); + var ctrlVirtual = new KeyboardEvent("", { key: "Control", code: "" }); + + let testDesc = "ShiftLeft press -> ShiftRight press -> ShiftRight release -> ShiftLeft release"; + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]); + TIP.keydown(shiftRight); + checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]); + TIP.keyup(shiftRight); + checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Right-Shift keyup)", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Right-Shift keyup)", [ "Shift" ]); + TIP.keyup(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]); + + is(events.length, 10, + description + testDesc + " should cause 10 events"); + checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]); + checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]); + checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftRight", [ "Shift" ]); + checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftLeft", [ ]); + + testDesc = "ShiftLeft press -> ShiftRight press -> ShiftLeft release -> ShiftRight release"; + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]); + TIP.keydown(shiftRight); + checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]); + TIP.keyup(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup)", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup)", [ "Shift" ]); + TIP.keyup(shiftRight); + checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ ]); + + is(events.length, 10, + description + testDesc + " should cause 10 events"); + checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]); + checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]); + checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftLeft", [ "Shift" ]); + checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftRight", [ ]); + + testDesc = "ShiftLeft press -> virtual Shift press -> virtual Shift release -> ShiftLeft release"; + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]); + TIP.keydown(shiftVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]); + TIP.keyup(shiftVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "Shift" ]); + TIP.keyup(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]); + + is(events.length, 10, + description + testDesc + " should cause 10 events"); + checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]); + checkModifiers(testDesc, events[1], "keydown", "Shift", "", [ "Shift" ]); + checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[5], "keyup", "Shift", "", [ "Shift" ]); + checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftLeft", [ ]); + + testDesc = "virtual Shift press -> ShiftRight press -> ShiftRight release -> virtual Shift release"; + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(shiftVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]); + TIP.keydown(shiftRight); + checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]); + TIP.keyup(shiftRight); + checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Right-Shift keyup)", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Right-Shift keyup)", [ "Shift" ]); + TIP.keyup(shiftVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ ]); + + is(events.length, 10, + description + testDesc + " should cause 10 events"); + checkModifiers(testDesc, events[0], "keydown", "Shift", "", [ "Shift" ]); + checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]); + checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftRight", [ "Shift" ]); + checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[9], "keyup", "Shift", "", [ ]); + + testDesc = "ShiftLeft press -> ShiftRight press -> ShiftRight release -> ShiftRight release -> ShiftLeft release"; + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]); + TIP.keydown(shiftRight); + checkAllTIPModifiers(testDesc + ", Right-Shift keydown", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]); + TIP.keyup(shiftRight); + checkAllTIPModifiers(testDesc + ", Right-Shift keyup", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "Shift" ]); + TIP.keyup(shiftRight); + checkAllTIPModifiers(testDesc + ", Right-Shift keyup again", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup again)", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup again)", [ "Shift" ]); + TIP.keyup(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]); + + is(events.length, 14, + description + testDesc + " should cause 14 events"); + checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]); + checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftRight", [ "Shift" ]); + checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftRight", [ "Shift" ]); + checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftRight", [ "Shift" ]); + checkModifiers(testDesc, events[10], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[11], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[12], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[13], "keyup", "Shift", "ShiftLeft", [ ]); + + testDesc = "ShiftLeft press -> ShiftLeft press -> ShiftLeft release -> ShiftLeft release"; + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keydown", [ "Shift" ]); + TIP.keydown(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keydown again", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift" ]); + TIP.keyup(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keyup", [ ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup)", [ ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup)", [ ]); + TIP.keyup(shiftLeft); + checkAllTIPModifiers(testDesc + ", Left-Shift keyup again", [ ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Left-Shift keyup again)", [ ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Left-Shift keyup again)", [ ]); + + is(events.length, 13, + description + testDesc + " should cause 13 events"); + checkModifiers(testDesc, events[0], "keydown", "Shift", "ShiftLeft", [ "Shift" ]); + checkModifiers(testDesc, events[1], "keydown", "Shift", "ShiftLeft", [ "Shift" ]); + checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[5], "keyup", "Shift", "ShiftLeft", [ ]); + checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ ]); + checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ ]); + checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ ]); + checkModifiers(testDesc, events[9], "keyup", "Shift", "ShiftLeft", [ ]); + checkModifiers(testDesc, events[10], "keydown", "a", "KeyA", [ ]); + checkModifiers(testDesc, events[11], "keypress", "a", "KeyA", [ ]); + checkModifiers(testDesc, events[12], "keyup", "a", "KeyA", [ ]); + + testDesc = "virtual Shift press -> virtual AltGraph press -> virtual AltGraph release -> virtual Shift release"; + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(shiftVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]); + TIP.keydown(altGrVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keydown", [ "Shift", "AltGraph" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift", "AltGraph" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift", "AltGraph" ]); + TIP.keyup(altGrVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keyup", [ "Shift" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-AltGraph keyup)", [ "Shift" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-AltGraph keyup)", [ "Shift" ]); + TIP.keyup(shiftVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ ]); + + is(events.length, 10, + description + testDesc + " should cause 10 events"); + checkModifiers(testDesc, events[0], "keydown", "Shift", "", [ "Shift" ]); + checkModifiers(testDesc, events[1], "keydown", "AltGraph", "", [ "Shift", "AltGraph" ]); + checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift", "AltGraph" ]); + checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift", "AltGraph" ]); + checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift", "AltGraph" ]); + checkModifiers(testDesc, events[5], "keyup", "AltGraph", "", [ "Shift" ]); + checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "Shift" ]); + checkModifiers(testDesc, events[9], "keyup", "Shift", "", [ ]); + + testDesc = "virtual Shift press -> virtual AltGraph press -> virtual Shift release -> virtual AltGr release"; + reset(); + doPreventDefaults = [ "keypress" ]; + TIP.keydown(shiftVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-Shift keydown", [ "Shift" ]); + TIP.keydown(altGrVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keydown", [ "Shift", "AltGraph" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown", [ "Shift", "AltGraph" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup", [ "Shift", "AltGraph" ]); + TIP.keyup(shiftVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-Shift keyup", [ "AltGraph" ]); + TIP.keydown(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keydown (after Virtual-Shift keyup)", [ "AltGraph" ]); + TIP.keyup(keyA); + checkAllTIPModifiers(testDesc + ", \"a\" keyup (after Virtual-Shift keyup)", [ "AltGraph" ]); + TIP.keyup(altGrVirtual); + checkAllTIPModifiers(testDesc + ", Virtual-AltGraph keyup", [ ]); + + is(events.length, 10, + description + testDesc + " should cause 10 events"); + checkModifiers(testDesc, events[0], "keydown", "Shift", "", [ "Shift" ]); + checkModifiers(testDesc, events[1], "keydown", "AltGraph", "", [ "Shift", "AltGraph" ]); + checkModifiers(testDesc, events[2], "keydown", "a", "KeyA", [ "Shift", "AltGraph" ]); + checkModifiers(testDesc, events[3], "keypress", "a", "KeyA", [ "Shift", "AltGraph" ]); + checkModifiers(testDesc, events[4], "keyup", "a", "KeyA", [ "Shift", "AltGraph" ]); + checkModifiers(testDesc, events[5], "keyup", "Shift", "", [ "AltGraph" ]); + checkModifiers(testDesc, events[6], "keydown", "a", "KeyA", [ "AltGraph" ]); + checkModifiers(testDesc, events[7], "keypress", "a", "KeyA", [ "AltGraph" ]); + checkModifiers(testDesc, events[8], "keyup", "a", "KeyA", [ "AltGraph" ]); + checkModifiers(testDesc, events[9], "keyup", "AltGraph", "", [ ]); + + // shareModifierStateOf(null) should cause resetting the modifier state + function checkTIPModifiers(aTestDesc, aTIP, aModifiers) + { + /* eslint-disable-next-line no-shadow */ + for (var i = 0; i < kModifiers.length; i++) { + is(aTIP.getModifierState(kModifiers[i]), aModifiers.includes(kModifiers[i]), + description + aTestDesc + ", aTIP.getModifierState(\"" + kModifiers[i] + "\") returns wrong value"); + } + } + TIP.keydown(shiftVirtual); + TIP.keydown(altGrVirtual); + sharedTIP.shareModifierStateOf(null); + checkTIPModifiers("sharedTIP.sharedModifierStateOf(null) shouldn't cause TIP's modifiers reset", TIP, [ "Shift", "AltGraph" ]); + checkTIPModifiers("sharedTIP.sharedModifierStateOf(null) should cause sharedTIP modifiers reset", sharedTIP, [ ]); + + // sharedTIP.shareModifierStateOf(null) should be unlinked from TIP. + TIP.keydown(ctrlVirtual); + checkTIPModifiers("TIP.keydown(ctrlVirtual) should cause TIP's modifiers set", TIP, [ "Shift", "AltGraph", "Control" ]); + checkTIPModifiers("TIP.keydown(ctrlVirtual) shouldn't cause sharedTIP modifiers set", sharedTIP, [ ]); + + // beginInputTransactionForTests() shouldn't cause modifier state reset. + ok(TIP.beginInputTransactionForTests(otherWindow), + description + "TIP.beginInputTransactionForTests(otherWindow) should return true"); + checkTIPModifiers("TIP.beginInputTransactionForTests(otherWindow) shouldn't cause TIP's modifiers set", TIP, [ "Shift", "AltGraph", "Control" ]); + TIP.keyup(shiftLeft); + TIP.keyup(altGrVirtual); + TIP.keyup(ctrlVirtual); + checkTIPModifiers("TIP should keep modifier's physical key state", TIP, [ "Shift" ]); + ok(TIP.beginInputTransactionForTests(window), + description + "TIP.beginInputTransactionForTests(window) should return true"); + checkTIPModifiers("TIP.beginInputTransactionForTests(window) shouldn't cause TIP's modifiers set", TIP, [ "Shift" ]); + TIP.keyup(shiftVirtual); + checkTIPModifiers("TIP should keep modifier's physical key state", TIP, [ ]); + + window.removeEventListener("keydown", handler); + window.removeEventListener("keypress", handler); + window.removeEventListener("keyup", handler); +} + +function runErrorTests() +{ + var description = "runErrorTests(): "; + + var TIP = createTIP(); + ok(TIP.beginInputTransactionForTests(window), + description + "TIP.beginInputTransactionForTests() should succeed"); + + input.value = ""; + input.focus(); + + // startComposition() should throw an exception if there is already a composition + TIP.startComposition(); + try { + TIP.startComposition(); + ok(false, + description + "startComposition() should fail if it was already called"); + } catch (e) { + ok(e.message.includes("NS_ERROR_FAILURE"), + description + "startComposition() should cause NS_ERROR_FAILURE if there is already composition"); + } finally { + TIP.cancelComposition(); + } + + // cancelComposition() should throw an exception if there is no composition + try { + TIP.cancelComposition(); + ok(false, + description + "cancelComposition() should fail if there is no composition"); + } catch (e) { + ok(e.message.includes("NS_ERROR_FAILURE"), + description + "cancelComposition() should cause NS_ERROR_FAILURE if there is no composition"); + } + + // commitComposition() without commit string should throw an exception if there is no composition + try { + TIP.commitComposition(); + ok(false, + description + "commitComposition() should fail if there is no composition"); + } catch (e) { + ok(e.message.includes("NS_ERROR_FAILURE"), + description + "commitComposition() should cause NS_ERROR_FAILURE if there is no composition"); + } + + // commitCompositionWith("") should throw an exception if there is no composition + try { + TIP.commitCompositionWith(""); + ok(false, + description + "commitCompositionWith(\"\") should fail if there is no composition"); + } catch (e) { + ok(e.message.includes("NS_ERROR_FAILURE"), + description + "commitCompositionWith(\"\") should cause NS_ERROR_FAILURE if there is no composition"); + } + + // Pending composition string should allow to flush without clause information (for compatibility) + try { + TIP.setPendingCompositionString("foo"); + TIP.flushPendingComposition(); + ok(true, + description + "flushPendingComposition() should succeed even if appendClauseToPendingComposition() has never been called"); + TIP.cancelComposition(); + } catch (e) { + ok(false, + description + "flushPendingComposition() shouldn't cause an exception even if appendClauseToPendingComposition() has never been called"); + } + + // Pending composition string must be filled by clause information + try { + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(2, TIP.ATTR_RAW_CLAUSE); + TIP.flushPendingComposition(); + ok(false, + description + "flushPendingComposition() should fail if appendClauseToPendingComposition() doesn't fill all composition string"); + TIP.cancelComposition(); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if appendClauseToPendingComposition() doesn't fill all composition string"); + } + + // Pending composition string must not be shorter than appended clause length + try { + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE); + TIP.flushPendingComposition(); + ok(false, + description + "flushPendingComposition() should fail if appendClauseToPendingComposition() appends longer clause information"); + TIP.cancelComposition(); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if appendClauseToPendingComposition() appends longer clause information"); + } + + // Pending composition must not have clause information with empty string + try { + TIP.appendClauseToPendingComposition(1, TIP.ATTR_RAW_CLAUSE); + TIP.flushPendingComposition(); + ok(false, + description + "flushPendingComposition() should fail if there is a clause with empty string"); + TIP.cancelComposition(); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if there is a clause with empty string"); + } + + // Appending a clause whose length is 0 should cause an exception + try { + TIP.appendClauseToPendingComposition(0, TIP.ATTR_RAW_CLAUSE); + ok(false, + description + "appendClauseToPendingComposition() should fail if the length is 0"); + TIP.flushPendingComposition(); + TIP.cancelComposition(); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "appendClauseToPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if the length is 0"); + } + + // Appending a clause whose attribute is invalid should cause an exception + try { + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, 0); + ok(false, + description + "appendClauseToPendingComposition() should fail if the attribute is invalid"); + TIP.flushPendingComposition(); + TIP.cancelComposition(); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "appendClauseToPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if the attribute is invalid"); + } + + // Setting caret position outside of composition string should cause an exception + try { + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(4); + TIP.flushPendingComposition(); + ok(false, + description + "flushPendingComposition() should fail if caret position is out of composition string"); + TIP.cancelComposition(); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if caret position is out of composition string"); + } + + // Calling keydown() with a KeyboardEvent initialized with invalid code value should cause an exception. + input.value = ""; + try { + let keyInvalidCode = new KeyboardEvent("", { key: "f", code: "InvalidCodeValue", keyCode: KeyboardEvent.DOM_VK_F }); + TIP.keydown(keyInvalidCode); + ok(false, + description + "TIP.keydown(keyInvalidCode) should cause throwing an exception because its code value is not registered"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "TIP.keydown(keyInvalidCode) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE"); + } finally { + is(input.value, "", + description + "The input element should not be modified"); + } + + // Calling keyup() with a KeyboardEvent initialized with invalid code value should cause an exception. + input.value = ""; + try { + let keyInvalidCode = new KeyboardEvent("", { key: "f", code: "InvalidCodeValue", keyCode: KeyboardEvent.DOM_VK_F }); + TIP.keyup(keyInvalidCode); + ok(false, + description + "TIP.keyup(keyInvalidCode) should cause throwing an exception because its code value is not registered"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "TIP.keyup(keyInvalidCode) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE"); + } finally { + is(input.value, "", + description + "The input element should not be modified"); + } + + // Calling keydown(KEY_NON_PRINTABLE_KEY) with a KeyboardEvent initialized with non-key name should cause an exception. + input.value = ""; + try { + let keyInvalidKey = new KeyboardEvent("", { key: "ESCAPE", code: "Escape", keyCode: KeyboardEvent.DOM_VK_Escape}); + TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY); + ok(false, + description + "TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception because its key value is not registered"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE"); + } finally { + is(input.value, "", + description + "The input element should not be modified"); + } + + // Calling keyup(KEY_NON_PRINTABLE_KEY) with a KeyboardEvent initialized with non-key name should cause an exception. + input.value = ""; + try { + let keyInvalidKey = new KeyboardEvent("", { key: "ESCAPE", code: "Escape", keyCode: KeyboardEvent.DOM_VK_Escape}); + TIP.keydown(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY); + ok(false, + description + "TIP.keyup(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception because its key value is not registered"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "keyup(keyInvalidKey, TIP.KEY_NON_PRINTABLE_KEY) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE"); + } finally { + is(input.value, "", + description + "The input element should not be modified"); + } + + // KEY_KEEP_KEY_LOCATION_STANDARD flag should be used only when .location is not initialized with non-zero value. + try { + let keyEvent = new KeyboardEvent("", { code: "Enter", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT }); + TIP.keydown(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD); + ok(false, + description + "keydown(KEY_KEEP_KEY_LOCATION_STANDARD) should fail if the .location of the key event is initialized with non-zero value"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "keydown(KEY_KEEP_KEY_LOCATION_STANDARD) should cause NS_ERROR_ILLEGAL_VALUE if the .location of the key event is initialized with nonzero value"); + } + try { + let keyEvent = new KeyboardEvent("", { code: "Enter", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT }); + TIP.keyup(keyEvent, TIP.KEY_KEEP_KEY_LOCATION_STANDARD); + ok(false, + description + "keyup(KEY_KEEP_KEY_LOCATION_STANDARD) should fail if the .location of the key event is initialized with non-zero value"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "keyup(KEY_KEEP_KEY_LOCATION_STANDARD) should cause NS_ERROR_ILLEGAL_VALUE if the .location of the key event is initialized with nonzero value"); + } + + // KEY_KEEP_KEYCODE_ZERO flag should be used only when .keyCode is not initialized with non-zero value. + try { + let keyEvent = new KeyboardEvent("", { key: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }); + TIP.keydown(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO); + ok(false, + description + "keydown(KEY_KEEP_KEYCODE_ZERO) should fail if the .keyCode of the key event is initialized with non-zero value"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "keydown(KEY_KEEP_KEYCODE_ZERO) should cause NS_ERROR_ILLEGAL_VALUE if the .keyCode of the key event is initialized with nonzero value"); + } + try { + let keyEvent = new KeyboardEvent("", { key: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN }); + TIP.keyup(keyEvent, TIP.KEY_KEEP_KEYCODE_ZERO); + ok(false, + description + "keyup(KEY_KEEP_KEYCODE_ZERO) should fail if the .keyCode of the key event is initialized with non-zero value"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "keyup(KEY_KEEP_KEYCODE_ZERO) should cause NS_ERROR_ILLEGAL_VALUE if the .keyCode of the key event is initialized with nonzero value"); + } + + // Specifying KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT with non-modifier key, it should cause an exception. + try { + let keyEvent = new KeyboardEvent("", { key: "a", code: "ShiftLeft" }); + TIP.keyup(keyEvent, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + ok(false, + description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should fail if the .key value isn't a modifier key"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should cause NS_ERROR_ILLEGAL_VALUE if the .key value isn't a modifier key"); + } + try { + let keyEvent = new KeyboardEvent("", { key: "Enter", code: "ShiftLeft" }); + TIP.keyup(keyEvent, TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT); + ok(false, + description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should fail if the .key value isn't a modifier key"); + } catch (e) { + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + description + "keydown(KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT) should cause NS_ERROR_ILLEGAL_VALUE if the .key value isn't a modifier key"); + } + + // The type of key events specified to composition methods should be "" or "keydown". + var kKeyEventTypes = [ + { type: "keydown", valid: true }, + { type: "keypress", valid: false }, + { type: "keyup", valid: false }, + { type: "", valid: true }, + { type: "mousedown", valid: false }, + { type: "foo", valid: false }, + ]; + for (var i = 0; i < kKeyEventTypes[i].length; i++) { + var keyEvent = + new KeyboardEvent(kKeyEventTypes[i].type, { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A }); + var testDescription = description + "type=\"" + kKeyEventTypes[i].type + "\", "; + try { + TIP.startComposition(keyEvent); + ok(kKeyEventTypes[i].valid, + testDescription + "TIP.startComposition(keyEvent) should not accept the event type"); + TIP.cancelComposition(); + } catch (e) { + ok(!kKeyEventTypes[i].valid, + testDescription + "TIP.startComposition(keyEvent) should not throw an exception for the event type"); + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + testDescription + "TIP.startComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid"); + } + try { + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(keyEvent); + ok(kKeyEventTypes[i].valid, + testDescription + "TIP.flushPendingComposition(keyEvent) should not accept the event type"); + TIP.cancelComposition(); + } catch (e) { + ok(!kKeyEventTypes[i].valid, + testDescription + "TIP.flushPendingComposition(keyEvent) should not throw an exception for the event type"); + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + testDescription + "TIP.flushPendingComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid"); + } + try { + TIP.startComposition(); + TIP.commitComposition(keyEvent); + ok(kKeyEventTypes[i].valid, + testDescription + "TIP.commitComposition(keyEvent) should not accept the event type"); + } catch (e) { + ok(!kKeyEventTypes[i].valid, + testDescription + "TIP.commitComposition(keyEvent) should not throw an exception for the event type"); + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + testDescription + "TIP.commitComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid"); + TIP.cancelComposition(); + } + try { + TIP.commitCompositionWith("foo", keyEvent); + ok(kKeyEventTypes[i].valid, + testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should not accept the event type"); + } catch (e) { + ok(!kKeyEventTypes[i].valid, + testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should not throw an exception for the event type"); + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + testDescription + "TIP.commitCompositionWith(\"foo\", keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid"); + } + try { + TIP.startComposition(); + TIP.cancelComposition(keyEvent); + ok(kKeyEventTypes[i].valid, + testDescription + "TIP.cancelComposition(keyEvent) should not accept the event type"); + } catch (e) { + ok(!kKeyEventTypes[i].valid, + testDescription + "TIP.cancelComposition(keyEvent) should not throw an exception for the event type"); + ok(e.message.includes("NS_ERROR_ILLEGAL_VALUE"), + testDescription + "TIP.cancelComposition(keyEvent) should cause NS_ERROR_ILLEGAL_VALUE if the key event type isn't valid"); + TIP.cancelComposition(); + } + input.value = ""; + } +} + +function runCommitCompositionTests() +{ + var description = "runCommitCompositionTests(): "; + + var TIP = createTIP(); + ok(TIP.beginInputTransactionForTests(window), + description + "TIP.beginInputTransactionForTests() should succeed"); + + input.focus(); + + // commitComposition() should commit the composition with the last data. + input.value = ""; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + TIP.commitComposition(); + is(input.value, "foo", + description + "commitComposition() should commit the composition with the last data"); + + // commitCompositionWith("") should commit the composition with empty string. + input.value = ""; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + TIP.commitCompositionWith(""); + is(input.value, "", + description + "commitCompositionWith(\"\") should commit the composition with empty string"); + + function doCommit(aText) + { + TIP.commitCompositionWith(aText); + } + + // doCommit() should commit the composition with the last data. + input.value = ""; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + doCommit(); + todo_is(input.value, "foo", + description + "doCommit() should commit the composition with the last data"); + + // doCommit("") should commit the composition with empty string. + input.value = ""; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + doCommit(""); + is(input.value, "", + description + "doCommit(\"\") should commit the composition with empty string"); + + // doCommit(null) should commit the composition with empty string. + input.value = ""; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + doCommit(null); + is(input.value, "", + description + "doCommit(null) should commit the composition with empty string"); + + // doCommit(undefined) should commit the composition with the last data. + input.value = ""; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + doCommit(undefined); + todo_is(input.value, "foo", + description + "doCommit(undefined) should commit the composition with the last data"); + + function doCommitWithNullCheck(aText) + { + TIP.commitCompositionWith(aText ? aText : ""); + } + + // doCommitWithNullCheck() should commit the composition with the last data. + input.value = ""; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + doCommitWithNullCheck(); + is(input.value, "", + description + "doCommitWithNullCheck() should commit the composition with empty string"); + + // doCommitWithNullCheck("") should commit the composition with empty string. + input.value = ""; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + doCommitWithNullCheck(""); + is(input.value, "", + description + "doCommitWithNullCheck(\"\") should commit the composition with empty string"); + + // doCommitWithNullCheck(null) should commit the composition with empty string. + input.value = ""; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + doCommitWithNullCheck(null); + is(input.value, "", + description + "doCommitWithNullCheck(null) should commit the composition with empty string"); + + // doCommitWithNullCheck(undefined) should commit the composition with the last data. + input.value = ""; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + doCommitWithNullCheck(undefined); + is(input.value, "", + description + "doCommitWithNullCheck(undefined) should commit the composition with empty string"); +} + +function runUnloadTests1() +{ + return new Promise(resolve => { + let description = "runUnloadTests1(): "; + + let TIP1 = createTIP(); + ok(TIP1.beginInputTransactionForTests(childWindow), + description + "TIP1.beginInputTransactionForTests() should succeed"); + + let oldSrc = iframe.src; + let parentWindow = window; + + iframe.addEventListener("load", function (aEvent) { + ok(true, description + "dummy page is loaded"); + childWindow = iframe.contentWindow; + textareaInFrame = null; + iframe.addEventListener("load", function () { + ok(true, description + "old iframe is restored"); + // And also restore the iframe information with restored contents. + childWindow = iframe.contentWindow; + textareaInFrame = iframe.contentDocument.getElementById("textarea"); + SimpleTest.executeSoon(resolve); + }, {capture: true, once: true}); + + // The composition should be committed internally. So, another TIP should + // be able to steal the rights to using TextEventDispatcher. + let TIP2 = createTIP(); + ok(TIP2.beginInputTransactionForTests(parentWindow), + description + "TIP2.beginInputTransactionForTests() should succeed"); + + input.focus(); + input.value = ""; + + TIP2.setPendingCompositionString("foo"); + TIP2.appendClauseToPendingComposition(3, TIP2.ATTR_RAW_CLAUSE); + TIP2.setCaretInPendingComposition(3); + TIP2.flushPendingComposition(); + is(input.value, "foo", + description + "the input in the parent document should have composition string"); + + TIP2.cancelComposition(); + + // Restore the old iframe content. + iframe.src = oldSrc; + }, {capture: true, once: true}); + + // Start composition in the iframe. + textareaInFrame.value = ""; + textareaInFrame.focus(); + + TIP1.setPendingCompositionString("foo"); + TIP1.appendClauseToPendingComposition(3, TIP1.ATTR_RAW_CLAUSE); + TIP1.setCaretInPendingComposition(3); + TIP1.flushPendingComposition(); + is(textareaInFrame.value, "foo", + description + "the textarea in the iframe should have composition string"); + + // Load different web page on the frame. + iframe.src = "data:text/html,<body>dummy page</body>"; + }); +} + +function runUnloadTests2() +{ + return new Promise(resolve => { + let description = "runUnloadTests2(): "; + + let TIP = createTIP(); + ok(TIP.beginInputTransactionForTests(childWindow), + description + "TIP.beginInputTransactionForTests() should succeed"); + + let oldSrc = iframe.src; + + iframe.addEventListener("load", function (aEvent) { + ok(true, description + "dummy page is loaded"); + childWindow = iframe.contentWindow; + textareaInFrame = null; + iframe.addEventListener("load", function () { + ok(true, description + "old iframe is restored"); + // And also restore the iframe information with restored contents. + childWindow = iframe.contentWindow; + textareaInFrame = iframe.contentDocument.getElementById("textarea"); + SimpleTest.executeSoon(resolve); + }, {capture: true, once: true}); + + input.focus(); + input.value = ""; + + // TIP should be still available in the same top level widget. + TIP.setPendingCompositionString("bar"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + if (input.value == "") { + // XXX TextInputProcessor or TextEventDispatcher may have a bug. + todo_is(input.value, "bar", + description + "the input in the parent document should have composition string"); + } else { + is(input.value, "bar", + description + "the input in the parent document should have composition string"); + } + + TIP.cancelComposition(); + + // Restore the old iframe content. + iframe.src = oldSrc; + }, {capture: true, once: true}); + + // Start composition in the iframe. + textareaInFrame.value = ""; + textareaInFrame.focus(); + + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.setCaretInPendingComposition(3); + TIP.flushPendingComposition(); + is(textareaInFrame.value, "foo", + description + "the textarea in the iframe should have composition string"); + + // Load different web page on the frame. + iframe.src = "data:text/html,<body>dummy page</body>"; + }); +} + +async function runCallbackTests(aForTests) +{ + let description = "runCallbackTests(aForTests=" + aForTests + "): "; + + input.value = ""; + input.focus(); + input.blur(); + + let TIP = createTIP(); + let notifications = []; + let waitingNextNotification; + function callback(aTIP, aNotification) + { + if (aTIP == TIP) { + notifications.push(aNotification); + } + switch (aNotification.type) { + case "request-to-commit": + aTIP.commitComposition(); + break; + case "request-to-cancel": + aTIP.cancelComposition(); + break; + } + if (waitingNextNotification) { + SimpleTest.executeSoon(waitingNextNotification); + waitingNextNotification = undefined; + } + return true; + } + + function dumpUnexpectedNotifications(aExpectedCount) + { + if (notifications.length <= aExpectedCount) { + return; + } + for (let i = aExpectedCount; i < notifications.length; i++) { + ok(false, + description + "Unexpected notification: " + notifications[i].type); + } + } + + function waitUntilNotificationsReceived() + { + return new Promise(resolve => { + if (notifications.length) { + SimpleTest.executeSoon(resolve); + } else { + waitingNextNotification = resolve; + } + }); + } + + function checkPositionChangeNotification(aNotification, aDescription) + { + is(!aNotification || aNotification.type, "notify-position-change", + aDescription + " should cause position change notification"); + } + + function checkSelectionChangeNotification(aNotification, aDescription, aExpected) + { + is(aNotification.type, "notify-selection-change", + aDescription + " should cause selection change notification"); + if (aNotification.type != "notify-selection-change") { + return; + } + is(aNotification.hasRange, aExpected.hasRange !== false, + `${aDescription} should cause selection change notification whose hasRange is ${aExpected.hasRange}`); + if (aNotification.hasRange) { + is(aNotification.offset, aExpected.offset, + `${aDescription} should cause selection change notification whose offset is ${aExpected.offset}`); + is(aNotification.text, aExpected.text, + `${aDescription} should cause selection change notification whose text is "${aExpected.text}"`); + is(aNotification.length, aExpected.text.length, + `${aDescription} should cause selection change notification whose length is ${aExpected.text.length}`); + is(aNotification.reversed, aExpected.reversed || false, + `${aDescription} should cause selection change notification whose reversed is ${aExpected.reversed || false}`); + } + is(aNotification.collapsed, aExpected.hasRange === false || !aExpected.text.length, + `${aDescription} should cause selection change notification whose collapsed is ${aExpected.hasRange === false || !aExpected.text.length}`); + is(aNotification.writingMode, aExpected.writingMode || "horizontal-tb", + `${aDescription} should cause selection change notification whose writingMode is ${aExpected.writingMode || "horizontal-tb"}`); + is(aNotification.causedByComposition, aExpected.causedByComposition || false, + `${aDescription} should cause selection change notification whose causedByComposition is ${aExpected.causedByComposition || false}`); + is(aNotification.causedBySelectionEvent, aExpected.causedBySelectionEvent || false, + `${aDescription} should cause selection change notification whose causedBySelectionEvent is ${aExpected.causedBySelectionEvent || false}`); + is(aNotification.occurredDuringComposition, aExpected.occurredDuringComposition || false, + `${aDescription} should cause cause selection change notification whose occurredDuringComposition is ${aExpected.occurredDuringComposition || false}`); + } + + function checkTextChangeNotification(aNotification, aDescription, aExpected) + { + is(aNotification.type, "notify-text-change", + aDescription + " should cause text change notification"); + if (aNotification.type != "notify-text-change") { + return; + } + is(aNotification.offset, aExpected.offset, + aDescription + " should cause text change notification whose offset is " + aExpected.offset); + is(aNotification.removedLength, aExpected.removedLength, + aDescription + " should cause text change notification whose removedLength is " + aExpected.removedLength); + is(aNotification.addedLength, aExpected.addedLength, + aDescription + " should cause text change notification whose addedLength is " + aExpected.addedLength); + is(aNotification.causedOnlyByComposition, aExpected.causedOnlyByComposition || false, + aDescription + " should cause text change notification whose causedOnlyByComposition is " + (aExpected.causedOnlyByComposition || false)); + is(aNotification.includingChangesDuringComposition, aExpected.includingChangesDuringComposition || false, + aDescription + " should cause text change notification whose includingChangesDuringComposition is " + (aExpected.includingChangesDuringComposition || false)); + is(aNotification.includingChangesWithoutComposition, typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true, + aDescription + " should cause text change notification whose includingChangesWithoutComposition is " + (typeof aExpected.includingChangesWithoutComposition === "boolean" ? aExpected.includingChangesWithoutComposition : true)); + } + + if (aForTests) { + TIP.beginInputTransactionForTests(window, callback); + } else { + TIP.beginInputTransaction(window, callback); + } + + notifications = []; + input.focus(); + is(notifications.length, 1, + description + "input.focus() should cause a notification"); + is(notifications[0].type, "notify-focus", + description + "input.focus() should cause \"notify-focus\""); + dumpUnexpectedNotifications(1); + + notifications = []; + input.blur(); + is(notifications.length, 1, + description + "input.blur() should cause a notification"); + is(notifications[0].type, "notify-blur", + description + "input.blur() should cause \"notify-focus\""); + dumpUnexpectedNotifications(1); + + input.focus(); + await waitUntilNotificationsReceived(); + notifications = []; + TIP.setPendingCompositionString("foo"); + TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE); + TIP.flushPendingComposition(); + is(notifications.length, 3, + description + "creating composition string 'foo' should cause 3 notifications"); + checkTextChangeNotification(notifications[0], description + "creating composition string 'foo'", + { offset: 0, removedLength: 0, addedLength: 3, + causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false}); + checkSelectionChangeNotification(notifications[1], description + "creating composition string 'foo'", + { offset: 3, text: "", causedByComposition: true, occurredDuringComposition: true }); + checkPositionChangeNotification(notifications[2], description + "creating composition string 'foo'"); + dumpUnexpectedNotifications(3); + + notifications = []; + synthesizeMouseAtCenter(input, {}); + is(notifications.length, 3, + description + "synthesizeMouseAtCenter(input, {}) during composition should cause 3 notifications"); + is(notifications[0].type, "request-to-commit", + description + "synthesizeMouseAtCenter(input, {}) during composition should cause \"request-to-commit\""); + checkTextChangeNotification(notifications[1], description + "synthesizeMouseAtCenter(input, {}) during composition", + { offset: 0, removedLength: 3, addedLength: 3, + causedOnlyByComposition: true, includingChangesDuringComposition: false, includingChangesWithoutComposition: false}); + checkPositionChangeNotification(notifications[2], description + "synthesizeMouseAtCenter(input, {}) during composition"); + dumpUnexpectedNotifications(3); + + input.focus(); + await waitUntilNotificationsReceived(); + notifications = []; + // XXX On macOS, window.moveBy() doesn't cause notify-position-change. + // Investigate this later (although, we cannot notify position change to + // native IME on macOS). + // Wayland also does not support it. + var isWayland = Services.prefs.getBoolPref("widget.wayland.test-workarounds.enabled", false); + if (!kIsMac && !isWayland) { + window.moveBy(0, 10); + await waitUntilNotificationsReceived(); + is(notifications.length, 1, + description + "window.moveBy(0, 10) should cause a notification"); + checkPositionChangeNotification(notifications[0], description + "window.moveBy(0, 10)"); + dumpUnexpectedNotifications(1); + + notifications = []; + window.moveBy(10, 0); + await waitUntilNotificationsReceived(); + is(notifications.length, 1, + description + "window.moveBy(10, 0) should cause a notification"); + checkPositionChangeNotification(notifications[0], description + "window.moveBy(10, 0)"); + dumpUnexpectedNotifications(1); + } + + input.focus(); + input.value = "abc" + notifications = []; + input.selectionStart = input.selectionEnd = 0; + await waitUntilNotificationsReceived(); + notifications = []; + let rightArrowKeyEvent = + new KeyboardEvent("", { key: "ArrowRight", code: "ArrowRight", keyCode: KeyboardEvent.DOM_VK_RIGHT }); + TIP.keydown(rightArrowKeyEvent); + TIP.keyup(rightArrowKeyEvent); + is(notifications.length, 1, + description + "ArrowRight key press should cause a notification"); + checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press", { offset: 1, text: "" }); + dumpUnexpectedNotifications(1); + + notifications = []; + let shiftKeyEvent = + new KeyboardEvent("", { key: "Shift", code: "ShiftLeft", keyCode: KeyboardEvent.DOM_VK_SHIFT }); + let leftArrowKeyEvent = + new KeyboardEvent("", { key: "ArrowLeft", code: "ArrowLeft", keyCode: KeyboardEvent.DOM_VK_LEFT }); + TIP.keydown(shiftKeyEvent); + TIP.keydown(leftArrowKeyEvent); + TIP.keyup(leftArrowKeyEvent); + TIP.keyup(shiftKeyEvent); + is(notifications.length, 1, + description + "ArrowLeft key press with Shift should cause a notification"); + checkSelectionChangeNotification(notifications[0], description + "ArrowLeft key press with Shift", { offset: 0, text: "a", reversed: true }); + dumpUnexpectedNotifications(1); + + TIP.keydown(rightArrowKeyEvent); + TIP.keyup(rightArrowKeyEvent); + notifications = []; + TIP.keydown(shiftKeyEvent); + TIP.keydown(rightArrowKeyEvent); + TIP.keyup(rightArrowKeyEvent); + TIP.keyup(shiftKeyEvent); + is(notifications.length, 1, + description + "ArrowRight key press with Shift should cause a notification"); + checkSelectionChangeNotification(notifications[0], description + "ArrowRight key press with Shift", { offset: 1, text: "b" }); + dumpUnexpectedNotifications(1); + + notifications = []; + input.editor.selection.removeAllRanges(); + await waitUntilNotificationsReceived(); + is(notifications.length, 1, + `${description}Removing all selection ranges should cause a selection change notification`); + checkSelectionChangeNotification( + notifications[0], + `${description}Removing all selection ranges in editor`, + { hasRange: false } + ); + dumpUnexpectedNotifications(1); + + notifications = []; + let TIP2 = createTIP(); + if (aForTests) { + TIP2.beginInputTransactionForTests(window, callback); + } else { + TIP2.beginInputTransaction(window, callback); + } + is(notifications.length, 1, + description + "Initializing another TIP should cause a notification"); + is(notifications[0].type, "notify-end-input-transaction", + description + "Initializing another TIP should cause \"notify-detached\""); + dumpUnexpectedNotifications(1); +} + +async function runFocusNotificationTestAfterDrop() { + const inputs = document.querySelectorAll("input[type=text]"); + inputs[0].value = "abc"; + inputs[1].value = ""; + + const TIP = createTIP(); + let notifications = []; + function callback(aTIP, aNotification) + { + if (aTIP != TIP) { + return true; + } + switch (aNotification.type) { + case "request-to-commit": + aTIP.commitComposition(); + break; + case "request-to-cancel": + aTIP.cancelComposition(); + break; + case "notify-focus": + case "notify-blur": + notifications.push(aNotification.type); + break; + } + return true; + } + + inputs[0].focus(); + TIP.beginInputTransactionForTests(window, callback); + inputs[0].select(); + try { + notifications = []; + await synthesizePlainDragAndDrop({ + srcSelection: SpecialPowers.wrap(inputs[0]).editor.selection, + destElement: inputs[1], + }); + } catch (ex) { + ok(false, `runFocusNotificationTestAfterDrop: unexpected error during DnD (${ex.message})`); + return; + } + is( + document.activeElement, + inputs[1], + "runFocusNotificationTestAfterDrop: Dropping to the second <input> should make it focused" + ); + ok( + notifications.length > 1, + "runFocusNotificationTestAfterDrop: At least two notifications should be fired" + ); + if (notifications.length) { + is( + notifications[notifications.length - 1], + "notify-focus", + "runFocusNotificationTestAfterDrop: focus notification should've been fired at last" + ); + } +} + +async function runQuerySelectionEventTestAtTextChangeNotification() { + contenteditable.innerHTML = "<p>abc</p><p>def</p>"; + contenteditable.focus(); + // Ensure to send notify-focus from IMEContentObserver + await new Promise( + resolve => requestAnimationFrame( + () => requestAnimationFrame(resolve) + ) + ); + document.execCommand("selectall"); + // Ensure to send notify-selection-change from IMEContentObserver + await new Promise( + resolve => requestAnimationFrame( + () => requestAnimationFrame(resolve) + ) + ); + + const kTestName = "runQuerySelectionEventTestAtTextChangeNotification"; + await new Promise(resolve => { + const TIP = createTIP(); + TIP.beginInputTransactionForTests(window, (aTIP, aNotification) => { + if (aTIP != TIP) { + return true; + } + switch (aNotification.type) { + case "request-to-commit": + aTIP.commitComposition(); + break; + case "request-to-cancel": + aTIP.cancelComposition(); + break; + case "notify-text-change": + const textContent = synthesizeQueryTextContent(0, 100); + if (textContent?.text.includes("abc")) { + break; // Different notification which we want to test, wait next one. + } + ok( + textContent?.succeeded, + `${kTestName}: query text content should succeed from notify-text-change handler` + ); + const selectedText = synthesizeQuerySelectedText(); + ok( + selectedText?.succeeded, + `${kTestName}: query selected text should succeed from notify-text-change handler` + ); + if (textContent?.succeeded && selectedText?.succeeded) { + is( + selectedText.text, + textContent.text, + `${kTestName}: selected text should be same as all text` + ); + } + resolve(); + break; + } + return true; + }); + // TODO: We want to do this while selection is batched but can flush + // pending notifications, however, I have no idea how to do it. + contenteditable.firstChild.remove(); + }); +} + +async function runIMEStateUpdateTests() { + const TIP = createTIP(); + let notifications = []; + function callback(aTIP, aNotification) + { + if (aTIP != TIP) { + return true; + } + switch (aNotification.type) { + case "request-to-commit": + aTIP.commitComposition(); + break; + case "request-to-cancel": + aTIP.cancelComposition(); + break; + case "notify-focus": + case "notify-blur": + notifications.push(aNotification.type); + break; + } + return true; + } + + contenteditable.focus(); + TIP.beginInputTransactionForTests(window, callback); + await new Promise(resolve => requestAnimationFrame(() => + requestAnimationFrame(resolve) + )); // wait for flushing pending notifications if there is. + + // run IMEStateManager::UpdateIMEState to disable IME + notifications = []; + const editor = getHTMLEditor(window); + editor.flags |= Ci.nsIEditor.eEditorReadonlyMask; + await new Promise(resolve => requestAnimationFrame(() => + requestAnimationFrame(resolve) + )); // wait for flush pending notification even if handled asynchronously. + is( + notifications.length ? notifications[0] : undefined, + "notify-blur", + "runIMEStateUpdateTests: Making the HTMLEditor readonly should cause a blur notification" + ); + is( + notifications.length, + 1, + `runIMEStateUpdateTests: Making the HTMLEditor readonly should not cause any other notifications, but got ${ + notifications.length > 1 ? notifications[1] : "" + } notification` + ); + is( + SpecialPowers.getDOMWindowUtils(window)?.IMEStatus, + Ci.nsIDOMWindowUtils.IME_STATUS_DISABLED, + `runIMEStateUpdateTests: Making the HTMLEditor readonly should make IME disabled` + ); + + // run IMEStateManager::UpdateIMEState to enable IME + notifications = []; + editor.flags &= ~Ci.nsIEditor.eEditorReadonlyMask; + await new Promise(resolve => requestAnimationFrame(() => + requestAnimationFrame(resolve) + )); // wait for flush pending notification even if handled asynchronously. + is( + notifications.length ? notifications[0] : undefined, + "notify-focus", + "runIMEStateUpdateTests: Making the HTMLEditor editable should cause a focus notification without blur notification" + ); + is( + notifications.length, + 1, + `runIMEStateUpdateTests: Making the HTMLEditor editable should not cause any other notifications, but got ${ + notifications.length > 1 ? notifications[1] : "" + } notification` + ); + is( + SpecialPowers.getDOMWindowUtils(window)?.IMEStatus, + Ci.nsIDOMWindowUtils.IME_STATUS_ENABLED, + `runIMEStateUpdateTests: Making the HTMLEditor readonly should make IME disabled` + ); +} + +async function runTextNotificationChangesDuringNoFrame() { + const TIP = createTIP(); + let onTextChange; + function callback(aTIP, aNotification) + { + if (aTIP != TIP) { + return true; + } + switch (aNotification.type) { + case "request-to-commit": + aTIP.commitComposition(); + break; + case "request-to-cancel": + aTIP.cancelComposition(); + break; + case "notify-text-change": + if (onTextChange) { + onTextChange(aNotification); + } + break; + } + return true; + } + + function promiseTextChangeNotification() { + return new Promise(resolve => onTextChange = resolve); + } + + function waitForTick() { + return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); + } + + input = document.querySelector("input[type=text]"); + input.focus(); + TIP.beginInputTransactionForTests(window, callback); + + await (async function test_text_change_notification_for_value_set_during_no_frame() { + const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_value_set_during_no_frame"; + input.value = "Start"; + input.style.display = "inline"; + input.getBoundingClientRect(); + await waitForTick(); + const waitNotifications = promiseTextChangeNotification(); + input.style.display = "block"; + input.value = "Changed"; + info(`${description}: waiting for notifications...`); + const notification = await waitNotifications; + is( + notification?.offset, + 0, + `${description}: offset should be 0` + ); + is( + notification?.removedLength, + "Start".length, + `${description}: removedLength should be the length of the old value` + ); + is( + notification?.addedLength, + "Changed".length, + `${description}: addedLength should be the length of the new value` + ); + })(); + + await (async function test_text_change_notification_for_multiple_value_set_during_no_frame() { + const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_multiple_value_set_during_no_frame"; + input.value = "Start"; + input.style.display = "inline"; + input.getBoundingClientRect(); + await waitForTick(); + const waitNotifications = promiseTextChangeNotification(); + input.style.display = "block"; + input.value = "Changed"; + input.value = "Again!"; + info(`${description}: waiting for notifications...`); + const notification = await waitNotifications; + is( + notification?.offset, + 0, + `${description}: offset should be 0` + ); + is( + notification?.removedLength, + "Start".length, + `${description}: removedLength should be the length of the old value` + ); + is( + notification?.addedLength, + "Again!".length, + `${description}: addedLength should be the length of the new value` + ); + })(); + + await (async function test_text_change_notification_for_value_set_and_typing_character_during_no_frame() { + const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_value_set_and_typing_character_during_no_frame"; + input.value = "Start"; + input.style.display = "inline"; + input.getBoundingClientRect(); + await waitForTick(); + const waitNotifications = promiseTextChangeNotification(); + input.style.display = "block"; + input.value = "Change"; + const dKey = new KeyboardEvent("", { code: "KeyD", key: "d", keyCode: KeyboardEvent.DOM_VK_D }); + TIP.keydown(dKey); + TIP.keyup(dKey); + info(`${description}: waiting for notifications...`); + const notification = await waitNotifications; + is( + notification?.offset, + 0, + `${description}: offset should be 0` + ); + is( + notification?.removedLength, + "Start".length, + `${description}: removedLength should be the length of the old value` + ); + is( + notification?.addedLength, + "Change".length, + `${description}: addedLength should be the length of the new (set) value` + ); + })(); + + input.style.display = ""; + + textarea.focus(); + TIP.beginInputTransaction(window, callback); + + await (async function test_text_change_notification_for_multi_line_value_set_during_no_frame() { + const description = "runTextNotificationChangesDuringNoFrame: test_text_change_notification_for_multi_line_value_set_during_no_frame"; + textarea.value = "Start\n2nd Line"; + textarea.style.display = "inline"; + textarea.getBoundingClientRect(); + await waitForTick(); + const waitNotifications = promiseTextChangeNotification(); + textarea.style.display = "block"; + textarea.value = "Changed\n2nd Line"; + info(`${description}: waiting for notifications...`); + const notification = await waitNotifications; + is( + notification?.offset, + 0, + `${description}: offset should be 0` + ); + is( + notification?.removedLength, + getNativeText("Start\n2nd Line").length, + `${description}: removedLength should be the length of the old value` + ); + is( + notification?.addedLength, + getNativeText("Changed\n2nd Line").length, + `${description}: addedLength should be the length of the new value` + ); + })(); + + textarea.style.display = ""; +} + +async function runTests() +{ + textareaInFrame = iframe.contentDocument.getElementById("textarea"); + runBeginInputTransactionMethodTests(); + runReleaseTests(); + runCompositionTests(); + runCompositionWithKeyEventTests(); + runConsumingKeydownBeforeCompositionTests(); + await runKeyTests(); + runErrorTests(); + runCommitCompositionTests(); + await runCallbackTests(false); + await runCallbackTests(true); + await runTextNotificationChangesDuringNoFrame(); + await runFocusNotificationTestAfterDrop(); + await runUnloadTests1(); + await runUnloadTests2(); + await runQuerySelectionEventTestAtTextChangeNotification(); + await runIMEStateUpdateTests(); + + finish(); +} + +]]> +</script> + +</window> diff --git a/dom/base/test/chrome/window_swapFrameLoaders.xhtml b/dom/base/test/chrome/window_swapFrameLoaders.xhtml new file mode 100644 index 0000000000..4a38bcc1fc --- /dev/null +++ b/dom/base/test/chrome/window_swapFrameLoaders.xhtml @@ -0,0 +1,223 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1242644 +Test swapFrameLoaders with different frame types and remoteness +--> +<window title="Mozilla Bug 1242644" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript"><![CDATA[ + ["SimpleTest", "SpecialPowers", "info", "is", "ok", "add_task"].forEach(key => { + window[key] = window.arguments[0][key]; + }) + + const NS = { + xul: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", + html: "http://www.w3.org/1999/xhtml", + } + + const TAG = { + xul: "browser", + html: "iframe", // mozbrowser + } + + const SCENARIOS = [ + ["xul", "xul"], + ["xul", "html"], + ["html", "xul"], + ["html", "html"], + ["xul", "xul", { remote: true }], + ["xul", "html", { remote: true }], + ["html", "xul", { remote: true }], + ["html", "html", { remote: true }], + ["xul", "html", { userContextId: 2 }], + ["xul", "html", { userContextId: 2, remote: true }], + ]; + + const HEIGHTS = [ + 200, + 400 + ]; + + function frameScript() { + /* eslint-env mozilla/frame-script */ + addEventListener("load", function onLoad() { + sendAsyncMessage("test:load"); + }, true); + } + + // Watch for loads in new frames + window.messageManager.loadFrameScript(`data:,(${frameScript})();`, true); + + function once(target, eventName, useCapture = false) { + info("Waiting for event: '" + eventName + "' on " + target + "."); + + return new Promise(resolve => { + for (let [add, remove] of [ + ["addEventListener", "removeEventListener"], + ["addMessageListener", "removeMessageListener"], + ]) { + if ((add in target) && (remove in target)) { + target[add](eventName, function onEvent(...aArgs) { + info("Got event: '" + eventName + "' on " + target + "."); + target[remove](eventName, onEvent, useCapture); + resolve(aArgs); + }, useCapture); + break; + } + } + }); + } + + async function addFrame(type, options, height) { + let remote = options && options.remote; + let userContextId = options && options.userContextId; + let frame = document.createElementNS(NS[type], TAG[type]); + frame.setAttribute("remote", remote); + if (remote && type == "xul") { + frame.setAttribute("style", "-moz-binding: none;"); + } + if (userContextId) { + frame.setAttribute("usercontextid", userContextId); + } + if (type == "html") { + frame.setAttribute("mozbrowser", "true"); + frame.setAttribute("noisolation", "true"); + frame.setAttribute("allowfullscreen", "true"); + } else if (type == "xul") { + frame.setAttribute("type", "content"); + } + let src = `data:text/html,<!doctype html>` + + `<body style="height:${height}px"/>`; + frame.setAttribute("src", src); + document.documentElement.appendChild(frame); + let mm = frame.frameLoader.messageManager; + await once(mm, "test:load"); + return frame; + } + + add_task(async function() { + for (let scenario of SCENARIOS) { + let [ typeA, typeB, options ] = scenario; + let heightA = HEIGHTS[0]; + info(`Adding frame A, type ${typeA}, options ${JSON.stringify(options)}, height ${heightA}`); + let frameA = await addFrame(typeA, options, heightA); + + let heightB = HEIGHTS[1]; + info(`Adding frame B, type ${typeB}, options ${JSON.stringify(options)}, height ${heightB}`); + let frameB = await addFrame(typeB, options, heightB); + + let frameScriptFactory = function(name) { + /* eslint-env mozilla/frame-script */ + return `function() { + addMessageListener("ping", function() { + sendAsyncMessage("pong", "${name}"); + }); + addMessageListener("check-browser-api", function() { + let exists = "api" in this; + sendAsyncMessage("check-browser-api", { + exists, + running: exists && !this.api._shuttingDown, + }); + }); + addEventListener("pagehide", function({ inFrameSwap }) { + sendAsyncMessage("pagehide", inFrameSwap); + }, {mozSystemGroup: true}); + }`; + } + + // Load frame script into each frame + { + let mmA = frameA.frameLoader.messageManager; + let mmB = frameB.frameLoader.messageManager; + + mmA.loadFrameScript("data:,(" + frameScriptFactory("A") + ")()", false); + mmB.loadFrameScript("data:,(" + frameScriptFactory("B") + ")()", false); + } + + // Ping before swap + { + let mmA = frameA.frameLoader.messageManager; + let mmB = frameB.frameLoader.messageManager; + + let inflightA = once(mmA, "pong"); + let inflightB = once(mmB, "pong"); + + info("Ping message manager for frame A"); + mmA.sendAsyncMessage("ping"); + let [ { data: pongA } ] = await inflightA; + is(pongA, "A", "Frame A message manager gets reply A before swap"); + + info("Ping message manager for frame B"); + mmB.sendAsyncMessage("ping"); + let [ { data: pongB } ] = await inflightB; + is(pongB, "B", "Frame B message manager gets reply B before swap"); + } + + // Ping after swap using message managers acquired before + { + let mmA = frameA.frameLoader.messageManager; + let mmB = frameB.frameLoader.messageManager; + + let pagehideA = once(mmA, "pagehide"); + let pagehideB = once(mmB, "pagehide"); + + info("swapFrameLoaders"); + frameA.swapFrameLoaders(frameB); + + let [ { data: inFrameSwapA } ] = await pagehideA; + ok(inFrameSwapA, "Frame A got pagehide with inFrameSwap: true"); + let [ { data: inFrameSwapB } ] = await pagehideB; + ok(inFrameSwapB, "Frame B got pagehide with inFrameSwap: true"); + + let inflightA = once(mmA, "pong"); + let inflightB = once(mmB, "pong"); + + info("Ping message manager for frame A"); + mmA.sendAsyncMessage("ping"); + let [ { data: pongA } ] = await inflightA; + is(pongA, "B", "Frame A message manager acquired before swap gets reply B after swap"); + + info("Ping message manager for frame B"); + mmB.sendAsyncMessage("ping"); + let [ { data: pongB } ] = await inflightB; + is(pongB, "A", "Frame B message manager acquired before swap gets reply A after swap"); + } + + // Check height after swap + if (frameA.getContentDimensions) { + let { height } = await frameA.getContentDimensions(); + is(height, heightB, "Frame A's content height is 400px after swap"); + } + if (frameB.getContentDimensions) { + let { height } = await frameB.getContentDimensions(); + is(height, heightA, "Frame B's content height is 200px after swap"); + } + + // Ping after swap using message managers acquired after + { + let mmA = frameA.frameLoader.messageManager; + let mmB = frameB.frameLoader.messageManager; + + let inflightA = once(mmA, "pong"); + let inflightB = once(mmB, "pong"); + + info("Ping message manager for frame A"); + mmA.sendAsyncMessage("ping"); + let [ { data: pongA } ] = await inflightA; + is(pongA, "B", "Frame A message manager acquired after swap gets reply B after swap"); + + info("Ping message manager for frame B"); + mmB.sendAsyncMessage("ping"); + let [ { data: pongB } ] = await inflightB; + is(pongB, "A", "Frame B message manager acquired after swap gets reply A after swap"); + } + + frameA.remove(); + frameB.remove(); + } + }); + ]]></script> +</window> |