diff options
Diffstat (limited to 'dom/base/test/chrome')
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 Binary files differnew file mode 100644 index 000000000..51e8aaf38 --- /dev/null +++ b/dom/base/test/chrome/fileconstructor_file.png 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,<html><head><title id='t'>Test</title></head></html>"/> + <iframe type="content" id="html2" src="data:text/html,<html><head><title id='t'>Test</title><title>Foo</title></head></html>"/> + <iframe type="content" id="html3" src="data:text/html,<html></html>"/> + <iframe type="content" id="xhtml1" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'><body><title id='t'>Test</title></body></html>"/> + <iframe type="content" id="xhtml2" src="data:text/xml,<title xmlns='http://www.w3.org/1999/xhtml'>Test</title>"/> + <iframe type="content" id="xhtml3" src="data:text/xml,<title xmlns='http://www.w3.org/1999/xhtml'>Te<div>bogus</div>st</title>"/> + <iframe type="content" id="xhtml4" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'/>"/> + <iframe type="content" id="xhtml5" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'><head/></html>"/> + <iframe type="content" id="xhtml6" src="data:text/xml,<html xmlns='http://www.w3.org/1999/xhtml'><head><style/></head></html>"/> + <iframe id="xul1" src="data:application/vnd.mozilla.xul+xml,<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' title='Test'/>"/> + <iframe id="xul2" src="data:application/vnd.mozilla.xul+xml,<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul' title='Test'/>"/> + <iframe type="content" id="svg1" src="data:text/xml,<svg xmlns='http://www.w3.org/2000/svg'><title id='t'>Test</title></svg>"/> + <iframe type="content" id="svg2" src="data:text/xml,<svg xmlns='http://www.w3.org/2000/svg'><title id='t'>Test</title></svg>"/> + + <script type="application/javascript"> + <![CDATA[ + var imports = [ "SimpleTest", "is", "isnot", "ok" ]; + for (var name of imports) { + window[name] = window.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,<textarea id='textarea' cols='20' rows='4'></textarea>"></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> |