summaryrefslogtreecommitdiffstats
path: root/dom/base/test/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/test/chrome')
-rw-r--r--dom/base/test/chrome/bug418986-1.js88
-rw-r--r--dom/base/test/chrome/bug421622-referer.sjs9
-rw-r--r--dom/base/test/chrome/bug884693.sjs8
-rw-r--r--dom/base/test/chrome/chrome.toml135
-rw-r--r--dom/base/test/chrome/clonedoc/chrome.manifest1
-rw-r--r--dom/base/test/chrome/clonedoc/content/doc.xml4
-rw-r--r--dom/base/test/chrome/custom_element_ep.js14
-rw-r--r--dom/base/test/chrome/file_bug1139964.xhtml60
-rw-r--r--dom/base/test/chrome/file_bug1209621.xhtml85
-rw-r--r--dom/base/test/chrome/file_bug549682.xhtml214
-rw-r--r--dom/base/test/chrome/file_bug616841.xhtml63
-rw-r--r--dom/base/test/chrome/file_bug816340.xhtml69
-rw-r--r--dom/base/test/chrome/file_bug990812-1.xhtml61
-rw-r--r--dom/base/test/chrome/file_bug990812-2.xhtml56
-rw-r--r--dom/base/test/chrome/file_bug990812-3.xhtml68
-rw-r--r--dom/base/test/chrome/file_bug990812-4.xhtml63
-rw-r--r--dom/base/test/chrome/file_bug990812-5.xhtml74
-rw-r--r--dom/base/test/chrome/file_bug990812.xhtml55
-rw-r--r--dom/base/test/chrome/file_document-element-inserted-inner.xhtml1
-rw-r--r--dom/base/test/chrome/file_document-element-inserted.xhtml3
-rw-r--r--dom/base/test/chrome/file_title.xhtml1
-rw-r--r--dom/base/test/chrome/fileconstructor_file.pngbin0 -> 95 bytes
-rw-r--r--dom/base/test/chrome/frame_custom_element_content.html5
-rw-r--r--dom/base/test/chrome/nochrome_bug1346936.html3
-rw-r--r--dom/base/test/chrome/nochrome_bug1346936.js4
-rw-r--r--dom/base/test/chrome/nochrome_bug1346936.js^headers^1
-rw-r--r--dom/base/test/chrome/nochrome_bug765993.html3
-rw-r--r--dom/base/test/chrome/nochrome_bug765993.js4
-rw-r--r--dom/base/test/chrome/nochrome_bug765993.js^headers^1
-rw-r--r--dom/base/test/chrome/test_bug1063837.xhtml36
-rw-r--r--dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xhtml47
-rw-r--r--dom/base/test/chrome/test_bug1139964.xhtml32
-rw-r--r--dom/base/test/chrome/test_bug120684.xhtml80
-rw-r--r--dom/base/test/chrome/test_bug1209621.xhtml34
-rw-r--r--dom/base/test/chrome/test_bug1339722.html86
-rw-r--r--dom/base/test/chrome/test_bug1346936.html61
-rw-r--r--dom/base/test/chrome/test_bug206691.xhtml32
-rw-r--r--dom/base/test/chrome/test_bug289714.xhtml33
-rw-r--r--dom/base/test/chrome/test_bug339494.xhtml73
-rw-r--r--dom/base/test/chrome/test_bug357450.xhtml56
-rw-r--r--dom/base/test/chrome/test_bug380418.html37
-rw-r--r--dom/base/test/chrome/test_bug380418.html^headers^4
-rw-r--r--dom/base/test/chrome/test_bug383430.html38
-rw-r--r--dom/base/test/chrome/test_bug418986-1.xhtml25
-rw-r--r--dom/base/test/chrome/test_bug421622.xhtml34
-rw-r--r--dom/base/test/chrome/test_bug429785.xhtml53
-rw-r--r--dom/base/test/chrome/test_bug430050.xhtml48
-rw-r--r--dom/base/test/chrome/test_bug467123.xhtml42
-rw-r--r--dom/base/test/chrome/test_bug473284.xhtml83
-rw-r--r--dom/base/test/chrome/test_bug549682.xhtml32
-rw-r--r--dom/base/test/chrome/test_bug571390.xhtml42
-rw-r--r--dom/base/test/chrome/test_bug616841.xhtml30
-rw-r--r--dom/base/test/chrome/test_bug635835.xhtml36
-rw-r--r--dom/base/test/chrome/test_bug682305.html150
-rw-r--r--dom/base/test/chrome/test_bug683852.xhtml87
-rw-r--r--dom/base/test/chrome/test_bug752226-3.xhtml28
-rw-r--r--dom/base/test/chrome/test_bug752226-4.xhtml28
-rw-r--r--dom/base/test/chrome/test_bug765993.html61
-rw-r--r--dom/base/test/chrome/test_bug780199.xhtml51
-rw-r--r--dom/base/test/chrome/test_bug780529.xhtml36
-rw-r--r--dom/base/test/chrome/test_bug800386.xhtml65
-rw-r--r--dom/base/test/chrome/test_bug816340.xhtml32
-rw-r--r--dom/base/test/chrome/test_bug884693.xhtml79
-rw-r--r--dom/base/test/chrome/test_bug914381.html58
-rw-r--r--dom/base/test/chrome/test_bug990812.xhtml42
-rw-r--r--dom/base/test/chrome/test_chromeOuterWindowID.xhtml138
-rw-r--r--dom/base/test/chrome/test_custom_element_content.xhtml55
-rw-r--r--dom/base/test/chrome/test_custom_element_ep.xhtml41
-rw-r--r--dom/base/test/chrome/test_document-element-inserted.xhtml54
-rw-r--r--dom/base/test/chrome/test_domparsing.xhtml145
-rw-r--r--dom/base/test/chrome/test_fileconstructor.xhtml86
-rw-r--r--dom/base/test/chrome/test_getElementsWithGrid.html121
-rw-r--r--dom/base/test/chrome/test_input_value_set_preserve_undo.xhtml37
-rw-r--r--dom/base/test/chrome/test_nsITextInputProcessor.xhtml29
-rw-r--r--dom/base/test/chrome/test_nsITextInputProcessorCallback_at_changing_default_value_of_textarea.html107
-rw-r--r--dom/base/test/chrome/test_permission_hasValidTransientUserActivation.xhtml93
-rw-r--r--dom/base/test/chrome/test_range_getClientRectsAndTexts.html74
-rw-r--r--dom/base/test/chrome/test_swapFrameLoaders.xhtml25
-rw-r--r--dom/base/test/chrome/test_title.xhtml29
-rw-r--r--dom/base/test/chrome/test_windowroot.xhtml18
-rw-r--r--dom/base/test/chrome/title_window.xhtml197
-rw-r--r--dom/base/test/chrome/window_chromeOuterWindowID.xhtml14
-rw-r--r--dom/base/test/chrome/window_nsITextInputProcessor.xhtml4874
-rw-r--r--dom/base/test/chrome/window_swapFrameLoaders.xhtml223
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
new file mode 100644
index 0000000000..51e8aaf38c
--- /dev/null
+++ b/dom/base/test/chrome/fileconstructor_file.png
Binary files differ
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&shy;b</div>
+
+<div id="hyphen2" style="width:100px">c&shy;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,&lt;html&gt;&lt;head&gt;&lt;title id='t'&gt;Test&lt;/title&gt;&lt;/head&gt;&lt;/html&gt;"/>
+ <iframe type="content" id="html2" src="data:text/html,&lt;html&gt;&lt;head&gt;&lt;title id='t'&gt;Test&lt;/title&gt;&lt;title&gt;Foo&lt;/title&gt;&lt;/head&gt;&lt;/html&gt;"/>
+ <iframe type="content" id="html3" src="data:text/html,&lt;html&gt;&lt;/html&gt;"/>
+ <iframe type="content" id="xhtml1" src="data:text/xml,&lt;html xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;body&gt;&lt;title id='t'&gt;Test&lt;/title&gt;&lt;/body&gt;&lt;/html&gt;"/>
+ <iframe type="content" id="xhtml2" src="data:text/xml,&lt;title xmlns='http://www.w3.org/1999/xhtml'&gt;Test&lt;/title&gt;"/>
+ <iframe type="content" id="xhtml3" src="data:text/xml,&lt;title xmlns='http://www.w3.org/1999/xhtml'&gt;Te&lt;div&gt;bogus&lt;/div&gt;st&lt;/title&gt;"/>
+ <iframe type="content" id="xhtml4" src="data:text/xml,&lt;html xmlns='http://www.w3.org/1999/xhtml'/>"/>
+ <iframe type="content" id="xhtml5" src="data:text/xml,&lt;html xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;head/>&lt;/html&gt;"/>
+ <iframe type="content" id="xhtml6" src="data:text/xml,&lt;html xmlns='http://www.w3.org/1999/xhtml'&gt;&lt;head&gt;&lt;style/>&lt;/head&gt;&lt;/html&gt;"/>
+ <iframe id="xul1" src="file_title.xhtml"/>
+ <iframe id="xul2" src="file_title.xhtml"/>
+ <iframe type="content" id="svg1" src="data:text/xml,&lt;svg xmlns='http://www.w3.org/2000/svg'&gt;&lt;title id='t'&gt;Test&lt;/title&gt;&lt;/svg&gt;"/>
+ <iframe type="content" id="svg2" src="data:text/xml,&lt;svg xmlns='http://www.w3.org/2000/svg'&gt;&lt;title id='t'&gt;Test&lt;/title&gt;&lt;/svg&gt;"/>
+
+ <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,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></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>