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/blockNoPlugins.xml7
-rw-r--r--dom/base/test/chrome/blockPluginHard.xml11
-rw-r--r--dom/base/test/chrome/bug418986-1.js73
-rw-r--r--dom/base/test/chrome/bug421622-referer.sjs8
-rw-r--r--dom/base/test/chrome/bug884693.sjs8
-rw-r--r--dom/base/test/chrome/chrome.ini76
-rw-r--r--dom/base/test/chrome/cpows_child.js382
-rw-r--r--dom/base/test/chrome/cpows_parent.xul493
-rw-r--r--dom/base/test/chrome/file_bug1139964.xul62
-rw-r--r--dom/base/test/chrome/file_bug1209621.xul79
-rw-r--r--dom/base/test/chrome/file_bug549682.xul226
-rw-r--r--dom/base/test/chrome/file_bug616841.xul63
-rw-r--r--dom/base/test/chrome/file_bug816340.xul70
-rw-r--r--dom/base/test/chrome/file_bug990812-1.xul64
-rw-r--r--dom/base/test/chrome/file_bug990812-2.xul59
-rw-r--r--dom/base/test/chrome/file_bug990812-3.xul71
-rw-r--r--dom/base/test/chrome/file_bug990812-4.xul68
-rw-r--r--dom/base/test/chrome/file_bug990812-5.xul77
-rw-r--r--dom/base/test/chrome/file_bug990812.xul58
-rw-r--r--dom/base/test/chrome/fileconstructor_file.pngbin0 -> 95 bytes
-rw-r--r--dom/base/test/chrome/frame_bug814638.xul15
-rw-r--r--dom/base/test/chrome/frame_registerElement_content.html5
-rw-r--r--dom/base/test/chrome/host_bug814638.xul9
-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/registerElement_ep.js8
-rw-r--r--dom/base/test/chrome/test_bug1063837.xul37
-rw-r--r--dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xul50
-rw-r--r--dom/base/test/chrome/test_bug1139964.xul33
-rw-r--r--dom/base/test/chrome/test_bug120684.xul80
-rw-r--r--dom/base/test/chrome/test_bug1209621.xul34
-rw-r--r--dom/base/test/chrome/test_bug1339722.html67
-rw-r--r--dom/base/test/chrome/test_bug206691.xul33
-rw-r--r--dom/base/test/chrome/test_bug289714.xul33
-rw-r--r--dom/base/test/chrome/test_bug339494.xul64
-rw-r--r--dom/base/test/chrome/test_bug357450.xul56
-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.xul26
-rw-r--r--dom/base/test/chrome/test_bug421622.xul35
-rw-r--r--dom/base/test/chrome/test_bug429785.xul61
-rw-r--r--dom/base/test/chrome/test_bug430050.xul50
-rw-r--r--dom/base/test/chrome/test_bug467123.xul35
-rw-r--r--dom/base/test/chrome/test_bug549682.xul33
-rw-r--r--dom/base/test/chrome/test_bug571390.xul42
-rw-r--r--dom/base/test/chrome/test_bug616841.xul31
-rw-r--r--dom/base/test/chrome/test_bug635835.xul37
-rw-r--r--dom/base/test/chrome/test_bug682305.html175
-rw-r--r--dom/base/test/chrome/test_bug683852.xul69
-rw-r--r--dom/base/test/chrome/test_bug752226-3.xul28
-rw-r--r--dom/base/test/chrome/test_bug752226-4.xul28
-rw-r--r--dom/base/test/chrome/test_bug765993.html61
-rw-r--r--dom/base/test/chrome/test_bug780199.xul51
-rw-r--r--dom/base/test/chrome/test_bug780529.xul40
-rw-r--r--dom/base/test/chrome/test_bug800386.xul68
-rw-r--r--dom/base/test/chrome/test_bug814638.xul64
-rw-r--r--dom/base/test/chrome/test_bug816340.xul31
-rw-r--r--dom/base/test/chrome/test_bug884693.xul67
-rw-r--r--dom/base/test/chrome/test_bug914381.html48
-rw-r--r--dom/base/test/chrome/test_bug990812.xul43
-rw-r--r--dom/base/test/chrome/test_cpows.xul33
-rw-r--r--dom/base/test/chrome/test_domparsing.xul144
-rw-r--r--dom/base/test/chrome/test_fileconstructor.xul72
-rw-r--r--dom/base/test/chrome/test_fileconstructor_tempfile.xul93
-rw-r--r--dom/base/test/chrome/test_groupedSHistory.xul32
-rw-r--r--dom/base/test/chrome/test_nsITextInputProcessor.xul30
-rw-r--r--dom/base/test/chrome/test_range_getClientRectsAndTexts.html60
-rw-r--r--dom/base/test/chrome/test_registerElement_content.xul55
-rw-r--r--dom/base/test/chrome/test_registerElement_ep.xul44
-rw-r--r--dom/base/test/chrome/test_swapFrameLoaders.xul25
-rw-r--r--dom/base/test/chrome/test_title.xul30
-rw-r--r--dom/base/test/chrome/test_windowroot.xul19
-rw-r--r--dom/base/test/chrome/title_window.xul198
-rw-r--r--dom/base/test/chrome/window_groupedSHistory.xul343
-rw-r--r--dom/base/test/chrome/window_nsITextInputProcessor.xul4027
-rw-r--r--dom/base/test/chrome/window_swapFrameLoaders.xul258
78 files changed, 9152 insertions, 0 deletions
diff --git a/dom/base/test/chrome/blockNoPlugins.xml b/dom/base/test/chrome/blockNoPlugins.xml
new file mode 100644
index 000000000..e4e191b37
--- /dev/null
+++ b/dom/base/test/chrome/blockNoPlugins.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310001">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ </pluginItems>
+</blocklist>
diff --git a/dom/base/test/chrome/blockPluginHard.xml b/dom/base/test/chrome/blockPluginHard.xml
new file mode 100644
index 000000000..24eb5bc6f
--- /dev/null
+++ b/dom/base/test/chrome/blockPluginHard.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="2"></versionRange>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/dom/base/test/chrome/bug418986-1.js b/dom/base/test/chrome/bug418986-1.js
new file mode 100644
index 000000000..918d90775
--- /dev/null
+++ b/dom/base/test/chrome/bug418986-1.js
@@ -0,0 +1,73 @@
+// The main test function.
+var test = function (isContent) {
+ SimpleTest.waitForExplicitFinish();
+
+ 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) {
+ is(eval(a), eval(b), a + " should be equal to " + b);
+ };
+
+ // Returns generator object that iterates through pref values.
+ let prefVals = (for (prefVal of [false, true]) prefVal);
+
+ // 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 (!item.startsWith("moz")) {
+ checkPair("window." + item, "chromeWindow." + item);
+ }
+ }
+ });
+ if (!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 000000000..850b320a2
--- /dev/null
+++ b/dom/base/test/chrome/bug421622-referer.sjs
@@ -0,0 +1,8 @@
+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 000000000..30d4f8a03
--- /dev/null
+++ b/dom/base/test/chrome/bug884693.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response)
+{
+ let [status, statusText, body] = request.queryString.split("&");
+ 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.ini b/dom/base/test/chrome/chrome.ini
new file mode 100644
index 000000000..765bbd2df
--- /dev/null
+++ b/dom/base/test/chrome/chrome.ini
@@ -0,0 +1,76 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ blockNoPlugins.xml
+ blockPluginHard.xml
+ bug418986-1.js
+ cpows_child.js
+ cpows_parent.xul
+ file_bug549682.xul
+ file_bug616841.xul
+ file_bug816340.xul
+ file_bug990812-1.xul
+ file_bug990812-2.xul
+ file_bug990812-3.xul
+ file_bug990812-4.xul
+ file_bug990812-5.xul
+ file_bug1139964.xul
+ file_bug1209621.xul
+ fileconstructor_file.png
+ frame_bug814638.xul
+ frame_registerElement_content.html
+ registerElement_ep.js
+ host_bug814638.xul
+ window_nsITextInputProcessor.xul
+ title_window.xul
+ window_swapFrameLoaders.xul
+ window_groupedSHistory.xul
+
+[test_bug120684.xul]
+[test_bug206691.xul]
+[test_bug289714.xul]
+[test_bug339494.xul]
+[test_bug357450.xul]
+support-files = ../file_bug357450.js
+[test_bug380418.html]
+[test_bug380418.html^headers^]
+[test_bug383430.html]
+[test_bug418986-1.xul]
+[test_bug421622.xul]
+[test_bug429785.xul]
+[test_bug430050.xul]
+[test_bug467123.xul]
+[test_bug549682.xul]
+[test_bug571390.xul]
+[test_bug1098074_throw_from_ReceiveMessage.xul]
+[test_bug616841.xul]
+[test_bug635835.xul]
+[test_bug682305.html]
+[test_bug683852.xul]
+[test_bug752226-3.xul]
+[test_bug752226-4.xul]
+[test_bug765993.html]
+[test_bug780199.xul]
+[test_bug780529.xul]
+[test_bug800386.xul]
+[test_bug814638.xul]
+[test_bug816340.xul]
+[test_bug884693.xul]
+[test_bug914381.html]
+[test_bug990812.xul]
+[test_bug1063837.xul]
+[test_bug1139964.xul]
+[test_bug1209621.xul]
+[test_cpows.xul]
+[test_registerElement_content.xul]
+[test_registerElement_ep.xul]
+[test_domparsing.xul]
+[test_fileconstructor.xul]
+[test_fileconstructor_tempfile.xul]
+[test_nsITextInputProcessor.xul]
+[test_range_getClientRectsAndTexts.html]
+[test_title.xul]
+[test_windowroot.xul]
+[test_swapFrameLoaders.xul]
+[test_groupedSHistory.xul]
+[test_bug1339722.html]
diff --git a/dom/base/test/chrome/cpows_child.js b/dom/base/test/chrome/cpows_child.js
new file mode 100644
index 000000000..28ae4d1a7
--- /dev/null
+++ b/dom/base/test/chrome/cpows_child.js
@@ -0,0 +1,382 @@
+dump('loaded child cpow test\n');
+
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+
+(function start() {
+ [is_remote] = sendRpcMessage("cpows:is_remote");
+
+ var tests = [
+ parent_test,
+ error_reporting_test,
+ dom_test,
+ xray_test,
+ symbol_test,
+ compartment_test,
+ regexp_test,
+ postmessage_test,
+ sync_test,
+ async_test,
+ rpc_test,
+ lifetime_test,
+ cancel_test,
+ cancel_test2,
+ dead_test,
+ unsafe_test,
+ ];
+
+ function go() {
+ if (tests.length == 0) {
+ sendRpcMessage("cpows:done", {});
+ return;
+ }
+
+ var test = tests[0];
+ tests.shift();
+ test(function() {
+ go();
+ });
+ }
+
+ go();
+})();
+
+function ok(condition, message) {
+ dump('condition: ' + condition + ', ' + message + '\n');
+ if (!condition) {
+ sendAsyncMessage("cpows:fail", { message: message });
+ throw 'failed check: ' + message;
+ }
+}
+
+var sync_obj;
+var async_obj;
+
+function make_object()
+{
+ let o = { };
+ o.i = 5;
+ o.b = true;
+ o.s = "hello";
+ o.x = { i: 10 };
+ o.f = function () { return 99; };
+ o.ctor = function() { this.a = 3; }
+
+ // Doing anything with this Proxy will throw.
+ var throwing = new Proxy({}, new Proxy({}, {
+ get: function (trap) { throw trap; }
+ }));
+
+ let array = [1, 2, 3];
+
+ let for_json = { "n": 3, "a": array, "s": "hello", o: { "x": 10 } };
+
+ let proto = { data: 42 };
+ let with_proto = Object.create(proto);
+
+ let with_null_proto = Object.create(null);
+
+ content.document.title = "Hello, Kitty";
+ return { "data": o,
+ "throwing": throwing,
+ "document": content.document,
+ "array": array,
+ "for_json": for_json,
+ "with_proto": with_proto,
+ "with_null_proto": with_null_proto
+ };
+}
+
+function make_json()
+{
+ return { check: "ok" };
+}
+
+function parent_test(finish)
+{
+ function f(check_func) {
+ // Make sure this doesn't crash.
+ let array = new Uint32Array(10);
+ content.crypto.getRandomValues(array);
+
+ let result = check_func(10);
+ ok(result == 20, "calling function in parent worked");
+ return result;
+ }
+
+ addMessageListener("cpows:from_parent", (msg) => {
+ let obj = msg.objects.obj;
+ ok(obj.a == 1, "correct value from parent");
+
+ // Test that a CPOW reference to a function in the chrome process
+ // is callable from unprivileged content. Greasemonkey uses this
+ // functionality.
+ let func = msg.objects.func;
+ let sb = Cu.Sandbox('http://www.example.com', {});
+ sb.func = func;
+ ok(sb.eval('func()') == 101, "can call parent's function in child");
+
+ finish();
+ });
+ sendRpcMessage("cpows:parent_test", {}, {func: f});
+}
+
+function error_reporting_test(finish) {
+ sendRpcMessage("cpows:error_reporting_test", {}, {});
+ finish();
+}
+
+function dom_test(finish)
+{
+ let element = content.document.createElement("div");
+ element.id = "it_works";
+ content.document.body.appendChild(element);
+
+ sendRpcMessage("cpows:dom_test", {}, {element: element});
+ Components.utils.schedulePreciseGC(function() {
+ sendRpcMessage("cpows:dom_test_after_gc");
+ finish();
+ });
+}
+
+function xray_test(finish)
+{
+ let element = content.document.createElement("div");
+ element.wrappedJSObject.foo = "hello";
+
+ sendRpcMessage("cpows:xray_test", {}, {element: element});
+ finish();
+}
+
+function symbol_test(finish)
+{
+ let iterator = Symbol.iterator;
+ let named = Symbol.for("cpow-test");
+
+ let object = {
+ [iterator]: iterator,
+ [named]: named,
+ };
+ let test = ['a'];
+ sendRpcMessage("cpows:symbol_test", {}, {object: object, test: test});
+ finish();
+}
+
+// Parent->Child references should go X->parent.privilegedJunkScope->child.privilegedJunkScope->Y
+// Child->Parent references should go X->child.privilegedJunkScope->parent.unprivilegedJunkScope->Y
+function compartment_test(finish)
+{
+ // This test primarily checks various compartment invariants for CPOWs, and
+ // doesn't make sense to run in-process.
+ if (!is_remote) {
+ finish();
+ return;
+ }
+
+ let sb = Cu.Sandbox('http://www.example.com', { wantGlobalProperties: ['XMLHttpRequest'] });
+ sb.eval('function getUnprivilegedObject() { var xhr = new XMLHttpRequest(); xhr.expando = 42; return xhr; }');
+ function testParentObject(obj) {
+ let results = [];
+ function is(a, b, msg) { results.push({ result: a === b ? "PASS" : "FAIL", message: msg }) };
+ function ok(x, msg) { results.push({ result: x ? "PASS" : "FAIL", message: msg }) };
+
+ let cpowLocation = Cu.getCompartmentLocation(obj);
+ ok(/Privileged Junk/.test(cpowLocation),
+ "child->parent CPOWs should live in the privileged junk scope: " + cpowLocation);
+ is(obj(), 42, "child->parent CPOW is invokable");
+ try {
+ obj.expando;
+ ok(false, "child->parent CPOW cannot access properties");
+ } catch (e) {
+ ok(true, "child->parent CPOW cannot access properties");
+ }
+
+ return results;
+ }
+ sendRpcMessage("cpows:compartment_test", {}, { getUnprivilegedObject: sb.getUnprivilegedObject,
+ testParentObject: testParentObject });
+ finish();
+}
+
+function regexp_test(finish)
+{
+ sendRpcMessage("cpows:regexp_test", {}, { regexp: /myRegExp/g });
+ finish();
+}
+
+function postmessage_test(finish)
+{
+ sendRpcMessage("cpows:postmessage_test", {}, { win: content.window });
+ finish();
+}
+
+function sync_test(finish)
+{
+ dump('beginning cpow sync test\n');
+ sync_obj = make_object();
+ sendRpcMessage("cpows:sync",
+ make_json(),
+ make_object());
+ finish();
+}
+
+function async_test(finish)
+{
+ dump('beginning cpow async test\n');
+ async_obj = make_object();
+ sendAsyncMessage("cpows:async",
+ make_json(),
+ async_obj);
+
+ addMessageListener("cpows:async_done", finish);
+}
+
+var rpc_obj;
+
+function rpc_test(finish)
+{
+ dump('beginning cpow rpc test\n');
+ rpc_obj = make_object();
+ rpc_obj.data.reenter = function () {
+ sendRpcMessage("cpows:reenter", { }, { data: { valid: true } });
+ return "ok";
+ }
+ sendRpcMessage("cpows:rpc",
+ make_json(),
+ rpc_obj);
+ finish();
+}
+
+function lifetime_test(finish)
+{
+ if (!is_remote) {
+ // Only run this test when running out-of-process. Otherwise it
+ // will fail, since local CPOWs don't follow the same ownership
+ // rules.
+ finish();
+ return;
+ }
+
+ dump("beginning lifetime test\n");
+ var obj = {"will_die": {"f": 1}};
+ let [result] = sendRpcMessage("cpows:lifetime_test_1", {}, {obj: obj});
+ ok(result == 10, "got sync result");
+ ok(obj.wont_die.f == 2, "got reverse CPOW");
+ obj.will_die = null;
+ Components.utils.schedulePreciseGC(function() {
+ addMessageListener("cpows:lifetime_test_3", (msg) => {
+ ok(obj.wont_die.f == 2, "reverse CPOW still works");
+ finish();
+ });
+ sendRpcMessage("cpows:lifetime_test_2");
+ });
+}
+
+function cancel_test(finish)
+{
+ if (!is_remote) {
+ // No point in doing this in single-process mode.
+ finish();
+ return;
+ }
+
+ let fin1 = false, fin2 = false;
+
+ // CPOW from the parent runs f. When it sends a sync message, the
+ // CPOW is canceled. The parent starts running again immediately
+ // after the CPOW is canceled; f also continues running.
+ function f() {
+ let res = sendSyncMessage("cpows:cancel_sync_message");
+ ok(res[0] == 12, "cancel_sync_message result correct");
+ fin1 = true;
+ if (fin1 && fin2) finish();
+ }
+
+ sendAsyncMessage("cpows:cancel_test", null, {f: f});
+ addMessageListener("cpows:cancel_test_done", msg => {
+ fin2 = true;
+ if (fin1 && fin2) finish();
+ });
+}
+
+function cancel_test2(finish)
+{
+ if (!is_remote) {
+ // No point in doing this in single-process mode.
+ finish();
+ return;
+ }
+
+ let fin1 = false, fin2 = false;
+
+ // CPOW from the parent runs f. When it does a sync XHR, the
+ // CPOW is canceled. The parent starts running again immediately
+ // after the CPOW is canceled; f also continues running.
+ function f() {
+ let req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
+ createInstance(Components.interfaces.nsIXMLHttpRequest);
+ let fin = false;
+ let reqListener = () => {
+ if (req.readyState != req.DONE) {
+ return;
+ }
+ ok(req.status == 200, "XHR succeeded");
+ fin = true;
+ };
+
+ req.onload = reqListener;
+ req.open("get", "http://example.com", false);
+ req.send(null);
+
+ ok(fin == true, "XHR happened");
+
+ fin1 = true;
+ if (fin1 && fin2) finish();
+ }
+
+ sendAsyncMessage("cpows:cancel_test2", null, {f: f});
+ addMessageListener("cpows:cancel_test2_done", msg => {
+ fin2 = true;
+ if (fin1 && fin2) finish();
+ });
+}
+
+function unsafe_test(finish)
+{
+ if (!is_remote) {
+ // Only run this test when running out-of-process.
+ finish();
+ return;
+ }
+
+ function f() {}
+
+ sendAsyncMessage("cpows:unsafe", null, {f});
+ addMessageListener("cpows:unsafe_done", msg => {
+ sendRpcMessage("cpows:safe", null, {f});
+ addMessageListener("cpows:safe_done", finish);
+ });
+}
+
+function dead_test(finish)
+{
+ if (!is_remote) {
+ // Only run this test when running out-of-process.
+ finish();
+ return;
+ }
+
+ let gcTrigger = function() {
+ // Force the GC to dead-ify the thing.
+ content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .garbageCollect();
+ }
+
+ {
+ let thing = { value: "Gonna croak" };
+ sendAsyncMessage("cpows:dead", null, { thing, gcTrigger });
+ }
+
+ addMessageListener("cpows:dead_done", finish);
+}
diff --git a/dom/base/test/chrome/cpows_parent.xul b/dom/base/test/chrome/cpows_parent.xul
new file mode 100644
index 000000000..f633f0a79
--- /dev/null
+++ b/dom/base/test/chrome/cpows_parent.xul
@@ -0,0 +1,493 @@
+<?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="MessageManager CPOW tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="start()">
+
+ <!-- test results are displayed in the html:body -->
+ <label value="CPOWs"/>
+
+ <script type="application/javascript"><![CDATA[
+ var test_state = "remote";
+ var test_node = null;
+ var reentered = false;
+ var savedMM = null;
+ const Cu = Components.utils;
+
+ function info(message) {
+ return opener.wrappedJSObject.info(message);
+ }
+
+ function ok(condition, message) {
+ return opener.wrappedJSObject.ok(condition, message);
+ }
+
+ function is(v1, v2, message) {
+ return opener.wrappedJSObject.is(v1, v2, message);
+ }
+
+ function todo_is(v1, v2, message) {
+ return opener.wrappedJSObject.todo_is(v1, v2, message);
+ }
+
+ // Make sure that an error in this file actually causes the test to fail.
+ var gReceivedErrorProbe = false;
+ window.onerror = function (msg, url, line) {
+ if (/Test Error Probe/.test(msg)) {
+ gReceivedErrorProbe = true;
+ return;
+ }
+ ok(false, "Error while executing: \n" + msg + "\n" + url + ":" + line);
+ };
+
+ function testCpowMessage(message) {
+ ok(message.json.check == "ok", "correct json");
+
+ ok(!Components.utils.isCrossProcessWrapper(message.json), "not everything is a CPOW");
+
+ let data = message.objects.data;
+ let document = message.objects.document;
+ if (test_state == "remote") {
+ ok(Components.utils.isCrossProcessWrapper(data), "got a CPOW");
+ ok(Components.utils.isCrossProcessWrapper(document), "got a CPOW");
+ }
+ ok(data.i === 5, "integer property");
+ ok(data.b === true, "boolean property");
+ ok(data.s === "hello", "string property");
+ ok(data.x.i === 10, "nested property");
+ ok(data.f() === 99, "function call");
+ is(Object.getOwnPropertyDescriptor(data, "doesn't exist"), undefined,
+ "getOwnPropertyDescriptor returns undefined for non-existant properties");
+ ok(Object.getOwnPropertyDescriptor(data, "i").value, 5,
+ "getOwnPropertyDescriptor.value works");
+ let obj = new data.ctor();
+ ok(obj.a === 3, "constructor call");
+ is(document.title, "Hello, Kitty", "document node");
+ is(typeof document.cookie, "string", "can get document.cookie");
+ is(typeof document.defaultView.navigator.userAgent, "string", "can get navigator.userAgent");
+
+ // Don't crash.
+ document.defaultView.screen;
+
+ data.i = 6;
+ data.b = false;
+ data.s = "bye";
+ data.x = null;
+ ok(data.i === 6, "integer property");
+ ok(data.b === false, "boolean property");
+ ok(data.s === "bye", "string property");
+ ok(data.x === null, "nested property");
+
+ let throwing = message.objects.throwing;
+ // Based on the table on:
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
+ let tests = [
+ () => Object.getOwnPropertyDescriptor(throwing, 'test'),
+ () => Object.getOwnPropertyNames(throwing),
+ () => Object.defineProperty(throwing, 'test', {value: 1}),
+ () => delete throwing.test,
+ () => "test" in throwing,
+ () => Object.prototype.hasOwnProperty.call(throwing, 'test'),
+ () => throwing.test,
+ () => { throwing.test = 1 },
+ // () => { for (let prop in throwing) {} }, Bug 783829
+ () => { for (let prop of throwing) {} },
+ () => Object.keys(throwing),
+ () => Function.prototype.call.call(throwing),
+ () => new throwing,
+ () => Object.preventExtensions(throwing),
+ () => Object.freeze(throwing),
+ () => Object.seal(throwing),
+ ]
+
+ for (let test of tests) {
+ let threw = false;
+ try {
+ test()
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "proxy operation threw exception");
+ }
+
+ let array = message.objects.array;
+ let i = 1;
+ for (let elt of array) {
+ ok(elt === i, "correct element found");
+ i++;
+ }
+ ok(i === 4, "array has correct length");
+
+ let j = message.objects.for_json;
+ let str = JSON.stringify(j);
+ let j2 = JSON.parse(str);
+ ok(j2.n === 3, "JSON integer property");
+ ok(j2.a[0] === 1, "JSON array index");
+ ok(j2.a[1] === 2, "JSON array index");
+ ok(j2.a[2] === 3, "JSON array index");
+ ok(j2.s === "hello", "JSON string property");
+ ok(j2.o.x === 10, "JSON object property");
+
+ let with_proto = message.objects.with_proto;
+ let proto = Object.getPrototypeOf(with_proto);
+ ok(proto.data == 42, "Object.getPrototypeOf works on CPOW");
+
+ let with_null_proto = message.objects.with_null_proto;
+ proto = Object.getPrototypeOf(with_null_proto);
+ ok(proto === null, "Object.getPrototypeOf works on CPOW (null proto)");
+ }
+
+ function recvAsyncMessage(message) {
+ testCpowMessage(message);
+ savedMM.sendAsyncMessage("cpows:async_done");
+ }
+
+ function recvSyncMessage(message) {
+ testCpowMessage(message);
+ }
+
+ function recvRpcMessage(message) {
+ ok(message.json.check == "ok", "correct json");
+
+ let data = message.objects.data;
+
+ // Sanity check.
+ ok(data.i === 5, "integer property");
+
+ // Check that we re-enter.
+ reentered = false;
+ let result = data.reenter();
+ ok(reentered, "re-entered rpc");
+ ok(result == "ok", "got correct result");
+ }
+
+ function recvReenterMessage(message) {
+ ok(message.objects.data.valid === true, "cpows work");
+ reentered = true;
+ }
+
+ function recvNestedSyncMessage(message) {
+ message.objects.data.reenter();
+ }
+
+ function recvReenterSyncMessage(message) {
+ ok(false, "should not have received re-entered sync message");
+ }
+
+ function recvFailMessage(message) {
+ ok(false, message.json.message);
+ }
+
+ function recvDoneMessage(message) {
+ if (test_state == "remote") {
+ test_node.parentNode.removeChild(test_node);
+ run_tests("inprocess");
+ return;
+ }
+
+ finish();
+ }
+
+ function recvParentTest(message) {
+ let func = message.objects.func;
+ let result = func(n => 2*n);
+ ok(result == 20, "result == 20");
+ function f() {
+ return 101;
+ }
+ let obj = {a:1, __exposedProps__: {"a": "r"}};
+ savedMM.sendAsyncMessage("cpows:from_parent", {}, {obj: obj, func: f});
+ }
+
+ // Make sure errors in this file actually hit window.onerror.
+ function recvErrorReportingTest(message) {
+ throw "Test Error Probe";
+ }
+
+ let savedElement = null;
+ function recvDomTest(message) {
+ savedElement = message.objects.element;
+
+ is(savedElement.QueryInterface(Components.interfaces.nsISupports), savedElement,
+ "QI to nsISupports works");
+ is(savedElement.QueryInterface(Components.interfaces.nsIDOMNode), savedElement,
+ "QI to a random (implemented) interface works");
+
+ function testNoInterface(savedElement, i) {
+ try {
+ savedElement.QueryInterface(i);
+ ok(false, "should have thrown an exception");
+ } catch (e) {
+ is(e.result, Components.results.NS_ERROR_NO_INTERFACE, "threw the right exception");
+ }
+ }
+
+ testNoInterface(savedElement, Components.interfaces.nsIDOMAttr);
+ testNoInterface(savedElement, Components.interfaces.nsIClassInfo);
+
+ // Test to ensure that we don't pass CPOWs to C++-implemented interfaces.
+ // See bug 1072980.
+ if (test_state == "remote") {
+ // This doesn't work because we intercept toString and QueryInterface specially
+ // and don't cache the function pointer.
+ // See bug 1140636.
+ todo_is(savedElement.toString, savedElement.toString, "toString identity works");
+ todo_is(savedElement.QueryInterface, savedElement.QueryInterface, "toString identity works");
+
+ is(Object.prototype.toString.call(savedElement), "[object HTMLDivElement]",
+ "prove that this works (and doesn't leak)");
+
+ is(Object.prototype.toString.call(savedElement), "[object HTMLDivElement]",
+ "prove that this works twice (since we cache it and doesn't leak)");
+
+ // This does work because we create a CPOW for isEqualNode that stays
+ // alive as long as we have a reference to the first CPOW (so as long
+ // as it's detectable).
+ is(savedElement.isEqualNode, savedElement.isEqualNode, "webidl function identity works");
+
+ let walker = Components.classes["@mozilla.org/inspector/deep-tree-walker;1"]
+ .createInstance(Components.interfaces.inIDeepTreeWalker);
+ const SHOW_ELEMENT = Components.interfaces.nsIDOMNodeFilter.SHOW_ELEMENT;
+ walker.showAnonymousContent = true;
+ walker.showSubDocuments = false;
+
+ try {
+ walker.init(savedElement, SHOW_ELEMENT);
+ ok(false, "expected exception passing CPOW to C++");
+ } catch (e) {
+ is(e.result, Components.results.NS_ERROR_XPC_CANT_PASS_CPOW_TO_NATIVE,
+ "got exception when passing CPOW to C++");
+ }
+ }
+ }
+
+ function recvDomTestAfterGC(message) {
+ let id;
+ try {
+ id = savedElement.id;
+ } catch (e) {
+ ok(false, "Got exception using DOM element");
+ }
+ is(id, "it_works", "DOM element has expected ID");
+ }
+
+ function recvXrayTest(message) {
+ let element = message.objects.element;
+ is(element.foo, undefined, "DOM element does not expose content properties");
+ }
+
+ function recvSymbolTest(message) {
+ let object = message.objects.object;
+ is(object[Symbol.iterator], Symbol.iterator, "Should use Symbol.iterator");
+ is(Symbol.keyFor(object[Symbol.for("cpow-test")]), "cpow-test", "Symbols aren't registered correctly");
+ let symbols = Object.getOwnPropertySymbols(object);
+ is(symbols.length, 2, "Object should have two symbol keys");
+ let test = undefined;
+ for (let x of message.objects.test) {
+ test = x;
+ }
+ is(test, "a", "for .. of iteration should work");
+ }
+
+ let systemGlobal = this;
+ function recvCompartmentTest(message) {
+ let getUnprivilegedObject = message.objects.getUnprivilegedObject;
+ let testParentObject = message.objects.testParentObject;
+
+ // Make sure that parent->child CPOWs live in the parent's privileged junk scope.
+ let unprivilegedObject = getUnprivilegedObject();
+ is(Cu.getGlobalForObject(getUnprivilegedObject),
+ Cu.getGlobalForObject(unprivilegedObject),
+ "all parent->child CPOWs should live in the same scope");
+ let cpowLocation = Cu.getCompartmentLocation(getUnprivilegedObject);
+ ok(/Privileged Junk/.test(cpowLocation),
+ "parent->child CPOWs should live in the privileged junk scope: " + cpowLocation);
+
+ // Make sure that parent->child CPOWs point through a privileged scope in the child
+ // (the privileged junk scope, but we don't have a good way to test for that
+ // specifically).
+ is(unprivilegedObject.expando, undefined, "parent->child references should get Xrays");
+ is(unprivilegedObject.wrappedJSObject.expando, 42, "parent->child references should get waivable Xrays");
+
+ // Send an object to the child to let it verify invariants in the other direction.
+ function passMe() { return 42; };
+ passMe.expando = 42;
+ let results = testParentObject(passMe);
+ ok(results.length > 0, "Need results");
+ results.forEach((x) => is(x.result, "PASS", x.message));
+ }
+
+ function recvRegExpTest(message) {
+ let regexp = message.objects.regexp;
+
+ // These work generically.
+ is(regexp.toString(), "/myRegExp/g", "toString works right");
+ ok(regexp.test("I like myRegExp to match"), "No false positives");
+ ok(!regexp.test("asdfsdf"), "No false positives");
+
+ // These go over regexp_toShared.
+ is("filler myRegExp filler".search(regexp), 7, "String.prototype.match works right");
+ var shell = /x/;
+ shell.compile(regexp);
+ is(regexp.toString(), shell.toString(), ".compile works right");
+ }
+
+ function recvPostMessageTest(message) {
+ let win = message.objects.win;
+ win.postMessage('nookery', '*');
+ ok(true, "Didn't crash invoking postMessage over CPOW");
+ }
+
+ let savedWilldieObj;
+ let wontDie = {f:2, __exposedProps__: {"f": "r"}};
+ function recvLifetimeTest1(message) {
+ let obj = message.objects.obj;
+ savedWilldieObj = obj.will_die;
+ ok(savedWilldieObj.f == 1, "limited-lifetime CPOW works at first");
+ obj.wont_die = wontDie;
+ obj = null;
+ return 10;
+ }
+ function recvLifetimeTest2(message) {
+ let threw = false;
+ try {
+ savedWilldieObj.f;
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "limited-lifetime CPOW stopped working");
+ wontDie = null;
+ Components.utils.schedulePreciseGC(function() {
+ savedMM.sendAsyncMessage("cpows:lifetime_test_3");
+ });
+ }
+
+ function recvCancelTest(msg) {
+ let failed = false;
+ try {
+ msg.objects.f();
+ } catch (e if /cross-process JS call failed/.test(String(e))) {
+ failed = true;
+ }
+ ok(failed, "CPOW should fail due to cancelation");
+ msg.target.messageManager.sendAsyncMessage("cpows:cancel_test_done");
+ }
+
+ function recvCancelSyncMessage() {
+ return 12;
+ }
+
+ function recvCancelTest2(msg) {
+ let failed = false;
+ try {
+ msg.objects.f();
+ } catch (e if /cross-process JS call failed/.test(String(e))) {
+ failed = true;
+ }
+ ok(failed, "CPOW should fail due to cancelation");
+ msg.target.messageManager.sendAsyncMessage("cpows:cancel_test2_done");
+ }
+
+ function recvUnsafe(msg) {
+ let failed = false;
+
+ const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser";
+ opener.wrappedJSObject.SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, true);
+ try {
+ msg.objects.f();
+ } catch (e if /unsafe CPOW usage forbidden/.test(String(e))) {
+ failed = true;
+ }
+ opener.wrappedJSObject.SpecialPowers.clearUserPref(PREF_UNSAFE_FORBIDDEN);
+ ok(failed, "CPOW should fail when unsafe");
+ msg.target.messageManager.sendAsyncMessage("cpows:unsafe_done");
+ }
+
+ function recvSafe(msg) {
+ const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser";
+ opener.wrappedJSObject.SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, true);
+ try {
+ msg.objects.f();
+ } catch (e if /unsafe CPOW usage forbidden/.test(String(e))) {
+ ok(false, "cpow failed");
+ }
+ opener.wrappedJSObject.SpecialPowers.clearUserPref(PREF_UNSAFE_FORBIDDEN);
+ msg.target.messageManager.sendAsyncMessage("cpows:safe_done");
+ }
+
+ function recvDead(msg) {
+ // Need to do this in a separate turn of the event loop.
+ setTimeout(() => {
+ msg.objects.gcTrigger();
+ try {
+ msg.objects.thing.value;
+ ok(false, "Should have been a dead CPOW");
+ } catch(e if /dead CPOW/.test(String(e))) {
+ ok(true, "Got the expected dead CPOW");
+ ok(e.stack, "The exception has a stack");
+ }
+ msg.target.messageManager.sendAsyncMessage("cpows:dead_done");
+ }, 0);
+ }
+
+ function run_tests(type) {
+ info("Running tests: " + type);
+ var node = document.getElementById('cpowbrowser_' + type);
+
+ test_state = type;
+ test_node = node;
+
+ function recvIsRemote(message) {
+ return type == "remote";
+ }
+
+ var mm = node.messageManager;
+ savedMM = mm;
+ mm.addMessageListener("cpows:is_remote", recvIsRemote);
+ mm.addMessageListener("cpows:async", recvAsyncMessage);
+ mm.addMessageListener("cpows:sync", recvSyncMessage);
+ mm.addMessageListener("cpows:rpc", recvRpcMessage);
+ mm.addMessageListener("cpows:reenter", recvReenterMessage);
+ mm.addMessageListener("cpows:reenter", recvReenterMessage);
+ mm.addMessageListener("cpows:nested_sync", recvNestedSyncMessage);
+ mm.addMessageListener("cpows:reenter_sync", recvReenterSyncMessage);
+ mm.addMessageListener("cpows:done", recvDoneMessage);
+ mm.addMessageListener("cpows:fail", recvFailMessage);
+ mm.addMessageListener("cpows:parent_test", recvParentTest);
+ mm.addMessageListener("cpows:error_reporting_test", recvErrorReportingTest);
+ mm.addMessageListener("cpows:dom_test", recvDomTest);
+ mm.addMessageListener("cpows:dom_test_after_gc", recvDomTestAfterGC);
+ mm.addMessageListener("cpows:xray_test", recvXrayTest);
+ if (typeof Symbol === "function") {
+ mm.addMessageListener("cpows:symbol_test", recvSymbolTest);
+ }
+ mm.addMessageListener("cpows:compartment_test", recvCompartmentTest);
+ mm.addMessageListener("cpows:regexp_test", recvRegExpTest);
+ mm.addMessageListener("cpows:postmessage_test", recvPostMessageTest);
+ mm.addMessageListener("cpows:lifetime_test_1", recvLifetimeTest1);
+ mm.addMessageListener("cpows:lifetime_test_2", recvLifetimeTest2);
+ mm.addMessageListener("cpows:cancel_test", recvCancelTest);
+ mm.addMessageListener("cpows:cancel_sync_message", recvCancelSyncMessage);
+ mm.addMessageListener("cpows:cancel_test2", recvCancelTest2);
+ mm.addMessageListener("cpows:unsafe", recvUnsafe);
+ mm.addMessageListener("cpows:safe", recvSafe);
+ mm.addMessageListener("cpows:dead", recvDead);
+ mm.loadFrameScript("chrome://mochitests/content/chrome/dom/base/test/chrome/cpows_child.js", true);
+ }
+
+ function start() {
+ run_tests('remote');
+ }
+
+ function finish() {
+ ok(gReceivedErrorProbe, "Should have reported error probe");
+ opener.setTimeout("done()", 0);
+ window.close();
+ }
+ ]]></script>
+
+ <browser type="content" src="about:blank" id="cpowbrowser_remote" remote="true"/>
+ <browser type="content" src="about:blank" id="cpowbrowser_inprocess"/>
+</window>
diff --git a/dom/base/test/chrome/file_bug1139964.xul b/dom/base/test/chrome/file_bug1139964.xul
new file mode 100644
index 000000000..251334301
--- /dev/null
+++ b/dom/base/test/chrome/file_bug1139964.xul
@@ -0,0 +1,62 @@
+<?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[
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var ppm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageBroadcaster);
+
+ function ok(cond, msg) {
+ opener.wrappedJSObject.ok(cond, msg);
+ }
+
+ var msgName = "TEST:Global_has_Promise";
+
+ function mmScriptForPromiseTest() {
+ 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 not 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, "TabChildGlobal should have Promise object in the global scope!");
+ ok(m.data.hasTextEncoder, "TabChildGlobal should have TextEncoder object in the global scope!");
+ ok(!m.data.hasWindow, "TabChildGlobal should not have Window object in the global scope!");
+
+ opener.setTimeout("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.xul b/dom/base/test/chrome/file_bug1209621.xul
new file mode 100644
index 000000000..05d81c3fb
--- /dev/null
+++ b/dom/base/test/chrome/file_bug1209621.xul
@@ -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=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[
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var Cr = Components.results;
+ var Cu = Components.utils;
+ function ok(cond, msg) {
+ opener.wrappedJSObject.ok(cond, msg);
+ }
+
+ function is(actual, expected, msg) {
+ opener.wrappedJSObject.is(actual, expected, msg);
+ }
+
+ function run() {
+ var docshell = window.getInterface(Ci.nsIDocShell);
+ 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 type=content-primary.");
+ is(treeOwner.primaryTabParent, null,
+ "There shouldn't be primaryTabParent because no remote browser has type=content-primary.");
+
+ var ip = document.getElementById("inprocess");
+ var remote = document.getElementById("remote");
+ var remote2 = document.getElementById("remote2");
+
+ ip.setAttribute("type", "content-primary");
+ ok(ip.docShell, "non-remote browser should have a DocShell.");
+ is(treeOwner.primaryContentShell, ip.docShell,
+ "content-primary browser should be the primaryContentShell.");
+ is(treeOwner.primaryTabParent, null,
+ "There shouldn't be primaryTabParent because no remote browser has type=content-primary.");
+
+ ip.setAttribute("type", "content");
+ remote.setAttribute("type", "content-primary");
+ is(treeOwner.primaryContentShell, null,
+ "There shouldn't be primaryContentShell because no browser has type=content-primary.");
+ var tp = remote.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.tabParent;
+ ok(tp, "Remote browsers should have a TabParent.");
+ is(treeOwner.primaryTabParent, tp,
+ "content-primary remote browser should be the primaryTabParent.");
+
+ remote.setAttribute("type", "content");
+ is(treeOwner.primaryContentShell, null,
+ "There shouldn't be primaryContentShell because no browser has type=content-primary.");
+ is(treeOwner.primaryTabParent, null,
+ "There shouldn't be primaryTabParent because no remote browser has type=content-primary.");
+
+ remote2.setAttribute("type", "content-primary");
+ var tp2 = remote2.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.tabParent;
+ ok(tp2, "Remote browsers should have a TabParent.");
+ is(treeOwner.primaryTabParent, tp2,
+ "content-primary remote browser should be the primaryTabParent.");
+ is(treeOwner.primaryContentShell, null,
+ "There shouldn't be primaryContentShell because no browser has type=content-primary.");
+
+ opener.setTimeout("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.xul b/dom/base/test/chrome/file_bug549682.xul
new file mode 100644
index 000000000..667e7ca0b
--- /dev/null
+++ b/dom/base/test/chrome/file_bug549682.xul
@@ -0,0 +1,226 @@
+<?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[
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var Cr = Components.results;
+ var Cu = Components.utils;
+
+ var didRunAsync = false;
+ var didRunLocal = false;
+
+ var global = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Components.interfaces.nsIMessageBroadcaster);
+ var ppm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Components.interfaces.nsIMessageBroadcaster);
+ var cpm = Cc["@mozilla.org/childprocessmessagemanager;1"]
+ .getService(Components.interfaces.nsISyncMessageSender);
+
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ function ok(cond, msg) {
+ opener.wrappedJSObject.ok(cond, msg);
+ }
+
+ function is(actual, expected, msg) {
+ opener.wrappedJSObject.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: XPCOMUtils.generateQI([Ci.nsIMessageListener,
+ Ci.nsISupportsWeakReference]),
+
+ receiveMessage: function(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: XPCOMUtils.generateQI([Ci.nsIMessageListener,
+ Ci.nsISupportsWeakReference]),
+
+ receiveMessage: function(msg) {
+ ok(false, 'Should not have received a message.');
+ }
+ };
+
+ function weakDoneListener() {
+ ok(weakMessageReceived, 'Got "weak" message.');
+ finish();
+ }
+
+ function finish() {
+ opener.setTimeout("done()", 0);
+ var i = document.getElementById("ifr");
+ i.parentNode.removeChild(i); // 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 wn = document.getElementById('ifr').contentWindow
+ .getInterface(Components.interfaces.nsIWebNavigation);
+ ok(wn, "Should have webnavigation");
+ var cfmm = wn.getInterface(Components.interfaces.nsIContentFrameMessageManager);
+ 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.createElement("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.xul b/dom/base/test/chrome/file_bug616841.xul
new file mode 100644
index 000000000..5110c0c9e
--- /dev/null
+++ b/dom/base/test/chrome/file_bug616841.xul
@@ -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[
+
+ 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: i, a: pair[0], b: pair[1] });
+ }
+ }
+
+ function recvCmp(m) {
+ var i = m.json.i, cmp = m.json.cmp;
+ var pair = toCompare[i];
+ opener.wrappedJSObject.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() {
+ opener.setTimeout("done()", 0);
+ window.close();
+ }
+
+ ]]></script>
+
+ <browser id="browser" type="content" src="about:blank"/>
+</window>
diff --git a/dom/base/test/chrome/file_bug816340.xul b/dom/base/test/chrome/file_bug816340.xul
new file mode 100644
index 000000000..bf980e437
--- /dev/null
+++ b/dom/base/test/chrome/file_bug816340.xul
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+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) {
+ opener.wrappedJSObject.ok(val, msg);
+ }
+
+ var elems =
+ [
+ "input",
+ "textarea",
+ "select",
+ "fieldset",
+ "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");
+ opener.setTimeout("done()", 0);
+ window.close();
+ }
+
+ ]]></script>
+
+ <browser id="browser" type="content" src="about:blank"/>
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-1.xul b/dom/base/test/chrome/file_bug990812-1.xul
new file mode 100644
index 000000000..1e085e76a
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-1.xul
@@ -0,0 +1,64 @@
+<?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[
+
+ 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 Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ function is(val, exp, msg) {
+ opener.wrappedJSObject.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();
+ opener.wrappedJSObject.is(msg.data, next, "received test:" + next);
+
+ if (order.length == 0) {
+ opener.setTimeout("next()");
+ window.close();
+ }
+ });
+
+ var browser = document.createElement("browser");
+ browser.setAttribute("messagemanagergroup", "test");
+ browser.setAttribute("src", "about:mozilla");
+ browser.setAttribute("type", "content-targetable");
+ 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.xul b/dom/base/test/chrome/file_bug990812-2.xul
new file mode 100644
index 000000000..957861cd4
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-2.xul
@@ -0,0 +1,59 @@
+<?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[
+
+ var FRAME_SCRIPT = "data:,sendAsyncMessage('test')";
+ var order = ["group", "window", "global"];
+
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ function is(val, exp, msg) {
+ opener.wrappedJSObject.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 () {
+ opener.setTimeout("next()");
+ self.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test" type="content-targetable" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-3.xul b/dom/base/test/chrome/file_bug990812-3.xul
new file mode 100644
index 000000000..6dc25c9bb
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-3.xul
@@ -0,0 +1,71 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=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[
+
+ var FRAME_SCRIPT = "data:,addMessageListener('test', function (msg) {" +
+ "sendSyncMessage('test', msg.data)})";
+
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ function is(val, exp, msg) {
+ opener.wrappedJSObject.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 == 0) {
+ 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 () {
+ opener.setTimeout("next()");
+ window.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
+ <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-4.xul b/dom/base/test/chrome/file_bug990812-4.xul
new file mode 100644
index 000000000..c6d2c04e9
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-4.xul
@@ -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[
+
+ var FRAME_SCRIPT1 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'frame1')})";
+ var FRAME_SCRIPT2 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'frame2')})";
+
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ function is(val, exp, msg) {
+ opener.wrappedJSObject.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"));
+
+ var flo1 = browser1.QueryInterface(Ci.nsIFrameLoaderOwner);
+ var flo2 = browser2.QueryInterface(Ci.nsIFrameLoaderOwner);
+ flo1.swapFrameLoaders(flo2);
+ messageManager.broadcastAsyncMessage("test");
+
+ Promise.all([promise1, promise2]).then(function () {
+ opener.setTimeout("next()");
+ window.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
+ <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812-5.xul b/dom/base/test/chrome/file_bug990812-5.xul
new file mode 100644
index 000000000..b00e05bc5
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812-5.xul
@@ -0,0 +1,77 @@
+<?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[
+
+ var FRAME_SCRIPT1 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'group1')})";
+ var FRAME_SCRIPT2 = "data:,addMessageListener('test', function () {" +
+ "sendSyncMessage('test', 'group2')})";
+
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ function is(val, exp, msg) {
+ opener.wrappedJSObject.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 () {
+ opener.setTimeout("next()");
+ window.close();
+ });
+ }
+
+ ]]></script>
+
+ <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
+ <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
+
+ <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+ <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+
+</window>
diff --git a/dom/base/test/chrome/file_bug990812.xul b/dom/base/test/chrome/file_bug990812.xul
new file mode 100644
index 000000000..e14661fe9
--- /dev/null
+++ b/dom/base/test/chrome/file_bug990812.xul
@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=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[
+
+ 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 Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+
+ 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 == 0) {
+ opener.setTimeout("next()");
+ window.close();
+ }
+ });
+
+ var browser = document.createElement("browser");
+ browser.setAttribute("messagemanagergroup", "test");
+ browser.setAttribute("src", "about:mozilla");
+ browser.setAttribute("type", "content-targetable");
+ 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/fileconstructor_file.png b/dom/base/test/chrome/fileconstructor_file.png
new file mode 100644
index 000000000..51e8aaf38
--- /dev/null
+++ b/dom/base/test/chrome/fileconstructor_file.png
Binary files differ
diff --git a/dom/base/test/chrome/frame_bug814638.xul b/dom/base/test/chrome/frame_bug814638.xul
new file mode 100644
index 000000000..3543a42c3
--- /dev/null
+++ b/dom/base/test/chrome/frame_bug814638.xul
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=814638
+-->
+<window title="Mozilla Bug 814638"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <keyset>
+ <key key="T" modifiers="control" oncommand="receivedKeyEvent()"/>
+ </keyset>
+
+ <iframe flex="1" src="about:"/>
+
+</window>
diff --git a/dom/base/test/chrome/frame_registerElement_content.html b/dom/base/test/chrome/frame_registerElement_content.html
new file mode 100644
index 000000000..aa1d75863
--- /dev/null
+++ b/dom/base/test/chrome/frame_registerElement_content.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<x-bar></x-bar>
+</body>
+</html>
diff --git a/dom/base/test/chrome/host_bug814638.xul b/dom/base/test/chrome/host_bug814638.xul
new file mode 100644
index 000000000..081a12578
--- /dev/null
+++ b/dom/base/test/chrome/host_bug814638.xul
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=814638
+-->
+<window title="Mozilla Bug 814638"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <iframe flex="1" src="frame_bug814638.xul"/>
+</window>
diff --git a/dom/base/test/chrome/nochrome_bug765993.html b/dom/base/test/chrome/nochrome_bug765993.html
new file mode 100644
index 000000000..158b20c88
--- /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 000000000..a84113e1e
--- /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 000000000..8efacff3c
--- /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/registerElement_ep.js b/dom/base/test/chrome/registerElement_ep.js
new file mode 100644
index 000000000..de32ba51c
--- /dev/null
+++ b/dom/base/test/chrome/registerElement_ep.js
@@ -0,0 +1,8 @@
+var proto = Object.create(HTMLElement.prototype);
+proto.magicNumber = 42;
+proto.createdCallback = function() {
+ finishTest(this.magicNumber === 42);
+};
+document.registerElement("x-foo", { prototype: proto });
+
+document.createElement("x-foo");
diff --git a/dom/base/test/chrome/test_bug1063837.xul b/dom/base/test/chrome/test_bug1063837.xul
new file mode 100644
index 000000000..6104295b4
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1063837.xul
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=206691
+-->
+<window title="Mozilla Bug 1063837"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ 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.URI.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.xul b/dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xul
new file mode 100644
index 000000000..ea154ae9c
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1098074_throw_from_ReceiveMessage.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/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 type="application/javascript"
+ 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 Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ 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);
+
+ 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.xul b/dom/base/test/chrome/test_bug1139964.xul
new file mode 100644
index 000000000..f0a7cefd7
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1139964.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1139964
+-->
+<window title="Mozilla Bug 1139964"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ 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.open("file_bug1139964.xul", "", "chrome");
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug120684.xul b/dom/base/test/chrome/test_bug120684.xul
new file mode 100644
index 000000000..bc2fc8d95
--- /dev/null
+++ b/dom/base/test/chrome/test_bug120684.xul
@@ -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 type="application/javascript" 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(list instanceof NodeList, "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.createElement("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.createElement("foo");
+ var e2 = document.createElement("foo");
+ var e3 = document.createElement("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.xul b/dom/base/test/chrome/test_bug1209621.xul
new file mode 100644
index 000000000..4bca58d98
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1209621.xul
@@ -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 type="application/javascript" 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.open("file_bug1209621.xul", "", "chrome");
+ });
+
+ ]]>
+ </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 000000000..261055dd8
--- /dev/null
+++ b/dom/base/test/chrome/test_bug1339722.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1339722
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1339722</title>
+ <script type="application/javascript" 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-useragent-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
+ * destoryed in the middle of ReallyStartLoading.
+ * 4. Verify that it doesn't crash.
+ **/
+
+ const {interfaces: Ci, utils: Cu} = Components;
+ Cu.import("resource://gre/modules/Services.jsm");
+
+ const TOPIC = 'http-on-useragent-request';
+ Services.obs.addObserver({
+ observe(subject, topic, data) {
+ info('Got ' + topic);
+ Services.obs.removeObserver(this, TOPIC);
+
+ // Query window proxy so it triggers DOMWindowCreated.
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ let win = channel.notificationCallbacks.getInterface(Ci.mozIDOMWindowProxy);
+ }
+ }, TOPIC, false);
+
+ let docShell = SpecialPowers.wrap(window)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
+ docShell.chromeEventHandler.addEventListener('DOMWindowCreated', function handler(e) {
+ docShell.chromeEventHandler.removeEventListener('DOMWindowCreated', handler);
+ 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);
+ });
+
+ 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_bug206691.xul b/dom/base/test/chrome/test_bug206691.xul
new file mode 100644
index 000000000..f6496e2df
--- /dev/null
+++ b/dom/base/test/chrome/test_bug206691.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=206691
+-->
+<window title="Mozilla Bug 206691"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ 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.xul b/dom/base/test/chrome/test_bug289714.xul
new file mode 100644
index 000000000..6d0804784
--- /dev/null
+++ b/dom/base/test/chrome/test_bug289714.xul
@@ -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 type="application/javascript" 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.xul b/dom/base/test/chrome/test_bug339494.xul
new file mode 100644
index 000000000..a81838450
--- /dev/null
+++ b/dom/base/test/chrome/test_bug339494.xul
@@ -0,0 +1,64 @@
+<?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 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=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 **/
+
+ var d = document.getElementById("d");
+
+ d.setAttribute("hhh", "testvalue");
+
+ document.addEventListener("DOMAttrModified", removeItAgain, false);
+ d.removeAttribute("hhh");
+ document.removeEventListener("DOMAttrModified", removeItAgain, false);
+
+ function removeItAgain()
+ {
+ ok(!d.hasAttribute("hhh"), "Value check 1",
+ "There should be no value");
+ isnot(d.getAttribute("hhh"), "testvalue", "Value check 2");
+ document.removeEventListener("DOMAttrModified", removeItAgain, false);
+ d.removeAttribute("hhh");
+ ok(true, "Reachability", "We shouldn't have crashed");
+ }
+
+ var s = document.getElementById("s");
+
+ s.setAttribute("ggg", "testvalue");
+
+ document.addEventListener("DOMAttrModified", compareVal, false);
+ s.setAttribute("ggg", "othervalue");
+ document.removeEventListener("DOMAttrModified", compareVal, false);
+
+ function compareVal()
+ {
+ 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");
+ }
+
+</script>
+
+</window>
diff --git a/dom/base/test/chrome/test_bug357450.xul b/dom/base/test/chrome/test_bug357450.xul
new file mode 100644
index 000000000..7723364ec
--- /dev/null
+++ b/dom/base/test/chrome/test_bug357450.xul
@@ -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 000000000..6fb524e76
--- /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 type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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 000000000..5f8d4969c
--- /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 000000000..cfb8595ac
--- /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 type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=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.xul b/dom/base/test/chrome/test_bug418986-1.xul
new file mode 100644
index 000000000..aa0c34077
--- /dev/null
+++ b/dom/base/test/chrome/test_bug418986-1.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=418986-1
+-->
+<window title="Mozilla Bug 418986 (Part 1)"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ 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;version=1.7" 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.xul b/dom/base/test/chrome/test_bug421622.xul
new file mode 100644
index 000000000..abbb9f2d5
--- /dev/null
+++ b/dom/base/test/chrome/test_bug421622.xul
@@ -0,0 +1,35 @@
+<?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 type="application/javascript"
+ 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.xul b/dom/base/test/chrome/test_bug429785.xul
new file mode 100644
index 000000000..5d8af4555
--- /dev/null
+++ b/dom/base/test/chrome/test_bug429785.xul
@@ -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=429785
+-->
+<window title="Mozilla Bug 429785"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" 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;
+ const serv = Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService);
+ var listener = {
+ QueryInterface : function(iid) {
+ if (!iid.equals(Components.interfaces.nsISupports) &&
+ !iid.equals(Components.interfaces.nsIConsoleListener)) {
+ throw Components.results.NS_NOINTERFACE;
+ }
+ return this;
+ },
+ observe : function (msg) { errorLogged = true; }
+ };
+
+ function step2() {
+ is(errorLogged, false, "Should be no errors");
+
+ serv.logStringMessage("This is a test");
+
+ setTimeout(step3, 0);
+
+ }
+
+ function step3() {
+ is(errorLogged, true, "Should see errors when they happen");
+ serv.unregisterListener(listener);
+ SimpleTest.finish();
+ }
+
+ serv.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.xul b/dom/base/test/chrome/test_bug430050.xul
new file mode 100644
index 000000000..b5948fad0
--- /dev/null
+++ b/dom/base/test/chrome/test_bug430050.xul
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=430050
+-->
+<window title="Mozilla Bug 430050"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ 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() {
+ document.documentElement.addEventListener('DOMAttrModified',
+ function(evt) {
+ if (evt.target == evt.currentTarget) {
+ document.getElementById('b').setAttribute("src",
+ "data:text/plain,failed");
+ document.getElementById('b').loadURI('data:text/plain,succeeded',
+ null,
+ 'UTF-8');
+ document.getElementById('b').addEventListener("load", endTest);
+ }
+ }, 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.xul b/dom/base/test/chrome/test_bug467123.xul
new file mode 100644
index 000000000..d44cbe4b9
--- /dev/null
+++ b/dom/base/test/chrome/test_bug467123.xul
@@ -0,0 +1,35 @@
+<?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 type="application/javascript"
+ 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 **/
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "chrome://global/content/bindings/button.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_bug549682.xul b/dom/base/test/chrome/test_bug549682.xul
new file mode 100644
index 000000000..8c7376bb5
--- /dev/null
+++ b/dom/base/test/chrome/test_bug549682.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=549682
+-->
+<window title="Mozilla Bug 549682"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ 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.open("file_bug549682.xul", "", "chrome");
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug571390.xul b/dom/base/test/chrome/test_bug571390.xul
new file mode 100644
index 000000000..aebb2cfc8
--- /dev/null
+++ b/dom/base/test/chrome/test_bug571390.xul
@@ -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 type="application/javascript" 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.xul b/dom/base/test/chrome/test_bug616841.xul
new file mode 100644
index 000000000..9b04eb3b1
--- /dev/null
+++ b/dom/base/test/chrome/test_bug616841.xul
@@ -0,0 +1,31 @@
+<?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 type="application/javascript"
+ 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.open("file_bug616841.xul", "", "chrome");
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug635835.xul b/dom/base/test/chrome/test_bug635835.xul
new file mode 100644
index 000000000..e06541fb4
--- /dev/null
+++ b/dom/base/test/chrome/test_bug635835.xul
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=635835
+-->
+<window title="Mozilla Bug 635835"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ 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 = Components.interfaces.nsIDOMNodeFilter.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 000000000..64f36c907
--- /dev/null
+++ b/dom/base/test/chrome/test_bug682305.html
@@ -0,0 +1,175 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=682305
+-->
+<head>
+ <title>XMLHttpRequest send and channel implemented in JS</title>
+ <script type="application/javascript" 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;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+/*
+ * Register a custom nsIProtocolHandler service
+ * in order to be able to implement *and use* an
+ * nsIChannel component written in Javascript.
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+var SimpleURI = Cc["@mozilla.org/network/simple-uri;1"];
+var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+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: function(listener, context) {
+ let stream = this.open();
+ try {
+ listener.onStartRequest(this, context);
+ } catch(e) {}
+ try {
+ listener.onDataAvailable(this, context, stream, 0, stream.available());
+ } catch(e) {}
+ try {
+ listener.onStopRequest(this, context, Cr.NS_OK);
+ } catch(e) {}
+ },
+ asyncOpen2: function(listener) {
+ // throws an error if security checks fail
+ var outListener = contentSecManager.performSecurityCheck(this, listener);
+ return this.asyncOpen(outListener, null);
+ },
+ open: function() {
+ let data = "bar";
+ let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+ stream.setData(data, data.length);
+ return stream;
+ },
+ open2: function() {
+ // throws an error if security checks fail
+ contentSecManager.performSecurityCheck(this, null);
+ return this.open();
+ },
+ isPending: function() {
+ return false;
+ },
+ cancel: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ suspend: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ resume: function() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel, Ci.nsIRequest])
+};
+
+
+function CustomProtocol() {}
+CustomProtocol.prototype = {
+ get scheme() {
+ return PROTOCOL_SCHEME;
+ },
+ get protocolFlags() {
+ return (Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD);
+ },
+ get defaultPort() {
+ return -1;
+ },
+ allowPort: function allowPort() {
+ return false;
+ },
+ newURI: function newURI(spec, charset, baseURI) {
+ var uri = SimpleURI.createInstance(Ci.nsIURI)
+ uri.spec = spec;
+ return uri.QueryInterface(Ci.nsIURI);
+ },
+ newChannel2: function newChannel2(URI, loadInfo) {
+ return new CustomChannel(URI, loadInfo);
+ },
+ newChannel: function newChannel(URI) {
+ return newChannel2(URI);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIProtocolHandler])
+};
+
+var gFactory = {
+ register: function() {
+ var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+ var classID = Components.ID("{ed064287-1e76-49ba-a28d-dc74394a8334}");
+ var description = PROTOCOL_SCHEME + ": protocol";
+ var contractID = "@mozilla.org/network/protocol;1?name=" + PROTOCOL_SCHEME;
+ var factory = XPCOMUtils._getFactory(CustomProtocol);
+
+ registrar.registerFactory(classID, description, contractID, factory);
+
+ this.unregister = function() {
+ registrar.unregisterFactory(classID, factory);
+ 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.xul b/dom/base/test/chrome/test_bug683852.xul
new file mode 100644
index 000000000..cebc8f358
--- /dev/null
+++ b/dom/base/test/chrome/test_bug683852.xul
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=683852
+-->
+<window title="Mozilla Bug 683852"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+ <button value="testbutton" id="testbutton"/>
+ <!-- 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();
+
+ function startTest() {
+ is(document.contains(document), true, "Document should contain itself!");
+
+ var tb = document.getElementById("testbutton");
+ is(document.contains(tb), true, "Document should contain element in it!");
+ is(tb.contains(tb), true, "Element should contain itself.")
+ var anon = document.getAnonymousElementByAttribute(tb, "anonid", "button-box");
+ is(document.contains(anon), false, "Document should not contain anonymous element in it!");
+ is(tb.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(tb), true, "Element should contain element in it!");
+ is(document.contains(document.createElement("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>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.xul b/dom/base/test/chrome/test_bug752226-3.xul
new file mode 100644
index 000000000..ed3d5c60c
--- /dev/null
+++ b/dom/base/test/chrome/test_bug752226-3.xul
@@ -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 type="application/javascript" 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.xul b/dom/base/test/chrome/test_bug752226-4.xul
new file mode 100644
index 000000000..d2335f9b8
--- /dev/null
+++ b/dom/base/test/chrome/test_bug752226-4.xul
@@ -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 type="application/javascript" 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 Components.utils.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 000000000..b297c8906
--- /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 type="application/javascript" 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 { -moz-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 **/
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+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 > 0, "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.xul b/dom/base/test/chrome/test_bug780199.xul
new file mode 100644
index 000000000..649538bec
--- /dev/null
+++ b/dom/base/test/chrome/test_bug780199.xul
@@ -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 type="application/javascript" 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.xul b/dom/base/test/chrome/test_bug780529.xul
new file mode 100644
index 000000000..f28ed4b78
--- /dev/null
+++ b/dom/base/test/chrome/test_bug780529.xul
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=780529
+-->
+<window title="Mozilla Bug 780529"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" 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(Components.interfaces.nsIChannelEventSink);
+ok(sink instanceof Components.interfaces.nsIChannelEventSink,
+ "Should be a channel event sink")
+ok("asyncOnChannelRedirect" in sink,
+ "Should have the right methods for an event sink");
+
+is(callbacks.getInterface(Components.interfaces.nsIXMLHttpRequest), req,
+ "Should have the right object");
+sinkReq = sink.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+isnot(sinkReq, callbacks, "Sink should not be the XHR object");
+is(sinkReq.getInterface(Components.interfaces.nsIXMLHttpRequest), req,
+ "Should have the XHR object now");
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug800386.xul b/dom/base/test/chrome/test_bug800386.xul
new file mode 100644
index 000000000..369e6ed81
--- /dev/null
+++ b/dom/base/test/chrome/test_bug800386.xul
@@ -0,0 +1,68 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=800386
+-->
+<window title="Mozilla Bug 800386"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" 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 **/
+ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ SimpleTest.waitForExplicitFinish();
+
+ var triedForwarding = false;
+ var forwardFailed = false;
+
+ var xhr = new XMLHttpRequest;
+ var xhr2 = new XMLHttpRequest;
+
+ var eventSink = xhr.getInterface(Components.interfaces.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: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIProgressEventSink)) {
+ triedForwarding = true;
+ try {
+ return xhr2.getInterface(aIID);
+ } catch (e) {
+ forwardFailed = true;
+ }
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsISupports,
+ Components.interfaces.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_bug814638.xul b/dom/base/test/chrome/test_bug814638.xul
new file mode 100644
index 000000000..f507099c9
--- /dev/null
+++ b/dom/base/test/chrome/test_bug814638.xul
@@ -0,0 +1,64 @@
+<?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=814638
+-->
+<window title="Mozilla Bug 814638"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" 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=814638"
+ target="_blank" id="link">Mozilla Bug 814638</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 814638 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function startTest() {
+ let hostURL = "chrome://mochitests/content/chrome/dom/base/test/chrome/host_bug814638.xul";
+ let host1 = window.open(hostURL, "_blank", "chrome");
+ let host2 = window.open(hostURL, "_blank", "chrome");
+
+ let isHost1Loaded = isHost2Loaded = false
+ host1.onload = function() {
+ isHost1Loaded = true;
+ if (isHost2Loaded) swapFrames();
+ }
+ host2.onload = function() {
+ isHost2Loaded = true;
+ if (isHost1Loaded) swapFrames();
+ }
+
+ function swapFrames() {
+ let iframe1 = host1.document.querySelector("iframe");
+ let iframe2 = host2.document.querySelector("iframe");
+ iframe2.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
+ iframe2.swapFrameLoaders(iframe1);
+ setTimeout(function() {
+ iframe2.contentWindow.receivedKeyEvent = receivedKeyEvent;
+ let innerIframe2 = iframe2.contentDocument.querySelector("iframe");
+ let e = innerIframe2.contentDocument.createEvent("KeyboardEvent");
+ e.initKeyEvent("keypress", true, true, null, true, false, false, false, 0, "t".charCodeAt(0));
+ innerIframe2.contentDocument.documentElement.dispatchEvent(e);
+ host1.close();
+ host2.close();
+ }, 0);
+ }
+ }
+
+ function receivedKeyEvent() {
+ ok(true, "Received key event");
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(startTest);
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_bug816340.xul b/dom/base/test/chrome/test_bug816340.xul
new file mode 100644
index 000000000..c6ad12b42
--- /dev/null
+++ b/dom/base/test/chrome/test_bug816340.xul
@@ -0,0 +1,31 @@
+<?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 type="application/javascript"
+ 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 type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ function done() {
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ window.open("file_bug816340.xul", "", "chrome");
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_bug884693.xul b/dom/base/test/chrome/test_bug884693.xul
new file mode 100644
index 000000000..6a2be9d73
--- /dev/null
+++ b/dom/base/test/chrome/test_bug884693.xul
@@ -0,0 +1,67 @@
+<?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 type="application/javascript" 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";
+
+ var { classes: Cc, interfaces: Ci } = Components;
+
+ let consoleService = Cc["@mozilla.org/consoleservice;1"].
+ getService(Ci.nsIConsoleService)
+
+ function runTest(status, statusText, body, expectedResponse, expectedMessages)
+ {
+ return new Promise((resolve, reject) => {
+ consoleService.reset();
+
+ let xhr = new XMLHttpRequest();
+
+ xhr.onload = () => {
+ is(xhr.responseText, expectedResponse, "Correct responseText returned");
+
+ let messages = consoleService.getMessageArray() || [];
+ is(messages.length, expectedMessages.length, "Got expected message count");
+ messages = messages.map(m => m.message).join(",");
+ for(let message of expectedMessages) {
+ ok(messages.indexOf(message) >= 0, "Got expected message: " + message);
+ }
+
+ resolve();
+ };
+
+ xhr.onerror = e => {
+ reject(e);
+ };
+
+ xhr.open("GET", `${SERVER_URL}?${status}&${statusText}&${body}`);
+ xhr.send();
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ runTest(204, "No content", "", "", []).
+ then(() => { return runTest(204, "No content", INVALID_XML, "", []); }).
+ then(() => { return runTest(304, "Not modified", "", "", []); }).
+ then(() => { return runTest(304, "Not modified", INVALID_XML, "", []); }).
+ then(() => { return runTest(200, "OK", "", "", ["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 000000000..aa463ca9e
--- /dev/null
+++ b/dom/base/test/chrome/test_bug914381.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=650776
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 914381</title>
+ <script type="application/javascript" 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">
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+function createFileWithData(fileData) {
+ var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+ var testFile = 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
+ 0666, 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");
+var f = File.createFromNsIFile(file);
+is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js");
+is(f.mozFullPath, file.path, "mozFullPath returns path if created with nsIFile");
+
+f = File.createFromFileName(file.path);
+is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js");
+is(f.mozFullPath, "", "mozFullPath returns blank if created with a string");
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/base/test/chrome/test_bug990812.xul b/dom/base/test/chrome/test_bug990812.xul
new file mode 100644
index 000000000..4be173429
--- /dev/null
+++ b/dom/base/test/chrome/test_bug990812.xul
@@ -0,0 +1,43 @@
+<?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 type="application/javascript"
+ 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.xul",
+ "file_bug990812-2.xul",
+ "file_bug990812-3.xul",
+ "file_bug990812-4.xul",
+ "file_bug990812-5.xul",
+ ];
+
+ function next() {
+ if (tests.length > 0) {
+ var file = tests.shift();
+ info("-- running " + file);
+ window.open(file, "", "chrome");
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ addLoadEvent(next);
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_cpows.xul b/dom/base/test/chrome/test_cpows.xul
new file mode 100644
index 000000000..acbbebb96
--- /dev/null
+++ b/dom/base/test/chrome/test_cpows.xul
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window title="Test CPOWs"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+
+ const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser";
+ SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, false);
+ SimpleTest.registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref(PREF_UNSAFE_FORBIDDEN);
+ });
+
+ function done() {
+ SimpleTest.finish();
+ }
+
+ addLoadEvent(function() {
+ window.open("cpows_parent.xul", "", "chrome");
+ });
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_domparsing.xul b/dom/base/test/chrome/test_domparsing.xul
new file mode 100644
index 000000000..c2a10710d
--- /dev/null
+++ b/dom/base/test/chrome/test_domparsing.xul
@@ -0,0 +1,144 @@
+<?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 type="application/javascript" 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=816410"
+ target="_blank">Mozilla Bug 816410</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript;version=1.7"><![CDATA[
+"use strict";
+/** Test for Bug 816410 **/
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+function throws(a, type, message) {
+ for (let fn of Array.isArray(a) ? a : [a]) {
+ try {
+ fn();
+ ok(false, message);
+ } catch (e) {
+ if (type) {
+ is(e.name, type, message);
+ } else {
+ ok(true, message);
+ }
+ }
+ }
+}
+
+// DOMParser constructor should not throw for null arguments
+new DOMParser(undefined);
+new DOMParser(null);
+
+throws([
+ function() { new DOMParser(false); },
+ function() { new DOMParser(0); },
+ function() { new DOMParser(""); },
+ function() { new DOMParser({}); },
+], "TypeError", "DOMParser constructor should throw for extra arguments");
+
+{
+ let parser = new DOMParser();
+ throws(function() {
+ parser.init();
+ }, "NS_ERROR_UNEXPECTED", "init method should throw when DOMParser is created by constructor");
+}
+
+// XMLSerializer constructor should not throw for extra arguments
+new XMLSerializer(undefined);
+new XMLSerializer(null);
+new XMLSerializer(false);
+new XMLSerializer(0);
+new XMLSerializer("");
+new XMLSerializer({});
+
+runTest(new DOMParser(), new XMLSerializer());
+
+{
+ let parser = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+ parser.init();
+ throws(function() {
+ parser.init();
+ }, "NS_ERROR_UNEXPECTED", "init method should throw when called twice");
+}
+
+runTest(Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser),
+ Cc["@mozilla.org/xmlextras/xmlserializer;1"]
+ .createInstance(Ci.nsIDOMSerializer));
+
+function runTest(parser, serializer) {
+ 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.init, "function", "init 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) {
+ is(serializer.serializeToString(parser.parseFromString(t.input, t.type)), t.expected,
+ "parseFromString test for " + t.type);
+
+ let ostream = {
+ write: function(buf, count) { this.data += buf; return count; }
+ };
+ for (let charset of [null, "UTF-8"]) {
+ ostream.data = "";
+ serializer.serializeToStream(parser.parseFromString(t.input, t.type), ostream, charset);
+ is(ostream.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.map(t.input, function(c) { return c.charCodeAt(c); });
+ let inputs = [
+ {array: 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, a.length, t.type)), t.expected,
+ input.name + " test for " + t.type);
+ throws(function() {
+ parser.parseFromBuffer(a, a.length + 1, t.type);
+ }, "NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY",
+ input.name + " should throw if bufLen parameter is greater than actual length"
+ );
+ }
+
+ 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.xul b/dom/base/test/chrome/test_fileconstructor.xul
new file mode 100644
index 000000000..2399b6ef8
--- /dev/null
+++ b/dom/base/test/chrome/test_fileconstructor.xul
@@ -0,0 +1,72 @@
+<?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 **/
+
+var file = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("CurWorkD", Components.interfaces.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");
+
+doTest(File.createFromFileName(file.path));
+doTest(File.createFromNsIFile(file));
+function doTest(domfile) {
+ ok(domfile instanceof File, "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)");
+}
+
+try {
+ var nonexistentfile = File.createFromFileName("i/sure/hope/this/does/not/exist/anywhere.txt");
+ ok(false, "This should never be reached!");
+} catch (e) {
+ ok(true, "Attempt to construct a non-existent file should fail.");
+}
+
+try {
+ var dir = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties)
+ .get("CurWorkD", Components.interfaces.nsIFile);
+ var dirfile = File.createFromNsIFile(dir);
+ ok(false, "This should never be reached!");
+} catch (e) {
+ ok(true, "Attempt to construct a file from a directory should fail.");
+}
+]]>
+</script>
+
+</window>
diff --git a/dom/base/test/chrome/test_fileconstructor_tempfile.xul b/dom/base/test/chrome/test_fileconstructor_tempfile.xul
new file mode 100644
index 000000000..e2ff3a0d2
--- /dev/null
+++ b/dom/base/test/chrome/test_fileconstructor_tempfile.xul
@@ -0,0 +1,93 @@
+<?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=982874
+
+Tests building a DOMFile with the "temporary" option and checks that
+the underlying file is removed when the DOMFile is gc'ed.
+-->
+<window title="Mozilla Bug 982874"
+ 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=982874">
+ Mozilla Bug 982874</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 982874 **/
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+SimpleTest.waitForExplicitFinish();
+
+function cleanup(tmp) {
+ // Force cycle and garbage collection and check that we removed the file.
+ for (let i = 0; i < 10; i++) {
+ Cu.forceCC();
+ Cu.forceGC();
+ }
+ if (tmp.exists()) {
+ ok(false, "Failed to remove temporary file!");
+ } else {
+ ok(true, "Temporary file removed when gc-ing the DOMFile");
+ }
+ SimpleTest.finish();
+}
+
+try {
+ // Create a file in $TMPDIR/mozilla-temp-files
+ let tmp = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ tmp.append("mozilla-temp-files");
+ tmp.append("test.txt");
+ tmp.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
+
+ // Add some content to the file.
+ let fileData = "I'm a temporary file!";
+ let outStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ outStream.init(tmp, 0x02 | 0x08 | 0x20, // write, create, truncate
+ 0666, 0);
+ outStream.write(fileData, fileData.length);
+ outStream.close();
+
+ // Create a scoped DOMFile so the gc will happily get rid of it.
+ {
+ let dirfile = File.createFromNsIFile(tmp, { temporary: true });
+ ok(true, "Temporary File() created");
+ let reader = new FileReader();
+ reader.readAsArrayBuffer(dirfile);
+ reader.onload = function(event) {
+ let buffer = event.target.result;
+ ok(buffer.byteLength > 0,
+ "Blob size should be > 0 : " + buffer.byteLength);
+ cleanup(tmp);
+ }
+ }
+} catch (e) {
+ ok(false, "Unable to create the File() object : " + e);
+ SimpleTest.finish();
+}
+]]>
+</script>
+
+</window>
diff --git a/dom/base/test/chrome/test_groupedSHistory.xul b/dom/base/test/chrome/test_groupedSHistory.xul
new file mode 100644
index 000000000..23cd4c6ad
--- /dev/null
+++ b/dom/base/test/chrome/test_groupedSHistory.xul
@@ -0,0 +1,32 @@
+<?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=1276553
+-->
+<window title="Mozilla Bug 1276553"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="loadTest();">
+ <script type="application/javascript"
+ 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=1276553"
+ target="_blank">Mozilla Bug 1276553</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for GroupedSHistory **/
+ SimpleTest.waitForExplicitFinish();
+ function loadTest() {
+ window.open("window_groupedSHistory.xul", "", "width=360,height=240,chrome");
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_nsITextInputProcessor.xul b/dom/base/test/chrome/test_nsITextInputProcessor.xul
new file mode 100644
index 000000000..23ad3e249
--- /dev/null
+++ b/dom/base/test/chrome/test_nsITextInputProcessor.xul
@@ -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"?>
+<window title="Testing nsITextInputProcessor behavior"
+ 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">
+<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.open("window_nsITextInputProcessor.xul", "_blank",
+ "chrome,width=600,height=600");
+
+]]>
+</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 000000000..5d8dc5a6e
--- /dev/null
+++ b/dom/base/test/chrome/test_range_getClientRectsAndTexts.html
@@ -0,0 +1,60 @@
+<html>
+<head>
+<meta charset="utf-8">
+<script type="text/javascript" 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\nbre"], 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"},
+ ];
+
+ 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>
+
+</body>
+</html> \ No newline at end of file
diff --git a/dom/base/test/chrome/test_registerElement_content.xul b/dom/base/test/chrome/test_registerElement_content.xul
new file mode 100644
index 000000000..9a918f2d7
--- /dev/null
+++ b/dom/base/test/chrome/test_registerElement_content.xul
@@ -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 type="application/javascript"
+ 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_registerElement_content.html"></iframe>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 1130028 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var createdCallbackCount = 0;
+
+ // Callback should be called once by element created in chrome,
+ // and once by element created in content.
+ function createdCallbackCalled() {
+ createdCallbackCount++;
+ ok(true, "Created callback called, should be called twice in test.");
+ is(this.magicNumber, 42, "Callback should be able to see the custom prototype.");
+ if (createdCallbackCount == 2) {
+ SimpleTest.finish();
+ }
+ }
+
+ function startTests() {
+ var frame = $("frame");
+
+ var c = frame.contentDocument.registerElement("x-foo");
+ var elem = new c();
+ is(elem.tagName, "X-FOO", "Constructor should create an x-foo element.");
+
+ var proto = Object.create(frame.contentWindow.HTMLElement.prototype);
+ proto.magicNumber = 42;
+ proto.createdCallback = createdCallbackCalled;
+ frame.contentDocument.registerElement("x-bar", { prototype: proto });
+
+ frame.contentDocument.createElement("x-bar");
+ }
+
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_registerElement_ep.xul b/dom/base/test/chrome/test_registerElement_ep.xul
new file mode 100644
index 000000000..6f1745268
--- /dev/null
+++ b/dom/base/test/chrome/test_registerElement_ep.xul
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1130028
+-->
+<window title="Mozilla Bug 1130028"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ 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_registerElement_content.html"></iframe>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ Components.utils.import("resource://gre/modules/Services.jsm");
+
+ /** Test for Bug 1130028 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function finishTest(canSeePrototype) {
+ ok(true, "createdCallback called when reigsterElement was called with an extended principal.");
+ ok(canSeePrototype, "createdCallback 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 = Components.utils.Sandbox([frame.contentWindow], { sandboxPrototype: frame.contentWindow });
+ sandbox.finishTest = finishTest;
+ Services.scriptloader.loadSubScript("chrome://mochitests/content/chrome/dom/base/test/chrome/registerElement_ep.js", sandbox);
+ }
+
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_swapFrameLoaders.xul b/dom/base/test/chrome/test_swapFrameLoaders.xul
new file mode 100644
index 000000000..2f69d390f
--- /dev/null
+++ b/dom/base/test/chrome/test_swapFrameLoaders.xul
@@ -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 type="application/javascript" 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.open("window_swapFrameLoaders.xul", "bug1242644",
+ "chrome,width=600,height=600");
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/test_title.xul b/dom/base/test/chrome/test_title.xul
new file mode 100644
index 000000000..6fa145bc6
--- /dev/null
+++ b/dom/base/test/chrome/test_title.xul
@@ -0,0 +1,30 @@
+<?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 type="application/javascript"
+ 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.open("title_window.xul", "bug481777",
+ "chrome,width=100,height=100");
+ ]]>
+ </script>
+</window>
diff --git a/dom/base/test/chrome/test_windowroot.xul b/dom/base/test/chrome/test_windowroot.xul
new file mode 100644
index 000000000..c9ff7f0b5
--- /dev/null
+++ b/dom/base/test/chrome/test_windowroot.xul
@@ -0,0 +1,19 @@
+<?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 type="application/javascript"
+ 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(root instanceof WindowRoot, "windowRoot should be a WindowRoot");
+ ]]></script>
+</window>
diff --git a/dom/base/test/chrome/title_window.xul b/dom/base/test/chrome/title_window.xul
new file mode 100644
index 000000000..5db180f8b
--- /dev/null
+++ b/dom/base/test/chrome/title_window.xul
@@ -0,0 +1,198 @@
+<?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="data:application/vnd.mozilla.xul+xml,&lt;window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' title='Test'/>"/>
+ <iframe id="xul2" src="data:application/vnd.mozilla.xul+xml,&lt;window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' title='Test'/>"/>
+ <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.opener.wrappedJSObject[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);
+ var done = false;
+
+ 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, false);
+ frame.contentDocument.addEventListener("DOMTitleChanged", listener2, false);
+ frame.contentWindow.addEventListener("DOMTitleChanged", listener3, false);
+
+ 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.parentNode.removeChild(t);
+ } ],
+ [ "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.parentNode.removeChild(t);
+ } ] ];
+
+ 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_groupedSHistory.xul b/dom/base/test/chrome/window_groupedSHistory.xul
new file mode 100644
index 000000000..aafa991ba
--- /dev/null
+++ b/dom/base/test/chrome/window_groupedSHistory.xul
@@ -0,0 +1,343 @@
+<?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=1276553
+-->
+<window title="Mozilla Bug 1276553"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="run();">
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
+ Cu.import("resource://testing-common/TestUtils.jsm");
+ Cu.import("resource://testing-common/ContentTask.jsm");
+ Cu.import("resource://testing-common/BrowserTestUtils.jsm");
+ Cu.import("resource://gre/modules/Task.jsm");
+ ContentTask.setTestScope(window.opener.wrappedJSObject);
+
+ let imports = ['SimpleTest', 'SpecialPowers', 'ok', 'is', 'info'];
+ for (let name of imports) {
+ window[name] = window.opener.wrappedJSObject[name];
+ }
+
+ /** Test for Bug 1276553 **/
+ function run() {
+ new Promise(resolve => SpecialPowers.pushPrefEnv(
+ {'set' : [[ 'browser.groupedhistory.enabled', true ]]}, resolve))
+ .then(() => test(false))
+ .then(() => test(true))
+ .then(() => {
+ window.close();
+ SimpleTest.finish();
+ });
+ }
+
+ function test(remote) {
+ let act, bg1, bg2;
+ return Promise.resolve()
+
+ // create first browser with 1 entry (which will always be the active one)
+ .then(() => info('TEST-INFO | test create active browser, remote=' + remote))
+ .then(() => createBrowser('pen', remote))
+ .then(b => act = b)
+ .then(() => verifyBrowser(act, 'pen' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ false /* canGoBack */,
+ false /* canGoForward */,
+ false /* partial */ ))
+
+ // create background browser 1 with 1 entry
+ .then(() => info('TEST-INFO | test create background browser 1, remote=' + remote))
+ .then(() => createBrowser('pineapple', remote))
+ .then(b => bg1 = b)
+ .then(() => verifyBrowser(bg1, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ false /* canGoBack */,
+ false /* canGoForward */,
+ false /* partial */ ))
+
+ // create background browser 2 with 2 entries
+ .then(() => info('TEST-INFO | test create background browser 2, remote=' + remote))
+ .then(() => createBrowser('apple', remote))
+ .then(b => bg2 = b)
+ .then(() => verifyBrowser(bg2, 'apple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ false /* canGoBack */,
+ false /* canGoForward */,
+ false /* partial */ ))
+ .then(() => loadURI(bg2, getDummyHtml('pencil')))
+ .then(() => verifyBrowser(bg2, 'pencil' /* title */,
+ 1 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ false /* partial */ ))
+
+ // merge to 2 entries pen-pineapple
+ .then(() => info('TEST-INFO | test merge history, remote=' + remote))
+ .then(() => mergeHistory(act, bg1))
+ .then(() => verifyBrowser(act, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 2 /* globalLength */ ))
+
+ // merge to 4 entries pen-pineapple-apple-pencil
+ .then(() => mergeHistory(act, bg2))
+ .then(() => verifyBrowser(act, 'pencil' /* title */,
+ 1 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+
+ // test go back
+ .then(() => info('TEST-INFO | test history go back, remote=' + remote))
+ .then(() => wrapHistoryNavFn(act, act.goBack.bind(act)))
+ .then(() => verifyBrowser(act, 'apple' /* title */,
+ 0 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+ // XXX The 2nd pageshow comes from reload as current index of the active
+ // partial history remains the same
+ .then(() => wrapHistoryNavFn(act, act.goBack.bind(act), true))
+ .then(() => verifyBrowser(act, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 4 /* globalLength */ ))
+ .then(() => wrapHistoryNavFn(act, act.goBack.bind(act), true))
+ .then(() => verifyBrowser(act, 'pen' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ false /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 0 /* offset */,
+ 4 /* globalLength */ ))
+
+ // test go forward
+ .then(() => info('TEST-INFO | test history go forward, remote=' + remote))
+ .then(() => wrapHistoryNavFn(act, act.goForward.bind(act), true))
+ .then(() => verifyBrowser(act, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 4 /* globalLength */ ))
+ .then(() => wrapHistoryNavFn(act, act.goForward.bind(act), true))
+ .then(() => verifyBrowser(act, 'apple' /* title */,
+ 0 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+ .then(() => wrapHistoryNavFn(act, act.goForward.bind(act)))
+ .then(() => verifyBrowser(act, 'pencil' /* title */,
+ 1 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+
+ // test goto index
+ .then(() => info('TEST-INFO | test history goto index, remote=' + remote))
+ .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 0), true))
+ .then(() => verifyBrowser(act, 'pen' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ false /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 0 /* offset */,
+ 4 /* globalLength */ ))
+ // expect 2 pageshow since we're also changing mIndex of the partial history
+ .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 2), true, 2))
+ .then(() => verifyBrowser(act, 'apple' /* title */,
+ 0 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+ .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 1), true))
+ .then(() => verifyBrowser(act, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 4 /* globalLength */ ))
+ // expect 2 pageshow since we're also changing mIndex of the partial history
+ .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 3), true, 2))
+ .then(() => verifyBrowser(act, 'pencil' /* title */,
+ 1 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ true /* partial */,
+ 2 /* offset */,
+ 4 /* globalLength */ ))
+
+ // test history change to 3 entries pen-pineapple-banana
+ .then(() => info('TEST-INFO | test history change, remote=' + remote))
+ .then(() => wrapHistoryNavFn(act, act.gotoIndex.bind(act, 1), true))
+ .then(() => verifyBrowser(act, 'pineapple' /* title */,
+ 0 /* index */,
+ 1 /* length */,
+ true /* canGoBack */,
+ true /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 4 /* globalLength */ ))
+ .then(() => loadURI(act, getDummyHtml('banana')))
+ .then(() => verifyBrowser(act, 'banana' /* title */,
+ 1 /* index */,
+ 2 /* length */,
+ true /* canGoBack */,
+ false /* canGoForward */,
+ true /* partial */,
+ 1 /* offset */,
+ 3 /* globalLength */ ))
+ }
+
+ function getDummyHtml(title) {
+ return 'data:text/html;charset=UTF-8,' +
+ '<html><head><title>' + title + '</title></head></html>'
+ }
+
+ function createBrowser(title, remote) {
+ let browser = document.createElement('browser');
+ browser.setAttribute('type', 'content');
+ browser.setAttribute('remote', remote);
+ browser.setAttribute('src', getDummyHtml(title));
+ document.getElementById('stack').appendChild(browser);
+ return BrowserTestUtils.browserLoaded(browser)
+ .then(() => {
+ browser.messageManager.loadFrameScript('data:,' +
+ 'addEventListener("pageshow", () => sendAsyncMessage("test:pageshow", null), false);' +
+ 'addEventListener("pagehide", () => sendAsyncMessage("test:pagehide", null), false);',
+ true);
+ })
+ .then(() => {
+ // a trick to ensure webProgress object is created for e10s case
+ ok(browser.webProgress, 'check browser.webProgress exists');
+ return browser;
+ });
+ }
+
+ function loadURI(browser, uri) {
+ let promise = BrowserTestUtils.browserLoaded(browser, false, uri);
+ browser.loadURI(uri);
+ return promise;
+ }
+
+ function mergeHistory(b1, b2) {
+ let promises = [];
+ let pagehide1, pagehide2;
+
+ // For swapping there should be a pagehide followed by a pageshow.
+ promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'test:pagehide', msg => pagehide1 = true));
+ promises.push(BrowserTestUtils.waitForMessage(b2.messageManager, 'test:pagehide', msg => pagehide2 = true));
+ promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'test:pageshow', msg => pagehide1));
+ promises.push(BrowserTestUtils.waitForMessage(b2.messageManager, 'test:pageshow', msg => pagehide2));
+
+ // For swapping remote browsers, we'll also receive Content:LocationChange
+ if (b1.isRemoteBrowser) {
+ promises.push(BrowserTestUtils.waitForMessage(b1.messageManager, 'Content:LocationChange'));
+ }
+
+ promises.push(Promise.resolve().then(() => {
+ let f1 = b1.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+ let f2 = b2.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+ f1.appendPartialSessionHistoryAndSwap(f2);
+ }));
+
+ return Promise.all(promises);
+ }
+
+ function wrapHistoryNavFn(browser, navFn, expectSwap = false, expectPageshowCount = 1) {
+ let promises = [];
+ let pagehide = false;
+ let pageshowCount = 0;
+
+ if (expectSwap) {
+ // For swapping there should be a pagehide followed by a pageshow.
+ promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
+ 'test:pagehide', msg => pagehide = true));
+
+ // For swapping remote browsers, we'll also receive Content:LocationChange
+ if (browser.isRemoteBrowser) {
+ promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
+ 'Content:LocationChange'));
+ }
+ }
+ promises.push(BrowserTestUtils.waitForMessage(browser.messageManager,
+ 'test:pageshow', msg => {
+ // Only count events after pagehide for swapping case.
+ if (!expectSwap || pagehide) {
+ return !--expectPageshowCount;
+ }
+ return false;
+ }));
+ promises.push(Task.spawn(navFn));
+
+ return Promise.all(promises);
+ }
+
+ function verifyBrowser(browser, title, index, length, canGoBack, canGoForward,
+ partial, offset = 0, globalLength = length) {
+ is(browser.canGoBack, canGoBack, 'check browser.canGoBack');
+ is(browser.canGoForward, canGoForward, 'check browser.canGoForward');
+ if (partial) {
+ let frameLoader = browser.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+ is(frameLoader.groupedSessionHistory.count, globalLength, 'check groupedSHistory.count');
+ }
+
+ return ContentTask.spawn(browser,
+ { title, index, length, canGoBack, canGoForward, partial, offset, globalLength },
+ ({ title, index, length, canGoBack, canGoForward, partial, offset, globalLength }) => {
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ let shistory = webNav.sessionHistory;
+ is(content.document.title, title, 'check title');
+ is(webNav.canGoBack, canGoBack, 'check webNav.canGoBack');
+ is(webNav.canGoForward, canGoForward, 'check webNav.canGoForward');
+ is(shistory.index, index, 'check shistory.index');
+ is(shistory.count, length, 'check shistory.count');
+ is(shistory.isPartial, partial, 'check shistory.isPartial');
+ is(shistory.globalIndexOffset, offset, 'check shistory.globalIndexOffset');
+ is(shistory.globalCount, globalLength, 'check shistory.globalCount');
+ });
+ }
+
+ ]]>
+ </script>
+ <stack id="stack" flex="1" />
+</window>
diff --git a/dom/base/test/chrome/window_nsITextInputProcessor.xul b/dom/base/test/chrome/window_nsITextInputProcessor.xul
new file mode 100644
index 000000000..ce6bfe3db
--- /dev/null
+++ b/dom/base/test/chrome/window_nsITextInputProcessor.xul
@@ -0,0 +1,4027 @@
+<?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 type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+<input id="input" type="text"/><br/>
+<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/>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+var SpecialPowers = window.opener.wrappedJSObject.SpecialPowers;
+var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
+
+SimpleTest.waitForFocus(runTests, window);
+
+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 finish()
+{
+ window.close();
+}
+
+function onunload()
+{
+ SimpleTest.finish();
+}
+
+var iframe = document.getElementById("iframe");
+var childWindow = iframe.contentWindow;
+var textareaInFrame;
+var input = document.getElementById("input");
+var otherWindow = window.opener;
+var otherDocument = otherWindow.document;
+var inputInChildWindow = otherDocument.getElementById("input");
+
+function createTIP()
+{
+ return Components.classes["@mozilla.org/text-input-processor;1"].
+ createInstance(Components.interfaces.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 be committed with the last composing string");
+
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.startComposition();");
+ }, false);
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 4,
+ description + "compositionstart, compositionupdate, text 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, "input",
+ description + "events[3] should be input");
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ TIP1.commitComposition();
+ is(events.length, 3,
+ description + "text, 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, "compositionend",
+ description + "events[1] should be compositionend");
+ is(events[2].type, "input",
+ description + "events[2] should be input");
+
+ // Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during text event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during compositionend event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransaction(window, simpleCallback),
+ description + "TIP2 shouldn't be able to begin input transaction during input event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, 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, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ TIP1.cancelComposition();
+ is(events.length, 4,
+ description + "compositionupdate, text, 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, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 4,
+ description + "keydown, keypress, 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, "input",
+ description + "events[2] should be input");
+ is(events[3].type, "keyup",
+ description + "events[3] should be keyup");
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during startComposition().
+ var events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.startComposition();");
+ }, false);
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ TIP1.beginInputTransactionForTests(window);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 4,
+ description + "compositionstart, compositionupdate, text 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, "input",
+ description + "events[3] should be input");
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ TIP1.commitComposition();
+ is(events.length, 3,
+ description + "text, 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, "compositionend",
+ description + "events[1] should be compositionend");
+ is(events[2].type, "input",
+ description + "events[2] should be input");
+
+ // Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
+ events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests from compositionstart event handler during TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during compositionupdate event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during text event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during compositionend event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ ok(!TIP2.beginInputTransactionForTests(window),
+ description + "TIP2 shouldn't be able to begin input transaction for tests during input event handler TIP1.commitCompositionWith(\"bar\");");
+ }, false);
+ TIP1.beginInputTransactionForTests(window);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, 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, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ TIP1.cancelComposition();
+ is(events.length, 4,
+ description + "compositionupdate, text, 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, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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();");
+ }, false);
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 4,
+ description + "keydown, keypress, 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, "input",
+ description + "events[2] should be input");
+ is(events[3].type, "keyup",
+ description + "events[3] should be keyup");
+
+ // Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition().
+ var events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 4,
+ description + "compositionstart, compositionupdate, text 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, "input",
+ description + "events[3] should be input");
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.commitComposition();
+ is(events.length, 3,
+ description + "text, 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, "compositionend",
+ description + "events[1] should be compositionend");
+ is(events[2].type, "input",
+ description + "events[2] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ TIP1.beginInputTransaction(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, 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, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.cancelComposition();
+ is(events.length, 4,
+ description + "compositionupdate, text, 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, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 4,
+ description + "keydown, keypress, 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, "input",
+ description + "events[2] should be input");
+ is(events[3].type, "keyup",
+ description + "events[3] should be keyup");
+
+ // Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during startComposition().
+ var events = [];
+ input.addEventListener("compositionstart", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.setPendingCompositionString(composingStr);
+ TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+ TIP1.flushPendingComposition();
+ is(events.length, 4,
+ description + "compositionstart, compositionupdate, text 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, "input",
+ description + "events[3] should be input");
+ 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.commitComposition();
+ is(events.length, 3,
+ description + "text, 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, "compositionend",
+ description + "events[1] should be compositionend");
+ is(events[2].type, "input",
+ description + "events[2] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("compositionupdate", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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\")");
+ }
+ }, false);
+ TIP1.beginInputTransactionForTests(window, simpleCallback);
+ TIP1.commitCompositionWith("bar");
+ is(events.length, 5,
+ description + "compositionstart, compositionupdate, text, 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, "compositionend",
+ description + "events[3] should be compositionend");
+ is(events[4].type, "input",
+ description + "events[4] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("text", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("compositionend", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ TIP1.cancelComposition();
+ is(events.length, 4,
+ description + "compositionupdate, text, 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, "compositionend",
+ description + "events[2] should be compositionend");
+ is(events[3].type, "input",
+ description + "events[3] should be input");
+
+ // 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);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("keypress", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("input", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ input.addEventListener("keyup", function (aEvent) {
+ events.push(aEvent);
+ input.removeEventListener(aEvent.type, arguments.callee, false);
+ 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()");
+ }
+ }, false);
+ var keyA = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
+ TIP1.keydown(keyA);
+ TIP1.keyup(keyA);
+ is(events.length, 4,
+ description + "keydown, keypress, 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, "input",
+ description + "events[2] should be input");
+ is(events[3].type, "keyup",
+ description + "events[3] 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 {
+ var 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 {
+ var 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.
+ SpecialPowers.gc();
+
+ 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, false);
+ window.addEventListener("compositionupdate", handler, false);
+ window.addEventListener("compositionend", handler, false);
+
+ 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 shoudn'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, false);
+ window.removeEventListener("compositionupdate", handler, false);
+ window.removeEventListener("compositionend", handler, false);
+}
+
+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, false);
+ window.addEventListener("compositionupdate", handler, false);
+ window.addEventListener("compositionend", handler, false);
+ window.addEventListener("keydown", handler, false);
+ window.addEventListener("keypress", handler, false);
+ window.addEventListener("keyup", handler, false);
+
+ 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 });
+
+ SpecialPowers.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");
+
+ SpecialPowers.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)");
+
+ SpecialPowers.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+
+ window.removeEventListener("compositionstart", handler, false);
+ window.removeEventListener("compositionupdate", handler, false);
+ window.removeEventListener("compositionend", handler, false);
+ window.removeEventListener("keydown", handler, false);
+ window.removeEventListener("keypress", handler, false);
+ window.removeEventListener("keyup", handler, false);
+}
+
+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, false);
+ window.addEventListener("compositionupdate", handler, false);
+ window.addEventListener("compositionend", handler, false);
+ window.addEventListener("keydown", handler, false);
+ window.addEventListener("keypress", handler, false);
+ window.addEventListener("keyup", handler, false);
+
+ 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 });
+
+ SpecialPowers.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");
+
+ SpecialPowers.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");
+
+ SpecialPowers.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+
+ window.removeEventListener("compositionstart", handler, false);
+ window.removeEventListener("compositionupdate", handler, false);
+ window.removeEventListener("compositionend", handler, false);
+ window.removeEventListener("keydown", handler, false);
+ window.removeEventListener("keypress", handler, false);
+ window.removeEventListener("keyup", handler, false);
+}
+
+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.indexOf(aEvent.type) >= 0) {
+ 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");
+ if (SpecialPowers.getBoolPref("dom.keyboardevent.code.enabled")) {
+ 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, false);
+ window.addEventListener("keypress", handler, false);
+ window.addEventListener("keyup", handler, false);
+
+ 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\"");
+
+ // 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 {
+ SpecialPowers.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");
+
+ SpecialPowers.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();
+ SpecialPowers.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: "OSLeft", location: KeyboardEvent.DOM_KEY_LOCATION_LEFT },
+ { code: "OSRight", 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 (var i = 0; i < kCodeToLocation.length; i++) {
+ var 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);
+ var longDesc = description + "testing computation of .location of \"" + kCodeToLocation[i].code + "\", ";
+ is(events.length, 3,
+ longDesc + "keydown, keypress and keyup events should be fired");
+ for (var 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);
+ var 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 (var 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.
+ var 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 (var 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: "OS", keyCode: KeyboardEvent.DOM_VK_WIN, isModifier: true },
+ { 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: KeyboardEvent.DOM_VK_META, 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 (var i = 0; i < kKeyToKeyCode.length; i++) {
+ var 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);
+ var 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 (var 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);
+ var 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 (var 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.
+ var 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 (var 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: "OSLeft", isLockable: false },
+ { key: "Meta", code: "OSRight", 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 },
+ { key: "OS", code: "OSLeft", isLockable: false },
+ { key: "OS", code: "OSRight", isLockable: false },
+ ];
+
+ 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");
+ if (SpecialPowers.getBoolPref("dom.keyboardevent.code.enabled")) {
+ is(aEvent.code, aCode,
+ desc + ", .code value is wrong");
+ }
+ is(aEvent.altKey, aModifiers.indexOf("Alt") >= 0,
+ desc + ", .altKey value is wrong");
+ is(aEvent.ctrlKey, aModifiers.indexOf("Control") >= 0,
+ desc + ", .ctrlKey value is wrong");
+ is(aEvent.metaKey, aModifiers.indexOf("Meta") >= 0,
+ desc + ", .metaKey value is wrong");
+ is(aEvent.shiftKey, aModifiers.indexOf("Shift") >= 0,
+ desc + ", .shiftKey value is wrong");
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(aEvent.getModifierState(kModifiers[i]), aModifiers.indexOf(kModifiers[i]) >= 0,
+ desc + ", .getModifierState(\"" + kModifiers[i] + "\") returns wrong value");
+ }
+ }
+
+ function checkAllTIPModifiers(aTestDesc, aModifiers)
+ {
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(TIP.getModifierState(kModifiers[i]), aModifiers.indexOf(kModifiers[i]) >= 0,
+ 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 });
+ var 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: "" });
+
+ var 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)
+ {
+ for (var i = 0; i < kModifiers.length; i++) {
+ is(aTIP.getModifierState(kModifiers[i]), aModifiers.indexOf(kModifiers[i]) >= 0,
+ 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, false);
+ window.removeEventListener("keypress", handler, false);
+ window.removeEventListener("keyup", handler, false);
+}
+
+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 {
+ var 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 {
+ var 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 {
+ var 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 {
+ var 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 {
+ var 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 {
+ var 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 {
+ var 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 {
+ var 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 {
+ var 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 {
+ var 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(aNextTest)
+{
+ var description = "runUnloadTests1(): ";
+
+ var TIP1 = createTIP();
+ ok(TIP1.beginInputTransactionForTests(childWindow),
+ description + "TIP1.beginInputTransactionForTests() should succeed");
+
+ var oldSrc = iframe.src;
+ var parentWindow = window;
+
+ iframe.addEventListener("load", function (aEvent) {
+ ok(true, description + "dummy page is loaded");
+ iframe.removeEventListener("load", arguments.callee, true);
+ 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.
+ iframe.removeEventListener("load", arguments.callee, true);
+ childWindow = iframe.contentWindow;
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+ setTimeout(aNextTest, 0);
+ }, true);
+
+ // The composition should be committed internally. So, another TIP should
+ // be able to steal the rights to using TextEventDispatcher.
+ var 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;
+ }, 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(aNextTest)
+{
+ var description = "runUnloadTests2(): ";
+
+ var TIP = createTIP();
+ ok(TIP.beginInputTransactionForTests(childWindow),
+ description + "TIP.beginInputTransactionForTests() should succeed");
+
+ var oldSrc = iframe.src;
+ var parentWindow = window;
+
+ iframe.addEventListener("load", function (aEvent) {
+ ok(true, description + "dummy page is loaded");
+ iframe.removeEventListener("load", arguments.callee, true);
+ 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.
+ iframe.removeEventListener("load", arguments.callee, true);
+ childWindow = iframe.contentWindow;
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+ setTimeout(aNextTest, 0);
+ }, 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;
+ }, 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>";
+}
+
+function runCallbackTests(aForTests)
+{
+ var description = "runCallbackTests(aForTests=" + aForTests + "): ";
+
+ input.value = "";
+ input.focus();
+ input.blur();
+
+ var TIP = createTIP();
+ var notifications = [];
+ function callback(aTIP, aNotification)
+ {
+ switch (aNotification.type) {
+ case "request-to-commit":
+ aTIP.commitComposition();
+ break;
+ case "request-to-cancel":
+ aTIP.cancelComposition();
+ break;
+ }
+ if (aTIP == TIP) {
+ notifications.push(aNotification);
+ }
+ return true;
+ }
+
+ function dumpUnexpectedNotifications(aExpectedCount)
+ {
+ if (notifications.length <= aExpectedCount) {
+ return;
+ }
+ for (var i = aExpectedCount; i < notifications.length; i++) {
+ ok(false,
+ description + "Unexpected notification: " + notifications[i].type);
+ }
+ }
+
+ 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();
+ TIP.setPendingCompositionString("foo");
+ TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+ TIP.flushPendingComposition();
+ notifications = [];
+ synthesizeMouseAtCenter(input, {});
+ is(notifications.length, 1,
+ description + "synthesizeMouseAtCenter(input, {}) during composition should cause a notification");
+ is(notifications[0].type, "request-to-commit",
+ description + "synthesizeMouseAtCenter(input, {}) during composition should cause \"request-to-commit\"");
+ dumpUnexpectedNotifications(1);
+
+ notifications = [];
+ var 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);
+}
+
+function runTests()
+{
+ textareaInFrame = iframe.contentDocument.getElementById("textarea");
+
+ runBeginInputTransactionMethodTests();
+ runReleaseTests();
+ runCompositionTests();
+ runCompositionWithKeyEventTests();
+ runConsumingKeydownBeforeCompositionTests();
+ runKeyTests();
+ runErrorTests();
+ runCommitCompositionTests();
+ runCallbackTests(false);
+ runCallbackTests(true);
+ runUnloadTests1(function () {
+ runUnloadTests2(function () {
+ finish();
+ });
+ });
+}
+
+]]>
+</script>
+
+</window>
diff --git a/dom/base/test/chrome/window_swapFrameLoaders.xul b/dom/base/test/chrome/window_swapFrameLoaders.xul
new file mode 100644
index 000000000..3deae2e63
--- /dev/null
+++ b/dom/base/test/chrome/window_swapFrameLoaders.xul
@@ -0,0 +1,258 @@
+<?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" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+
+ <script type="application/javascript"><![CDATA[
+ ["SimpleTest", "SpecialPowers", "info", "is", "ok"].forEach(key => {
+ window[key] = window.opener[key];
+ })
+ const { interfaces: Ci } = Components;
+
+ 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"],
+ ["xul", "html", "remote"],
+ ["html", "xul", "remote"],
+ ["html", "html", "remote"],
+ ];
+
+ const HEIGHTS = [
+ 200,
+ 400
+ ];
+
+ function frameScript() {
+ 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;
+ }
+ }
+ });
+ }
+
+ function* addFrame(type, remote, height) {
+ let frame = document.createElementNS(NS[type], TAG[type]);
+ frame.setAttribute("remote", remote);
+ if (remote && type == "xul") {
+ frame.setAttribute("style", "-moz-binding: none;");
+ }
+ 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;
+ yield once(mm, "test:load");
+ return frame;
+ }
+
+ add_task(function*() {
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ { "set": [["dom.mozBrowserFramesEnabled", true],
+ ["network.disable.ipc.security", true]] },
+ resolve);
+ });
+ });
+
+ add_task(function*() {
+ for (let scenario of SCENARIOS) {
+ let [ typeA, typeB, remote ] = scenario;
+ remote = !!remote;
+ let heightA = HEIGHTS[0];
+ info(`Adding frame A, type ${typeA}, remote ${remote}, height ${heightA}`);
+ let frameA = yield addFrame(typeA, remote, heightA);
+
+ let heightB = HEIGHTS[1];
+ info(`Adding frame B, type ${typeB}, remote ${remote}, height ${heightB}`);
+ let frameB = yield addFrame(typeB, remote, heightB);
+
+ let frameScriptFactory = function(name) {
+ 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,
+ });
+ });
+ }`;
+ }
+
+ // 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 } ] = yield 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 } ] = yield inflightB;
+ is(pongB, "B", "Frame B message manager gets reply B before swap");
+ }
+
+ // Check height before swap
+ {
+ if (frameA.getContentDimensions) {
+ let { height } = yield frameA.getContentDimensions();
+ is(height, heightA, "Frame A's content height is 200px before swap");
+ }
+ if (frameB.getContentDimensions) {
+ let { height } = yield frameB.getContentDimensions();
+ is(height, heightB, "Frame B's content height is 400px before swap");
+ }
+ }
+
+ // Ping after swap using message managers acquired before
+ {
+ let mmA = frameA.frameLoader.messageManager;
+ let mmB = frameB.frameLoader.messageManager;
+
+ info("swapFrameLoaders");
+ frameA.swapFrameLoaders(frameB);
+
+ let inflightA = once(mmA, "pong");
+ let inflightB = once(mmB, "pong");
+
+ info("Ping message manager for frame A");
+ mmA.sendAsyncMessage("ping");
+ let [ { data: pongA } ] = yield 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 } ] = yield 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 } = yield frameA.getContentDimensions();
+ is(height, heightB, "Frame A's content height is 400px after swap");
+ }
+ if (frameB.getContentDimensions) {
+ let { height } = yield 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 } ] = yield 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 } ] = yield inflightB;
+ is(pongB, "A", "Frame B message manager acquired after swap gets reply A after swap");
+ }
+
+ // Verify browser API frame scripts destroyed if swapped out of browser frame
+ if (frameA.hasAttribute("mozbrowser") != frameB.hasAttribute("mozbrowser")) {
+ let mmA = frameA.frameLoader.messageManager;
+ let mmB = frameB.frameLoader.messageManager;
+
+ let inflightA = once(mmA, "check-browser-api");
+ let inflightB = once(mmB, "check-browser-api");
+
+ info("Check browser API for frame A");
+ mmA.sendAsyncMessage("check-browser-api");
+ let [ { data: apiA } ] = yield inflightA;
+ if (frameA.hasAttribute("mozbrowser")) {
+ ok(apiA.exists && apiA.running, "Frame A browser API exists and is running");
+ } else {
+ ok(apiA.exists && !apiA.running, "Frame A browser API did exist but is now destroyed");
+ }
+
+ info("Check browser API for frame B");
+ mmB.sendAsyncMessage("check-browser-api");
+ let [ { data: apiB } ] = yield inflightB;
+ if (frameB.hasAttribute("mozbrowser")) {
+ ok(apiB.exists && apiB.running, "Frame B browser API exists and is running");
+ } else {
+ ok(apiB.exists && !apiB.running, "Frame B browser API did exist but is now destroyed");
+ }
+ } else {
+ info("Frames have matching mozbrowser state, skipping browser API destruction check");
+ }
+
+ frameA.remove();
+ frameB.remove();
+ }
+ });
+ ]]></script>
+</window>