diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /toolkit/content/tests/widgets | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/content/tests/widgets')
44 files changed, 5041 insertions, 0 deletions
diff --git a/toolkit/content/tests/widgets/.eslintrc.js b/toolkit/content/tests/widgets/.eslintrc.js new file mode 100644 index 000000000..e14919375 --- /dev/null +++ b/toolkit/content/tests/widgets/.eslintrc.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../testing/mochitest/mochitest.eslintrc.js", + "../../../../testing/mochitest/chrome.eslintrc.js" + ] +}; diff --git a/toolkit/content/tests/widgets/audio.ogg b/toolkit/content/tests/widgets/audio.ogg Binary files differnew file mode 100644 index 000000000..a553c23e7 --- /dev/null +++ b/toolkit/content/tests/widgets/audio.ogg diff --git a/toolkit/content/tests/widgets/audio.wav b/toolkit/content/tests/widgets/audio.wav Binary files differnew file mode 100644 index 000000000..c6fd5cb86 --- /dev/null +++ b/toolkit/content/tests/widgets/audio.wav diff --git a/toolkit/content/tests/widgets/chrome.ini b/toolkit/content/tests/widgets/chrome.ini new file mode 100644 index 000000000..841b86c0f --- /dev/null +++ b/toolkit/content/tests/widgets/chrome.ini @@ -0,0 +1,20 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + tree_shared.js + popup_shared.js + window_menubar.xul + seek_with_sound.ogg + +[test_contextmenu_nested.xul] +skip-if = os == 'linux' # Bug 1116215 +[test_contextmenu_menugroup.xul] +skip-if = os == 'linux' # Bug 1115088 +[test_editor_currentURI.xul] +[test_menubar.xul] +skip-if = os == 'mac' +[test_popupanchor.xul] +skip-if = os == 'android' +[test_popupreflows.xul] +[test_tree_column_reorder.xul] +[test_videocontrols_onclickplay.html] diff --git a/toolkit/content/tests/widgets/head.js b/toolkit/content/tests/widgets/head.js new file mode 100644 index 000000000..c2ae0c7ae --- /dev/null +++ b/toolkit/content/tests/widgets/head.js @@ -0,0 +1,23 @@ +"use strict"; + +function waitForCondition(condition, nextTest, errorMsg) { + var tries = 0; + var interval = setInterval(function() { + if (tries >= 30) { + ok(false, errorMsg); + moveOn(); + } + var conditionPassed; + try { + conditionPassed = condition(); + } catch (e) { + ok(false, e + "\n" + e.stack); + conditionPassed = false; + } + if (conditionPassed) { + moveOn(); + } + tries++; + }, 100); + var moveOn = function() { clearInterval(interval); nextTest(); }; +} diff --git a/toolkit/content/tests/widgets/mochitest.ini b/toolkit/content/tests/widgets/mochitest.ini new file mode 100644 index 000000000..abc77c80b --- /dev/null +++ b/toolkit/content/tests/widgets/mochitest.ini @@ -0,0 +1,40 @@ +[DEFAULT] +support-files = + audio.wav + audio.ogg + seek_with_sound.ogg + head.js + tree_shared.js + videocontrols_direction-1-ref.html + videocontrols_direction-1a.html + videocontrols_direction-1b.html + videocontrols_direction-1c.html + videocontrols_direction-1d.html + videocontrols_direction-1e.html + videocontrols_direction-2-ref.html + videocontrols_direction-2a.html + videocontrols_direction-2b.html + videocontrols_direction-2c.html + videocontrols_direction-2d.html + videocontrols_direction-2e.html + videocontrols_direction_test.js + videomask.css + +[test_audiocontrols_dimensions.html] +skip-if = toolkit == 'android' +[test_mousecapture_area.html] +[test_videocontrols.html] +tags = fullscreen +skip-if = toolkit == 'android' #TIMED_OUT +[test_videocontrols_vtt.html] +skip-if = toolkit == 'android' +[test_videocontrols_iframe_fullscreen.html] +[test_videocontrols_audio.html] +[test_videocontrols_audio_direction.html] +[test_videocontrols_jsdisabled.html] +skip-if = toolkit == 'android' # bug 1272646 +[test_videocontrols_standalone.html] +skip-if = true # bug 1075573, bug 1262130 +[test_videocontrols_video_direction.html] +skip-if = os == 'win' +[test_bug898940.html] diff --git a/toolkit/content/tests/widgets/popup_shared.js b/toolkit/content/tests/widgets/popup_shared.js new file mode 100644 index 000000000..49735c5ad --- /dev/null +++ b/toolkit/content/tests/widgets/popup_shared.js @@ -0,0 +1,424 @@ +/* + * This script is used for menu and popup tests. Call startPopupTests to start + * the tests, passing an array of tests as an argument. Each test is an object + * with the following properties: + * testname - name of the test + * test - function to call to perform the test + * events - a list of events that are expected to be fired in sequence + * as a result of calling the 'test' function. This list should be + * an array of strings of the form "eventtype targetid" where + * 'eventtype' is the event type and 'targetid' is the id of + * target of the event. This function will be passed two + * arguments, the testname and the step argument. + * Alternatively, events may be a function which returns the array + * of events. This can be used when the events vary per platform. + * result - function to call after all the events have fired to check + * for additional results. May be null. This function will be + * passed two arguments, the testname and the step argument. + * steps - optional array of values. The test will be repeated for + * each step, passing each successive value within the array to + * the test and result functions + * autohide - if set, should be set to the id of a popup to hide after + * the test is complete. This is a convenience for some tests. + * condition - an optional function which, if it returns false, causes the + * test to be skipped. + * end - used for debugging. Set to true to stop the tests after running + * this one. + */ + +const menuactiveAttribute = "_moz-menuactive"; + +var gPopupTests = null; +var gTestIndex = -1; +var gTestStepIndex = 0; +var gTestEventIndex = 0; +var gAutoHide = false; +var gExpectedEventDetails = null; +var gExpectedTriggerNode = null; +var gWindowUtils; +var gPopupWidth = -1, gPopupHeight = -1; + +function startPopupTests(tests) +{ + document.addEventListener("popupshowing", eventOccurred, false); + document.addEventListener("popupshown", eventOccurred, false); + document.addEventListener("popuphiding", eventOccurred, false); + document.addEventListener("popuphidden", eventOccurred, false); + document.addEventListener("command", eventOccurred, false); + document.addEventListener("DOMMenuItemActive", eventOccurred, false); + document.addEventListener("DOMMenuItemInactive", eventOccurred, false); + document.addEventListener("DOMMenuInactive", eventOccurred, false); + document.addEventListener("DOMMenuBarActive", eventOccurred, false); + document.addEventListener("DOMMenuBarInactive", eventOccurred, false); + + gPopupTests = tests; + gWindowUtils = SpecialPowers.getDOMWindowUtils(window); + + goNext(); +} + +function finish() +{ + if (window.opener) { + window.close(); + window.opener.SimpleTest.finish(); + return; + } + SimpleTest.finish(); + return; +} + +function ok(condition, message) { + if (window.opener) + window.opener.SimpleTest.ok(condition, message); + else + SimpleTest.ok(condition, message); +} + +function is(left, right, message) { + if (window.opener) + window.opener.SimpleTest.is(left, right, message); + else + SimpleTest.is(left, right, message); +} + +function disableNonTestMouse(aDisable) { + gWindowUtils.disableNonTestMouseEvents(aDisable); +} + +function eventOccurred(event) +{ + if (gPopupTests.length <= gTestIndex) { + ok(false, "Extra " + event.type + " event fired"); + return; + } + + var test = gPopupTests[gTestIndex]; + if ("autohide" in test && gAutoHide) { + if (event.type == "DOMMenuInactive") { + gAutoHide = false; + setTimeout(goNextStep, 0); + } + return; + } + + var events = test.events; + if (typeof events == "function") + events = events(); + if (events) { + if (events.length <= gTestEventIndex) { + ok(false, "Extra " + event.type + " event fired for " + event.target.id + + " " +gPopupTests[gTestIndex].testname); + return; + } + + var eventitem = events[gTestEventIndex].split(" "); + var matches; + if (eventitem[1] == "#tooltip") { + is(event.originalTarget.localName, "tooltip", + test.testname + " event.originalTarget.localName is 'tooltip'"); + is(event.originalTarget.getAttribute("default"), "true", + test.testname + " event.originalTarget default attribute is 'true'"); + matches = event.originalTarget.localName == "tooltip" && + event.originalTarget.getAttribute("default") == "true"; + } else { + is(event.type, eventitem[0], + test.testname + " event type " + event.type + " fired"); + is(event.target.id, eventitem[1], + test.testname + " event target ID " + event.target.id); + matches = eventitem[0] == event.type && eventitem[1] == event.target.id; + } + + var modifiersMask = eventitem[2]; + if (modifiersMask) { + var m = ""; + m += event.altKey ? '1' : '0'; + m += event.ctrlKey ? '1' : '0'; + m += event.shiftKey ? '1' : '0'; + m += event.metaKey ? '1' : '0'; + is(m, modifiersMask, test.testname + " modifiers mask matches"); + } + + var expectedState; + switch (event.type) { + case "popupshowing": expectedState = "showing"; break; + case "popupshown": expectedState = "open"; break; + case "popuphiding": expectedState = "hiding"; break; + case "popuphidden": expectedState = "closed"; break; + } + + if (gExpectedTriggerNode && event.type == "popupshowing") { + if (gExpectedTriggerNode == "notset") // check against null instead + gExpectedTriggerNode = null; + + is(event.originalTarget.triggerNode, gExpectedTriggerNode, test.testname + " popupshowing triggerNode"); + var isTooltip = (event.target.localName == "tooltip"); + is(document.popupNode, isTooltip ? null : gExpectedTriggerNode, + test.testname + " popupshowing document.popupNode"); + is(document.tooltipNode, isTooltip ? gExpectedTriggerNode : null, + test.testname + " popupshowing document.tooltipNode"); + } + + if (expectedState) + is(event.originalTarget.state, expectedState, + test.testname + " " + event.type + " state"); + + if (matches) { + gTestEventIndex++ + if (events.length <= gTestEventIndex) + setTimeout(checkResult, 0); + } + } +} + +function checkResult() +{ + var step = null; + var test = gPopupTests[gTestIndex]; + if ("steps" in test) + step = test.steps[gTestStepIndex]; + + if ("result" in test) + test.result(test.testname, step); + + if ("autohide" in test) { + gAutoHide = true; + document.getElementById(test.autohide).hidePopup(); + return; + } + + goNextStep(); +} + +function goNextStep() +{ + gTestEventIndex = 0; + + var step = null; + var test = gPopupTests[gTestIndex]; + if ("steps" in test) { + gTestStepIndex++; + step = test.steps[gTestStepIndex]; + if (gTestStepIndex < test.steps.length) { + test.test(test.testname, step); + return; + } + } + + goNext(); +} + +function goNext() +{ + // We want to continue after the next animation frame so that + // we're in a stable state and don't get spurious mouse events at unexpected targets. + window.requestAnimationFrame( + function() { + setTimeout(goNextStepSync, 0); + } + ); +} + +function goNextStepSync() +{ + if (gTestIndex >= 0 && "end" in gPopupTests[gTestIndex] && gPopupTests[gTestIndex].end) { + finish(); + return; + } + + gTestIndex++; + gTestStepIndex = 0; + if (gTestIndex < gPopupTests.length) { + var test = gPopupTests[gTestIndex]; + // Set the location hash so it's easy to see which test is running + document.location.hash = test.testname; + + // skip the test if the condition returns false + if ("condition" in test && !test.condition()) { + goNext(); + return; + } + + // start with the first step if there are any + var step = null; + if ("steps" in test) + step = test.steps[gTestStepIndex]; + + test.test(test.testname, step); + + // no events to check for so just check the result + if (!("events" in test)) + checkResult(); + } + else { + finish(); + } +} + +function openMenu(menu) +{ + if ("open" in menu) { + menu.open = true; + } + else { + var bo = menu.boxObject; + if (bo instanceof MenuBoxObject) + bo.openMenu(true); + else + synthesizeMouse(menu, 4, 4, { }); + } +} + +function closeMenu(menu, popup) +{ + if ("open" in menu) { + menu.open = false; + } + else { + var bo = menu.boxObject; + if (bo instanceof MenuBoxObject) + bo.openMenu(false); + else + popup.hidePopup(); + } +} + +function checkActive(popup, id, testname) +{ + var activeok = true; + var children = popup.childNodes; + for (var c = 0; c < children.length; c++) { + var child = children[c]; + if ((id == child.id && child.getAttribute(menuactiveAttribute) != "true") || + (id != child.id && child.hasAttribute(menuactiveAttribute) != "")) { + activeok = false; + break; + } + } + ok(activeok, testname + " item " + (id ? id : "none") + " active"); +} + +function checkOpen(menuid, testname) +{ + var menu = document.getElementById(menuid); + if ("open" in menu) + ok(menu.open, testname + " " + menuid + " menu is open"); + else if (menu.boxObject instanceof MenuBoxObject) + ok(menu.getAttribute("open") == "true", testname + " " + menuid + " menu is open"); +} + +function checkClosed(menuid, testname) +{ + var menu = document.getElementById(menuid); + if ("open" in menu) + ok(!menu.open, testname + " " + menuid + " menu is open"); + else if (menu.boxObject instanceof MenuBoxObject) + ok(!menu.hasAttribute("open"), testname + " " + menuid + " menu is closed"); +} + +function convertPosition(anchor, align) +{ + if (anchor == "topleft" && align == "topleft") return "overlap"; + if (anchor == "topleft" && align == "topright") return "start_before"; + if (anchor == "topleft" && align == "bottomleft") return "before_start"; + if (anchor == "topright" && align == "topleft") return "end_before"; + if (anchor == "topright" && align == "bottomright") return "before_end"; + if (anchor == "bottomleft" && align == "bottomright") return "start_after"; + if (anchor == "bottomleft" && align == "topleft") return "after_start"; + if (anchor == "bottomright" && align == "bottomleft") return "end_after"; + if (anchor == "bottomright" && align == "topright") return "after_end"; + return ""; +} + +/* + * When checking position of the bottom or right edge of the popup's rect, + * use this instead of strict equality check of rounded values, + * because we snap the top/left edges to pixel boundaries, + * which can shift the bottom/right up to 0.5px from its "ideal" location, + * and could cause it to round differently. (See bug 622507.) + */ +function isWithinHalfPixel(a, b) +{ + return Math.abs(a - b) <= 0.5; +} + +function compareEdge(anchor, popup, edge, offsetX, offsetY, testname) +{ + testname += " " + edge; + + checkOpen(anchor.id, testname); + + var anchorrect = anchor.getBoundingClientRect(); + var popuprect = popup.getBoundingClientRect(); + var check1 = false, check2 = false; + + if (gPopupWidth == -1) { + ok((Math.round(popuprect.right) - Math.round(popuprect.left)) && + (Math.round(popuprect.bottom) - Math.round(popuprect.top)), + testname + " size"); + } + else { + is(Math.round(popuprect.width), gPopupWidth, testname + " width"); + is(Math.round(popuprect.height), gPopupHeight, testname + " height"); + } + + var spaceIdx = edge.indexOf(" "); + if (spaceIdx > 0) { + let cornerX, cornerY; + let [position, align] = edge.split(" "); + switch (position) { + case "topleft": cornerX = anchorrect.left; cornerY = anchorrect.top; break; + case "topcenter": cornerX = anchorrect.left + anchorrect.width / 2; cornerY = anchorrect.top; break; + case "topright": cornerX = anchorrect.right; cornerY = anchorrect.top; break; + case "leftcenter": cornerX = anchorrect.left; cornerY = anchorrect.top + anchorrect.height / 2; break; + case "rightcenter": cornerX = anchorrect.right; cornerY = anchorrect.top + anchorrect.height / 2; break; + case "bottomleft": cornerX = anchorrect.left; cornerY = anchorrect.bottom; break; + case "bottomcenter": cornerX = anchorrect.left + anchorrect.width / 2; cornerY = anchorrect.bottom; break; + case "bottomright": cornerX = anchorrect.right; cornerY = anchorrect.bottom; break; + } + + switch (align) { + case "topleft": cornerX += offsetX; cornerY += offsetY; break; + case "topright": cornerX += -popuprect.width + offsetX; cornerY += offsetY; break; + case "bottomleft": cornerX += offsetX; cornerY += -popuprect.height + offsetY; break; + case "bottomright": cornerX += -popuprect.width + offsetX; cornerY += -popuprect.height + offsetY; break; + } + + is(Math.round(popuprect.left), Math.round(cornerX), testname + " x position"); + is(Math.round(popuprect.top), Math.round(cornerY), testname + " y position"); + return; + } + + if (edge == "after_pointer") { + is(Math.round(popuprect.left), Math.round(anchorrect.left) + offsetX, testname + " x position"); + is(Math.round(popuprect.top), Math.round(anchorrect.top) + offsetY + 21, testname + " y position"); + return; + } + + if (edge == "overlap") { + ok(Math.round(anchorrect.left) + offsetY == Math.round(popuprect.left) && + Math.round(anchorrect.top) + offsetY == Math.round(popuprect.top), + testname + " position"); + return; + } + + if (edge.indexOf("before") == 0) + check1 = isWithinHalfPixel(anchorrect.top + offsetY, popuprect.bottom); + else if (edge.indexOf("after") == 0) + check1 = (Math.round(anchorrect.bottom) + offsetY == Math.round(popuprect.top)); + else if (edge.indexOf("start") == 0) + check1 = isWithinHalfPixel(anchorrect.left + offsetX, popuprect.right); + else if (edge.indexOf("end") == 0) + check1 = (Math.round(anchorrect.right) + offsetX == Math.round(popuprect.left)); + + if (0 < edge.indexOf("before")) + check2 = (Math.round(anchorrect.top) + offsetY == Math.round(popuprect.top)); + else if (0 < edge.indexOf("after")) + check2 = isWithinHalfPixel(anchorrect.bottom + offsetY, popuprect.bottom); + else if (0 < edge.indexOf("start")) + check2 = (Math.round(anchorrect.left) + offsetX == Math.round(popuprect.left)); + else if (0 < edge.indexOf("end")) + check2 = isWithinHalfPixel(anchorrect.right + offsetX, popuprect.right); + + ok(check1 && check2, testname + " position"); +} diff --git a/toolkit/content/tests/widgets/seek_with_sound.ogg b/toolkit/content/tests/widgets/seek_with_sound.ogg Binary files differnew file mode 100644 index 000000000..c86d9946b --- /dev/null +++ b/toolkit/content/tests/widgets/seek_with_sound.ogg diff --git a/toolkit/content/tests/widgets/test_audiocontrols_dimensions.html b/toolkit/content/tests/widgets/test_audiocontrols_dimensions.html new file mode 100644 index 000000000..0f295cce9 --- /dev/null +++ b/toolkit/content/tests/widgets/test_audiocontrols_dimensions.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Audio controls test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <audio id="audio" controls preload="auto"></audio> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + function loadedmetadata(event) { + is(event.type, "loadedmetadata", "checking event type"); + is(audio.clientHeight, 28, "checking height of audio element"); + + SimpleTest.finish(); + } + + var audio = document.getElementById("audio"); + + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startTest); + function startTest() { + // Kick off test once audio has loaded. + audio.addEventListener("loadedmetadata", loadedmetadata, false); + audio.src = "audio.wav"; + } + + SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_bug898940.html b/toolkit/content/tests/widgets/test_bug898940.html new file mode 100644 index 000000000..10a6a80d9 --- /dev/null +++ b/toolkit/content/tests/widgets/test_bug898940.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that an audio element that's already playing when controls are attached displays the controls</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <audio id="audio" controls src="audio.ogg"></audio> +</div> + +<pre id="test"> +<script class="testbody"> + var audio = document.getElementById("audio"); + audio.play(); + audio.ontimeupdate = function doTest() { + ok(audio.getBoundingClientRect().height > 0, + "checking audio element height is greater than zero"); + audio.ontimeupdate = null; + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_contextmenu_menugroup.xul b/toolkit/content/tests/widgets/test_contextmenu_menugroup.xul new file mode 100644 index 000000000..594c0264d --- /dev/null +++ b/toolkit/content/tests/widgets/test_contextmenu_menugroup.xul @@ -0,0 +1,102 @@ +<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Context menugroup Tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <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>
+ <script type="application/javascript" src="popup_shared.js"></script>
+
+<menupopup id="context">
+ <menugroup>
+ <menuitem id="a"/>
+ <menuitem id="b"/>
+ </menugroup>
+ <menuitem id="c" label="c"/>
+ <menugroup/>
+</menupopup>
+
+<button label="Check"/>
+
+<vbox id="popuparea" popup="context" width="20" height="20"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+var gMenuPopup = $("context");
+ok(gMenuPopup, "Got the reference to the context menu");
+
+var popupTests = [
+{
+ testname: "one-down-key",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "popupshowing context", "popupshown context", "DOMMenuItemActive a" ],
+ test: function () {
+ synthesizeMouse($("popuparea"), 4, 4, {});
+ synthesizeKey("VK_DOWN", {});
+ },
+ result: function (testname) {
+ checkActive(gMenuPopup, "a", testname);
+ }
+},
+{
+ testname: "two-down-keys",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "DOMMenuItemInactive a", "DOMMenuItemActive b" ],
+ test: () => synthesizeKey("VK_DOWN", {}),
+ result: function (testname) {
+ checkActive(gMenuPopup, "b", testname);
+ }
+},
+{
+ testname: "three-down-keys",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "DOMMenuItemInactive b", "DOMMenuItemActive c" ],
+ test: () => synthesizeKey("VK_DOWN", {}),
+ result: function (testname) {
+ checkActive(gMenuPopup, "c", testname);
+ }
+},
+{
+ testname: "three-down-keys-one-up-key",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "DOMMenuItemInactive c", "DOMMenuItemActive b" ],
+ test: () => synthesizeKey("VK_UP", {}),
+ result: function (testname) {
+ checkActive(gMenuPopup, "b", testname);
+ }
+},
+{
+ testname: "three-down-keys-two-up-keys",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "DOMMenuItemInactive b", "DOMMenuItemActive a" ],
+ test: () => synthesizeKey("VK_UP", {}),
+ result: function (testname) {
+ checkActive(gMenuPopup, "a", testname);
+ }
+},
+{
+ testname: "three-down-keys-three-up-key",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "DOMMenuItemInactive a", "DOMMenuItemActive c" ],
+ test: () => synthesizeKey("VK_UP", {}),
+ result: function (testname) {
+ checkActive(gMenuPopup, "c", testname);
+ }
+},
+];
+
+SimpleTest.waitForFocus(function runTest() {
+ startPopupTests(popupTests);
+});
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml"><p id="display"/></body>
+
+</window>
diff --git a/toolkit/content/tests/widgets/test_contextmenu_nested.xul b/toolkit/content/tests/widgets/test_contextmenu_nested.xul new file mode 100644 index 000000000..9eb42a1ed --- /dev/null +++ b/toolkit/content/tests/widgets/test_contextmenu_nested.xul @@ -0,0 +1,138 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Nested Context Menu Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <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> + <script type="application/javascript" src="popup_shared.js"></script> + +<menupopup id="outercontext"> + <menuitem label="Context One"/> + <menu id="outercontextmenu" label="Sub"> + <menupopup id="innercontext"> + <menuitem id="innercontextmenu" label="Sub Context One"/> + </menupopup> + </menu> +</menupopup> + +<menupopup id="outermain"> + <menuitem label="One"/> + <menu id="outermenu" label="Sub"> + <menupopup id="innermain"> + <menuitem id="innermenu" label="Sub One" context="outercontext"/> + </menupopup> + </menu> +</menupopup> + +<button label="Check"/> + +<vbox id="popuparea" popup="outermain" width="20" height="20"/> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var popupTests = [ +{ + testname: "open outer popup", + events: [ "popupshowing outermain", "popupshown outermain" ], + test: () => synthesizeMouse($("popuparea"), 4, 4, {}), + result: function (testname) { + is($("outermain").triggerNode, $("popuparea"), testname); + is(document.popupNode, $("popuparea"), testname + " document.popupNode"); + } +}, +{ + testname: "open inner popup", + events: [ "DOMMenuItemActive outermenu", "popupshowing innermain", "popupshown innermain" ], + test: function () { + synthesizeMouse($("outermenu"), 4, 4, { type: "mousemove" }); + synthesizeMouse($("outermenu"), 2, 2, { type: "mousemove" }); + }, + result: function (testname) { + is($("outermain").triggerNode, $("popuparea"), testname + " outer"); + is($("innermain").triggerNode, $("popuparea"), testname + " inner"); + is($("outercontext").triggerNode, null, testname + " outer context"); + is(document.popupNode, $("popuparea"), testname + " document.popupNode"); + } +}, +{ + testname: "open outer context", + condition: function() { return (navigator.platform.indexOf("Mac") == -1); }, + events: [ "popupshowing outercontext", "popupshown outercontext" ], + test: () => synthesizeMouse($("innermenu"), 4, 4, { type: "contextmenu", button: 2 }), + result: function (testname) { + is($("outermain").triggerNode, $("popuparea"), testname + " outer"); + is($("innermain").triggerNode, $("popuparea"), testname + " inner"); + is($("outercontext").triggerNode, $("innermenu"), testname + " outer context"); + is(document.popupNode, $("innermenu"), testname + " document.popupNode"); + } +}, +{ + testname: "open inner context", + condition: function() { return (navigator.platform.indexOf("Mac") == -1); }, + events: [ "DOMMenuItemActive outercontextmenu", "popupshowing innercontext", "popupshown innercontext" ], + test: function () { + synthesizeMouse($("outercontextmenu"), 4, 4, { type: "mousemove" }); + setTimeout(function() { + synthesizeMouse($("outercontextmenu"), 2, 2, { type: "mousemove" }); + }, 1000); + }, + result: function (testname) { + is($("outermain").triggerNode, $("popuparea"), testname + " outer"); + is($("innermain").triggerNode, $("popuparea"), testname + " inner"); + is($("outercontext").triggerNode, $("innermenu"), testname + " outer context"); + is($("innercontext").triggerNode, $("innermenu"), testname + " inner context"); + is(document.popupNode, $("innermenu"), testname + " document.popupNode"); + } +}, +{ + testname: "close context", + condition: function() { return (navigator.platform.indexOf("Mac") == -1); }, + events: [ "popuphiding innercontext", "popuphidden innercontext", + "popuphiding outercontext", "popuphidden outercontext", + "DOMMenuInactive innercontext", + "DOMMenuItemInactive outercontextmenu", "DOMMenuItemInactive outercontextmenu", + "DOMMenuInactive outercontext" ], + test: () => $("outercontext").hidePopup(), + result: function (testname) { + is($("outermain").triggerNode, $("popuparea"), testname + " outer"); + is($("innermain").triggerNode, $("popuparea"), testname + " inner"); + is($("outercontext").triggerNode, null, testname + " outer context"); + is($("innercontext").triggerNode, null, testname + " inner context"); + is(document.popupNode, $("popuparea"), testname + " document.popupNode"); + } +}, +{ + testname: "hide menus", + events: [ "popuphiding innermain", "popuphidden innermain", + "popuphiding outermain", "popuphidden outermain", + "DOMMenuInactive innermain", + "DOMMenuItemInactive outermenu", "DOMMenuItemInactive outermenu", + "DOMMenuInactive outermain" ], + + test: () => $("outermain").hidePopup(), + result: function (testname) { + is($("outermain").triggerNode, null, testname + " outer"); + is($("innermain").triggerNode, null, testname + " inner"); + is($("outercontext").triggerNode, null, testname + " outer context"); + is($("innercontext").triggerNode, null, testname + " inner context"); + is(document.popupNode, null, testname + " document.popupNode"); + } +} +]; + +SimpleTest.waitForFocus(function runTest() { + return startPopupTests(popupTests); +}); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"><p id="display"/></body> + +</window> diff --git a/toolkit/content/tests/widgets/test_editor_currentURI.xul b/toolkit/content/tests/widgets/test_editor_currentURI.xul new file mode 100644 index 000000000..20ab3af7c --- /dev/null +++ b/toolkit/content/tests/widgets/test_editor_currentURI.xul @@ -0,0 +1,39 @@ +<?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 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Editor currentURI Tests" onload="runTest();"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p/> + <editor xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + id="editor" + type="content" + editortype="html" + style="width: 400px; height: 100px;"/> + <p/> + <pre id="test"> + </pre> + </body> + <script class="testbody" type="application/javascript"> + <![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + function runTest() { + var editor = document.getElementById("editor"); + // Check that currentURI is a property of editor. + var result = "currentURI" in editor; + is(result, true, "currentURI is a property of editor"); + is(editor.currentURI.spec, "about:blank", "currentURI.spec is about:blank"); + SimpleTest.finish(); + } +]]> +</script> +</window> diff --git a/toolkit/content/tests/widgets/test_menubar.xul b/toolkit/content/tests/widgets/test_menubar.xul new file mode 100644 index 000000000..7aa15fb2a --- /dev/null +++ b/toolkit/content/tests/widgets/test_menubar.xul @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menubar Popup Tests" + onload="setTimeout(runTest, 0);" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <title>Menubar Popup Tests</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_menubar.xul", "_blank", "width=600,height=600"); +} +</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> + +</window> diff --git a/toolkit/content/tests/widgets/test_mousecapture_area.html b/toolkit/content/tests/widgets/test_mousecapture_area.html new file mode 100644 index 000000000..532f41a5a --- /dev/null +++ b/toolkit/content/tests/widgets/test_mousecapture_area.html @@ -0,0 +1,109 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Mouse capture on area elements tests</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <!-- The border="0" on the images is needed so that when we use + synthesizeMouse we don't accidentally target the border of the image and + miss the area because synthesizeMouse gets the rect of the primary frame + of the target (the area), which is the image due to bug 135040, which + includes the border, but the events targetted at the border aren't + targeted at the area. --> + + <!-- 20x20 of red --> + <img id="image" border="0" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC" + usemap="#Map"/> + + <map name="Map"> + <!-- area over the whole image --> + <area id="area" onmousedown="this.setCapture();" onmouseup="this.releaseCapture();" + shape="poly" coords="0,0, 0,20, 20,20, 20,0" href="javascript:void(0);"/> + </map> + + + <!-- 20x20 of red --> + <img id="img1" border="0" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC" + usemap="#sharedMap"/> + + <!-- 20x20 of red --> + <img id="img2" border="0" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC" + usemap="#sharedMap"/> + + <map name="sharedMap"> + <!-- area over the whole image --> + <area id="sharedarea" onmousedown="this.setCapture();" onmouseup="this.releaseCapture();" + shape="poly" coords="0,0, 0,20, 20,20, 20,0" href="javascript:void(0);"/> + </map> + + + <div id="otherelement" style="width: 100px; height: 100px;"></div> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.expectAssertions(3); + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + // XXX We send a useless click to each image to force it to setup its image + // map, because flushing layout won't do it. Hopefully bug 135040 will make + // this not suck. + synthesizeMouse($("image"), 5, 5, { type: "mousedown" }); + synthesizeMouse($("image"), 5, 5, { type: "mouseup" }); + synthesizeMouse($("img1"), 5, 5, { type: "mousedown" }); + synthesizeMouse($("img1"), 5, 5, { type: "mouseup" }); + synthesizeMouse($("img2"), 5, 5, { type: "mousedown" }); + synthesizeMouse($("img2"), 5, 5, { type: "mouseup" }); + + + // test that setCapture works on an area element (bug 517737) + var area = document.getElementById("area"); + synthesizeMouse(area, 5, 5, { type: "mousedown" }); + synthesizeMouseExpectEvent($("otherelement"), 5, 5, { type: "mousemove" }, + area, "mousemove", "setCapture works on areas"); + synthesizeMouse(area, 5, 5, { type: "mouseup" }); + + // test that setCapture works on an area element when it is part of an image + // map that is used by two images + + var img1 = document.getElementById("img1"); + var sharedarea = document.getElementById("sharedarea"); + // synthesizeMouse just sends the event by coordinates, so this is really a click on the area + synthesizeMouse(img1, 5, 5, { type: "mousedown" }); + synthesizeMouseExpectEvent($("otherelement"), 5, 5, { type: "mousemove" }, + sharedarea, "mousemove", "setCapture works on areas with multiple images"); + synthesizeMouse(img1, 5, 5, { type: "mouseup" }); + + var img2 = document.getElementById("img2"); + // synthesizeMouse just sends the event by coordinates, so this is really a click on the area + synthesizeMouse(img2, 5, 5, { type: "mousedown" }); + synthesizeMouseExpectEvent($("otherelement"), 5, 5, { type: "mousemove" }, + sharedarea, "mousemove", "setCapture works on areas with multiple images"); + synthesizeMouse(img2, 5, 5, { type: "mouseup" }); + + // Bug 862673 - nuke all content so assertions in this test are attributed to + // this test rather than the one which happens to follow. + var content = document.getElementById("content"); + content.parentNode.removeChild(content); + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(runTests); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_popupanchor.xul b/toolkit/content/tests/widgets/test_popupanchor.xul new file mode 100644 index 000000000..814d9272f --- /dev/null +++ b/toolkit/content/tests/widgets/test_popupanchor.xul @@ -0,0 +1,430 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Anchor Tests" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <panel id="testPanel" + type="arrow" + animate="false" + noautohide="true"> + </panel> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +<![CDATA[ +var anchor, panel, arrow; + +function is_close(got, exp, msg) { + // on some platforms we see differences of a fraction of a pixel - so + // allow any difference of < 1 pixels as being OK. + ok(Math.abs(got - exp) < 1, msg + ": " + got + " should be equal(-ish) to " + exp); +} + +function isArrowPositionedOn(side, offset) { + var arrowRect = arrow.getBoundingClientRect(); + var arrowMidX = (arrowRect.left + arrowRect.right) / 2; + var arrowMidY = (arrowRect.top + arrowRect.bottom) / 2; + var panelRect = panel.getBoundingClientRect(); + var panelMidX = (panelRect.left + panelRect.right) / 2; + var panelMidY = (panelRect.top + panelRect.bottom) / 2; + // First check the "flip" of the panel is correct. If we are expecting the + // arrow to be pointing to the left side of the anchor, the arrow must + // also be on the left side of the panel (and vice-versa) + // XXX - on OSX, the arrow seems to always be exactly in the center, hence + // the 'equals' sign in the "<=" and ">=" comparisons. NFI why though... + switch (side) { + case "left": + ok(arrowMidX <= panelMidX, "arrow should be on the left of the panel"); + break; + case "right": + ok(arrowMidX >= panelMidX, "arrow should be on the right of the panel"); + break; + case "top": + ok(arrowMidY <= panelMidY, "arrow should be on the top of the panel"); + break; + case "bottom": + ok(arrowMidY >= panelMidY, "arrow should be on the bottom of the panel"); + break; + default: + ok(false, "invalid position " + where); + break; + } + // Now check the arrow really is pointing where we expect. The middle of + // the arrow should be pointing exactly to the left (or right) side of the + // anchor rect, +- any offsets. + if (offset === null) // special case - explicit 'null' means 'don't check offset' + return; + offset = offset || 0; // no param means no offset expected. + var anchorRect = anchor.getBoundingClientRect(); + var anchorPos = anchorRect[side]; + switch (side) { + case "left": + case "right": + is_close(arrowMidX - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); + is_close(panelRect.top, anchorRect.bottom, "top of panel should be at bottom of anchor"); + break; + case "top": + case "bottom": + is_close(arrowMidY - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); + is_close(panelRect.right, anchorRect.left, "right of panel should be left of anchor"); + break; + default: + ok(false, "unknown side " + side); + break; + } +} + +function openSlidingPopup(position, callback) { + panel.setAttribute("flip", "slide"); + _openPopup(position, callback); +} + +function openPopup(position, callback) { + panel.setAttribute("flip", "both"); + _openPopup(position, callback); +} + +function waitForPopupPositioned(actionFn, callback) +{ + panel.addEventListener("popuppositioned", function listener() { + panel.removeEventListener("popuppositioned", listener, false); + callback(); + }, false); + actionFn(); +} + +function _openPopup(position, callback) { + // this is very ugly: the panel CSS sets the arrow's list-style-image based + // on the 'side' attribute. If the setting of the 'side' attribute causes + // the image to change, we may get the popupshown event before the new + // image has loaded - which causes the size of the arrow to be incorrect + // for a brief moment - right when we are measuring it! + // So we work around this in 2 steps: + // * Force the 'side' attribute to a value which causes the CSS to not + // specify an image - then when the popup gets shown, the correct image + // is set, causing a load() event on the image element. + // * Listen to *both* popupshown and the image load event. When both have + // fired (the order is indeterminate) we start the test. + panel.setAttribute("side", "noside"); + var numEvents = 0; + function onEvent() { + if (++numEvents == 2) // after both panel 'popupshown' and image 'load' + callback(); + }; + panel.addEventListener("popupshown", function popupshown() { + panel.removeEventListener("popupshown", popupshown); + onEvent(); + }); + arrow.addEventListener("load", function imageload() { + arrow.removeEventListener("load", imageload); + onEvent(); + }); + panel.openPopup(anchor, position); +} + +var tests = [ + // A panel with the anchor after_end - the anchor should not move on resize + ['simpleResizeHorizontal', 'middle', function(next) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + var origPanelRect = panel.getBoundingClientRect(); + panel.sizeTo(100, 100); + isArrowPositionedOn("right"); // should not have flipped, so still "right" + panel.sizeTo(origPanelRect.width, origPanelRect.height); + isArrowPositionedOn("right"); // should not have flipped, so still "right" + next(); + }); + }], + + ['simpleResizeVertical', 'middle', function(next) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + var origPanelRect = panel.getBoundingClientRect(); + panel.sizeTo(100, 100); + isArrowPositionedOn("bottom"); // should not have flipped + panel.sizeTo(origPanelRect.width, origPanelRect.height); + isArrowPositionedOn("bottom"); // should not have flipped + next(); + }); + }], + + ['flippingResizeHorizontal', 'middle', function(next) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + panel.sizeTo(anchor.getBoundingClientRect().left + 50, 50); + isArrowPositionedOn("left"); // check it flipped and has zero offset. + next(); + }); + }], + + ['flippingResizeVertical', 'middle', function(next) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + panel.sizeTo(50, anchor.getBoundingClientRect().top + 50); + isArrowPositionedOn("top"); // check it flipped and has zero offset. + next(); + }); + }], + + ['simpleMoveToAnchorHorizontal', 'middle', function(next) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + panel.moveToAnchor(anchor, "after_end", 20, 0); + // the anchor and the panel should have moved 20px right without flipping. + isArrowPositionedOn("right", 20); + panel.moveToAnchor(anchor, "after_end", -20, 0); + // the anchor and the panel should have moved 20px left without flipping. + isArrowPositionedOn("right", -20); + next(); + }); + }], + + ['simpleMoveToAnchorVertical', 'middle', function(next) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + panel.moveToAnchor(anchor, "start_after", 0, 20); + // the anchor and the panel should have moved 20px down without flipping. + isArrowPositionedOn("bottom", 20); + panel.moveToAnchor(anchor, "start_after", 0, -20); + // the anchor and the panel should have moved 20px up without flipping. + isArrowPositionedOn("bottom", -20); + next(); + }); + }], + + // Do a moveToAnchor that causes the panel to flip horizontally + ['flippingMoveToAnchorHorizontal', 'middle', function(next) { + var anchorRight = anchor.getBoundingClientRect().right; + // Size the panel such that it only just fits from the left-hand side of + // the window to the right of the anchor - thus, it will fit when + // anchored to the right-hand side of the anchor. + panel.sizeTo(anchorRight - 10, 100); + openPopup("after_end", function() { + isArrowPositionedOn("right"); + // Ask for it to be anchored 1/2 way between the left edge of the window + // and the anchor right - it can't fit with the panel on the left/arrow + // on the right, so it must flip (arrow on the left, panel on the right) + var offset = Math.floor(-anchorRight / 2); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "after_end", offset, 0), + () => { + isArrowPositionedOn("left", offset); // should have flipped and have the offset. + // resize back to original and move to a zero offset - it should flip back. + + panel.sizeTo(anchorRight - 10, 100); + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "after_end", 0, 0), + () => { + isArrowPositionedOn("right"); // should have flipped back and no offset + next(); + }); + }); + }); + }], + + // Do a moveToAnchor that causes the panel to flip vertically + ['flippingMoveToAnchorVertical', 'middle', function(next) { + var anchorBottom = anchor.getBoundingClientRect().bottom; + // See comments above in flippingMoveToAnchorHorizontal, but read + // "top/bottom" instead of "left/right" + panel.sizeTo(100, anchorBottom - 10); + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + var offset = Math.floor(-anchorBottom / 2); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "start_after", 0, offset), + () => { + isArrowPositionedOn("top", offset); + panel.sizeTo(100, anchorBottom - 10); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "start_after", 0, 0), + () => { + isArrowPositionedOn("bottom"); + next(); + }); + }); + }); + }], + + ['veryWidePanel-after_end', 'middle', function(next) { + openSlidingPopup("after_end", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now move it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit, + // meaning the arrow should "slide" down the panel. + panel.sizeTo(window.innerWidth - 10, 60); + is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested.") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + ['veryWidePanel-before_start', 'middle', function(next) { + openSlidingPopup("before_start", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now size it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit. + panel.sizeTo(window.innerWidth - 10, 60); + is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + ['veryTallPanel-start_after', 'middle', function(next) { + openSlidingPopup("start_after", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now move it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit, + // meaning the arrow should "slide" down the panel. + panel.sizeTo(100, window.innerHeight - 10); + is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested.") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + ['veryTallPanel-start_before', 'middle', function(next) { + openSlidingPopup("start_before", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now size it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit. + panel.sizeTo(100, window.innerHeight - 10); + is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + // Tests against the anchor at the right-hand side of the window + ['afterend', 'right', function(next) { + openPopup("after_end", function() { + // when we request too far to the right/bottom, the panel gets shrunk + // and moved. The amount it is shrunk by is how far it is moved. + var panelRect = panel.getBoundingClientRect(); + // panel was requested 100px wide - calc offset based on actual width. + var offset = panelRect.width - 100; + isArrowPositionedOn("right", offset); + next(); + }); + }], + + ['after_start', 'right', function(next) { + openPopup("after_start", function() { + // See above - we are still too far to the right, but the anchor is + // on the other side. + var panelRect = panel.getBoundingClientRect(); + var offset = panelRect.width - 100; + isArrowPositionedOn("right", offset); + next(); + }); + }], + + // Tests against the anchor at the left-hand side of the window + ['after_start', 'left', function(next) { + openPopup("after_start", function() { + var panelRect = panel.getBoundingClientRect(); + is(panelRect.left, 0, "panel remains within the screen"); + // not sure how to determine the offset here, so given we have checked + // the panel is as left as possible while still being inside the window, + // we just don't check the offset. + isArrowPositionedOn("left", null); + next(); + }); + }], +] + +function runTests() { + function runNextTest() { + let result = tests.shift(); + if (!result) { + // out of tests + panel.hidePopup(); + SimpleTest.finish(); + return; + } + let [name, anchorPos, test] = result; + SimpleTest.info("sub-test " + anchorPos + "." + name + " starting"); + // first arrange for the anchor to be where the test requires it. + panel.hidePopup(); + panel.sizeTo(100, 50); + // hide all the anchors here, then later we make one of them visible. + document.getElementById("anchor-left-wrapper").style.display = "none"; + document.getElementById("anchor-middle-wrapper").style.display = "none"; + document.getElementById("anchor-right-wrapper").style.display = "none"; + switch(anchorPos) { + case 'middle': + anchor = document.getElementById("anchor-middle"); + document.getElementById("anchor-middle-wrapper").style.display = "block"; + break; + case 'left': + anchor = document.getElementById("anchor-left"); + document.getElementById("anchor-left-wrapper").style.display = "block"; + break; + case 'right': + anchor = document.getElementById("anchor-right"); + document.getElementById("anchor-right-wrapper").style.display = "block"; + break; + default: + SimpleTest.ok(false, "Bad anchorPos: " + anchorPos); + runNextTest(); + return; + } + try { + test(runNextTest); + } catch (ex) { + SimpleTest.ok(false, "sub-test " + anchorPos + "." + name + " failed: " + ex.toString() + "\n" + ex.stack); + runNextTest(); + } + } + runNextTest(); +} + +SimpleTest.waitForExplicitFinish(); + +addEventListener("load", function() { + // anchor is set by the test runner above + panel = document.getElementById("testPanel"); + + arrow = SpecialPowers.wrap(document).getAnonymousElementByAttribute(panel, "anonid", "arrow"); + runTests(); +}); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<!-- Our tests assume at least 100px around the anchor on all sides, else the + panel may flip when we don't expect it to +--> +<div id="anchor-middle-wrapper" style="margin: 100px 100px 100px 100px;"> + <p>The anchor --> <span id="anchor-middle">v</span> <--</p> +</div> +<div id="anchor-left-wrapper" style="text-align: left; display: none;"> + <p><span id="anchor-left">v</span> <-- The anchor;</p> +</div> +<div id="anchor-right-wrapper" style="text-align: right; display: none;"> + <p>The anchor --> <span id="anchor-right">v</span></p> +</div> +</body> + +</window> diff --git a/toolkit/content/tests/widgets/test_popupreflows.xul b/toolkit/content/tests/widgets/test_popupreflows.xul new file mode 100644 index 000000000..6969f7767 --- /dev/null +++ b/toolkit/content/tests/widgets/test_popupreflows.xul @@ -0,0 +1,111 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Reflow Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <panel id="testPanel" + type="arrow" + noautohide="true"> + </panel> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +<![CDATA[ +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Promise.jsm"); + +let panel, anchor, arrow; + +// A reflow observer - it just remembers the stack trace of all sync reflows +// done by the panel. +let observer = { + reflows: [], + reflow: function (start, end) { + // Ignore reflows triggered by native code + // (Reflows from native code only have an empty stack after the first frame) + var path = (new Error().stack).split("\n").slice(1).join(""); + if (path === "") { + return; + } + + this.reflows.push(new Error().stack); + }, + + reflowInterruptible: function (start, end) { + // We're not interested in interruptible reflows. Why, you ask? Because + // we've simply cargo-culted this test from browser_tabopen_reflows.js! + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver, + Ci.nsISupportsWeakReference]) +}; + +// A test utility that counts the reflows caused by a test function. If the +// count of reflows isn't what is expected, it causes a test failure and logs +// the stack trace of all seen reflows. +function countReflows(testfn, expected) { + let deferred = Promise.defer(); + observer.reflows = []; + let docShell = panel.ownerDocument.defaultView + .QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebNavigation) + .QueryInterface(Components.interfaces.nsIDocShell); + docShell.addWeakReflowObserver(observer); + testfn().then(() => { + docShell.removeWeakReflowObserver(observer); + SimpleTest.is(observer.reflows.length, expected, "correct number of reflows"); + if (observer.reflows.length != expected) { + SimpleTest.info("stack traces of reflows:\n" + observer.reflows.join("\n") + "\n"); + } + deferred.resolve(); + }); + return deferred.promise +} + +function openPopup() { + let deferred = Promise.defer(); + panel.addEventListener("popupshown", function popupshown() { + panel.removeEventListener("popupshown", popupshown); + deferred.resolve(); + }); + panel.openPopup(anchor, "before_start"); + return deferred.promise +} + +// ******************** +// The actual tests... +// We only have one atm - simply open a popup. +// +function testSimplePanel() { + return openPopup(); +} + +// ******************** +// The test harness... +// +SimpleTest.waitForExplicitFinish(); + +addEventListener("load", function() { + anchor = document.getElementById("anchor"); + panel = document.getElementById("testPanel"); + arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow"); + + // Cancel the arrow panel slide-in transition (bug 767133) - we are only + // testing reflows in the core panel implementation and not reflows that may + // or may not be caused by transitioning.... + arrow.style.transition = "none"; + + // and off we go... + countReflows(testSimplePanel, 1).then(SimpleTest.finish); +}); +]]> +</script> +<body xmlns="http://www.w3.org/1999/xhtml"> + <p>The anchor --> <span id="anchor">v</span> <--</p> +</body> +</window> diff --git a/toolkit/content/tests/widgets/test_tree_column_reorder.xul b/toolkit/content/tests/widgets/test_tree_column_reorder.xul new file mode 100644 index 000000000..5315fee43 --- /dev/null +++ b/toolkit/content/tests/widgets/test_tree_column_reorder.xul @@ -0,0 +1,76 @@ +<?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"?> +<!-- + XUL Widget Test for reordering tree columns + --> +<window title="Tree" width="500" height="600" + onload="setTimeout(testtag_tree_column_reorder, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script src="tree_shared.js"/> + +<tree id="tree-column-reorder" rows="1" enableColumnDrag="true"> + <treecols> + <treecol id="col_0" label="col_0" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_1" label="col_1" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_2" label="col_2" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_3" label="col_3" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_4" label="col_4" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_5" label="col_5" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_6" label="col_6" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_7" label="col_7" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_8" label="col_8" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_9" label="col_9" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_10" label="col_10" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_11" label="col_11" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_12" label="col_12" flex="1"/> + </treecols> + <treechildren id="treechildren-column-reorder"> + <treeitem> + <treerow> + <treecell label="col_0"/> + <treecell label="col_1"/> + <treecell label="col_2"/> + <treecell label="col_3"/> + <treecell label="col_4"/> + <treecell label="col_5"/> + <treecell label="col_6"/> + <treecell label="col_7"/> + <treecell label="col_8"/> + <treecell label="col_9"/> + <treecell label="col_10"/> + <treecell label="col_11"/> + <treecell label="col_12"/> + </treerow> + </treeitem> + </treechildren> +</tree> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> + diff --git a/toolkit/content/tests/widgets/test_videocontrols.html b/toolkit/content/tests/widgets/test_videocontrols.html new file mode 100644 index 000000000..191aaef58 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols.html @@ -0,0 +1,411 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <video width="320" height="240" id="video" controls mozNoDynamicControls preload="auto"></video> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/* + * Positions of the UI elements, relative to the upper-left corner of the + * <video> box. + */ +const videoWidth = 320; +const videoHeight = 240; +const videoDuration = 3.8329999446868896; + +const playButtonWidth = 28; +const playButtonHeight = 28; +const muteButtonWidth = 33; +const muteButtonHeight = 28; +const durationWidth = 34; +const fullscreenButtonWidth = 28; +const fullscreenButtonHeight = 28; +const volumeSliderWidth = 32; +const scrubberWidth = videoWidth - playButtonWidth - durationWidth - muteButtonWidth - volumeSliderWidth - fullscreenButtonWidth; +const scrubberHeight = 28; + +// Play button is on the bottom-left +const playButtonCenterX = 0 + Math.round(playButtonWidth / 2); +const playButtonCenterY = videoHeight - Math.round(playButtonHeight / 2); +// Mute button is on the bottom-right before the full screen button and volume slider +const muteButtonCenterX = videoWidth - Math.round(muteButtonWidth / 2) - volumeSliderWidth - fullscreenButtonWidth; +const muteButtonCenterY = videoHeight - Math.round(muteButtonHeight / 2); +// Fullscreen button is on the bottom-right at the far end +const fullscreenButtonCenterX = videoWidth - Math.round(fullscreenButtonWidth / 2); +const fullscreenButtonCenterY = videoHeight - Math.round(fullscreenButtonHeight / 2); +// Scrubber bar is between the play and mute buttons. We don't need it's +// X center, just the offset of its box. +const scrubberOffsetX = 0 + playButtonWidth; +const scrubberCenterY = videoHeight - Math.round(scrubberHeight / 2); + +var testnum = 1; +var video = document.getElementById("video"); + +const domUtil = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + +function getButtonByAttribute(aName, aValue) { + var kids = domUtil.getChildrenForNode(video, true); + var videocontrols = kids[1]; + return SpecialPowers.wrap(document) + .getAnonymousElementByAttribute(videocontrols, aName, aValue); +} + +function isMuteButtonMuted() { + var muteButton = getButtonByAttribute('class', 'muteButton'); + return muteButton.getAttribute('muted') === 'true'; +} + +function isVolumeSliderShowingCorrectVolume(expectedVolume) { + var volumeButton = getButtonByAttribute('anonid', 'volumeForeground'); + let expectedPaddingRight = (1 - expectedVolume) * volumeSliderWidth + "px"; + is(volumeButton.style.paddingRight, expectedPaddingRight, + "volume slider should match expected volume"); +} + +function forceReframe() { + // Setting display then getting the bounding rect to force a frame + // reconstruction on the video element. + video.style.display = "block"; + video.getBoundingClientRect(); + video.style.display = ""; + video.getBoundingClientRect(); +} + +function runTest(event) { + ok(true, "----- test #" + testnum + " -----"); + + switch (testnum) { + /* + * Check operation of play/pause/mute/unmute buttons. + */ + case 1: + // Check initial state upon load + is(event.type, "canplaythrough", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, false, "checking video mute state"); + + // Click the play button + SimpleTest.executeSoon(() => { + synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { }); + }); + break; + + case 2: + is(event.type, "play", "checking event type"); + is(video.paused, false, "checking video play state"); + is(video.muted, false, "checking video mute state"); + + // Click the pause button + SimpleTest.executeSoon(() => { + synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { }); + }); + break; + + case 3: + is(event.type, "pause", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, false, "checking video mute state"); + + SimpleTest.executeSoon(() => { + synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); // Mute. + }); + break; + + case 4: + is(event.type, "volumechange", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, true, "checking video mute state"); + + SimpleTest.executeSoon(() => { + synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); // Unmute. + }); + break; + + /* + * Bug 470596: Make sure that having CSS border or padding doesn't + * break the controls (though it should move them) + */ + case 5: + is(event.type, "volumechange", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, false, "checking video mute state"); + + video.style.border = "medium solid purple"; + video.style.borderWidth = "30px 40px 50px 60px"; + video.style.padding = "10px 20px 30px 40px"; + // totals: top: 40px, right: 60px, bottom: 80px, left: 100px + + // Click the play button + SimpleTest.executeSoon(() => { + synthesizeMouse(video, 100 + playButtonCenterX, 40 + playButtonCenterY, { }); + }); + break; + + case 6: + is(event.type, "play", "checking event type"); + is(video.paused, false, "checking video play state"); + is(video.muted, false, "checking video mute state"); + video.pause(); + break; + + case 7: + is(event.type, "pause", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, false, "checking video mute state"); + + // Click the mute button + SimpleTest.executeSoon(() => { + synthesizeMouse(video, 100 + muteButtonCenterX, 40 + muteButtonCenterY, { }); + }); + break; + + case 8: + is(event.type, "volumechange", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, true, "checking video mute state"); + // Clear the style set in test 5. + video.style.border = ""; + video.style.borderWidth = ""; + video.style.padding = ""; + + video.muted = false; + break; + + /* + * Previous tests have moved playback postion, reset it to 0. + */ + case 9: + is(event.type, "volumechange", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, false, "checking video mute state"); + ok(true, "video position is at " + video.currentTime); + video.currentTime = 0.0; + break; + + case 10: + is(event.type, "seeking", "checking event type"); + ok(true, "video position is at " + video.currentTime); + break; + + /* + * Drag the slider's thumb to the halfway point with the mouse. + */ + case 11: + is(event.type, "seeked", "checking event type"); + ok(true, "video position is at " + video.currentTime); + // Bug 477434 -- sometimes we get 0.098999 here instead of 0! + // is(video.currentTime, 0.0, "checking playback position"); + + SimpleTest.executeSoon(() => { + var beginDragX = scrubberOffsetX; + var endDragX = scrubberOffsetX + (scrubberWidth / 2); + synthesizeMouse(video, beginDragX, scrubberCenterY, { type: "mousedown", button: 0 }); + synthesizeMouse(video, endDragX, scrubberCenterY, { type: "mousemove", button: 0 }); + synthesizeMouse(video, endDragX, scrubberCenterY, { type: "mouseup", button: 0 }); + }); + break; + + case 12: + is(event.type, "seeking", "checking event type"); + ok(true, "video position is at " + video.currentTime); + break; + + /* + * Click the slider at the 1/4 point with the mouse (jump backwards) + */ + case 13: + is(event.type, "seeked", "checking event type"); + ok(true, "video position is at " + video.currentTime); + var expectedTime = videoDuration / 2; + ok(Math.abs(video.currentTime - expectedTime) < 0.1, "checking expected playback position"); + + SimpleTest.executeSoon(() => { + synthesizeMouse(video, scrubberOffsetX + (scrubberWidth / 4), scrubberCenterY, { }); + }); + break; + + case 14: + is(event.type, "seeking", "checking event type"); + ok(true, "video position is at " + video.currentTime); + break; + + case 15: + is(event.type, "seeked", "checking event type"); + ok(true, "video position is at " + video.currentTime); + // The scrubber currently just jumps towards the nearest pageIncrement point, not + // precisely to the point clicked. So, expectedTime isn't (videoDuration / 4). + // We should end up at 1.733, but sometimes we end up at 1.498. I guess + // it's timing depending if the <scale> things it's click-and-hold, or a + // single click. So, just make sure we end up less that the previous + // position. + lastPosition = (videoDuration / 2) - 0.1; + ok(video.currentTime < lastPosition, "checking expected playback position"); + + // Set volume to 0.1 so one down arrow hit will decrease it to 0. + video.volume = 0.1; + break; + + // See bug 694696. + case 16: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.1, "Volume should be set."); + ok(!video.muted, "Video is not muted."); + + video.focus(); + SimpleTest.executeSoon(() => synthesizeKey("VK_DOWN", {})); + break; + + case 17: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0, "Volume should be 0."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => { + ok(isMuteButtonMuted(), "Mute button says it's muted"); + synthesizeKey("VK_UP", {}); + }); + break; + + case 18: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.1, "Volume is increased."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => { + ok(!isMuteButtonMuted(), "Mute button says it's not muted"); + synthesizeKey("VK_DOWN", {}); + }); + break; + + case 19: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0, "Volume should be 0."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => { + ok(isMuteButtonMuted(), "Mute button says it's muted"); + synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); + }); + break; + + case 20: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.5, "Volume should be 0.5."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => synthesizeKey("VK_UP", {})); + break; + + case 21: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.6, "Volume should be 0.6."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => { + synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); + }); + break; + + case 22: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.6, "Volume should be 0.6."); + ok(video.muted, "Video is muted."); + + SimpleTest.executeSoon(() => { + ok(isMuteButtonMuted(), "Mute button says it's muted"); + synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); + }); + break; + + case 23: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.6, "Volume should be 0.6."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => { + ok(!isMuteButtonMuted(), "Mute button says it's not muted"); + synthesizeMouse(video, fullscreenButtonCenterX, fullscreenButtonCenterY, { }); + }); + break; + + case 24: + is(event.type, "mozfullscreenchange", "checking event type"); + is(video.volume, 0.6, "Volume should still be 0.6"); + SimpleTest.executeSoon(function() { + isVolumeSliderShowingCorrectVolume(video.volume); + synthesizeKey("VK_ESCAPE", {}); + }); + break; + + case 25: + is(event.type, "mozfullscreenchange", "checking event type"); + is(video.volume, 0.6, "Volume should still be 0.6"); + SimpleTest.executeSoon(function() { + isVolumeSliderShowingCorrectVolume(video.volume); + forceReframe(); + video.focus(); + synthesizeKey("VK_DOWN", {}); + }); + break; + + case 26: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.5, "Volume should be decreased by 0.1"); + SimpleTest.executeSoon(function() { + isVolumeSliderShowingCorrectVolume(video.volume); + SimpleTest.finish(); + }); + break; + + default: + throw "unexpected test #" + testnum + " w/ event " + event.type; + } + + testnum++; +} + + + +function canplaythroughevent(event) { + video.removeEventListener("canplaythrough", canplaythroughevent, false); + // Other events expected by the test. + video.addEventListener("play", runTest, false); + video.addEventListener("pause", runTest, false); + video.addEventListener("volumechange", runTest, false); + video.addEventListener("seeking", runTest, false); + video.addEventListener("seeked", runTest, false); + document.addEventListener("mozfullscreenchange", runTest, false); + // Begin the test. + runTest(event); +} + +function startMediaLoad() { + // Kick off test once video has loaded, in its canplaythrough event handler. + video.src = "seek_with_sound.ogg"; + video.addEventListener("canplaythrough", canplaythroughevent, false); +} + +function loadevent(event) { + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startMediaLoad); +} + +window.addEventListener("load", loadevent, false); + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_audio.html b/toolkit/content/tests/widgets/test_videocontrols_audio.html new file mode 100644 index 000000000..7d1dc32e3 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_audio.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls with Audio file test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <video id="video" controls preload="metadata"></video> +</div> + +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + + var domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]. + getService(SpecialPowers.Ci.inIDOMUtils); + + function findElementByAttribute(element, aName, aValue) { + if (!('getAttribute' in element)) { + return false; + } + if (element.getAttribute(aName) === aValue) { + return element; + } + let children = domUtils.getChildrenForNode(element, true); + for (let child of children) { + var result = findElementByAttribute(child, aName, aValue); + if (result) { + return result; + } + } + return false; + } + + function loadedmetadata(event) { + SimpleTest.executeSoon(function() { + var controlBar = findElementByAttribute(video, "class", "controlBar"); + is(controlBar.getAttribute("fullscreen-unavailable"), "true", "Fullscreen button is hidden"); + SimpleTest.finish(); + }); + } + + var video = document.getElementById("video"); + + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startTest); + function startTest() { + // Kick off test once audio has loaded. + video.addEventListener("loadedmetadata", loadedmetadata, false); + video.src = "audio.ogg"; + } + + SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_audio_direction.html b/toolkit/content/tests/widgets/test_videocontrols_audio_direction.html new file mode 100644 index 000000000..2512b997a --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_audio_direction.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls directionality test</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var tests = [ + {op: "==", test: "videocontrols_direction-2a.html", ref: "videocontrols_direction-2-ref.html"}, + {op: "==", test: "videocontrols_direction-2b.html", ref: "videocontrols_direction-2-ref.html"}, + {op: "==", test: "videocontrols_direction-2c.html", ref: "videocontrols_direction-2-ref.html"}, + {op: "==", test: "videocontrols_direction-2d.html", ref: "videocontrols_direction-2-ref.html"}, + {op: "==", test: "videocontrols_direction-2e.html", ref: "videocontrols_direction-2-ref.html"} +]; + +</script> +<script type="text/javascript" src="videocontrols_direction_test.js"></script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_iframe_fullscreen.html b/toolkit/content/tests/widgets/test_videocontrols_iframe_fullscreen.html new file mode 100644 index 000000000..6391dcc1b --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_iframe_fullscreen.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test - iframe</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> +<iframe id="ifr1"></iframe> +<iframe id="ifr2" allowfullscreen></iframe> +</div> + +<pre id="test"> +<script clas="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + const domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]. + getService(SpecialPowers.Ci.inIDOMUtils); + const iframe1 = SpecialPowers.wrap(document.getElementById("ifr1")); + const iframe2 = SpecialPowers.wrap(document.getElementById("ifr2")); + const testCases = []; + + function checkIframeFullscreenAvailable(ifr) { + const available = ifr.hasAttribute("allowfullscreen"); + let video; + + return () => new Promise(resolve => { + ifr.srcdoc = `<video id="video" controls preload="auto"></video>`; + ifr.addEventListener("load", resolve, false); + }).then(() => new Promise(resolve => { + video = ifr.contentDocument.getElementById("video"); + video.src = "seek_with_sound.ogg"; + video.addEventListener("loadedmetadata", resolve, false); + })).then(() => new Promise(resolve => { + const videoControl = domUtils.getChildrenForNode(video, true)[1]; + const controlBar = video.ownerDocument.getAnonymousElementByAttribute( + videoControl, "class", "controlBar"); + + is(controlBar.getAttribute("fullscreen-unavailable") == "true", !available, "The controlbar should have an attribute marking whether fullscreen is available that corresponds to if the iframe has the allowfullscreen attribute."); + resolve(); + })); + } + + function start() { + testCases.reduce((promise, task) => promise.then(task), Promise.resolve()); + } + + function load() { + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, start); + } + + testCases.push(checkIframeFullscreenAvailable(iframe1)); + testCases.push(checkIframeFullscreenAvailable(iframe2)); + testCases.push(SimpleTest.finish); + + window.addEventListener("load", load, false); +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_jsdisabled.html b/toolkit/content/tests/widgets/test_videocontrols_jsdisabled.html new file mode 100644 index 000000000..f57cda063 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_jsdisabled.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function runTest(event) { + info(true, "----- test #" + testnum + " -----"); + + switch (testnum) { + case 1: + is(event.type, "timeupdate", "checking event type"); + is(video.paused, false, "checking video play state"); + video.removeEventListener("timeupdate", runTest); + + // Click to toggle play/pause (now pausing) + synthesizeMouseAtCenter(video, {}, win); + break; + + case 2: + is(event.type, "pause", "checking event type"); + is(video.paused, true, "checking video play state"); + win.close(); + + SimpleTest.finish(); + break; + + default: + ok(false, "unexpected test #" + testnum + " w/ event " + event.type); + throw "unexpected test #" + testnum + " w/ event " + event.type; + } + + testnum++; +} + +SpecialPowers.pushPrefEnv({"set": [["javascript.enabled", false]]}, startTest); + +var testnum = 1; + +var video; +function loadevent(event) { + is(win["testExpando"], undefined, "expando shouldn't exist because js is disabled"); + video = win.document.querySelector("video"); + // Other events expected by the test. + video.addEventListener("timeupdate", runTest, false); + video.addEventListener("pause", runTest, false); +} + +var win; +function startTest() { + var videoURL = new URL("seek_with_sound.ogg", document.documentURI).href; + var url = "data:text/html,<video src=" + videoURL + " controls autoplay=true></video><script>window.testExpando = true;</scr" + "ipt>"; + + win = window.open(url); + win.addEventListener("load", loadevent, false); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_onclickplay.html b/toolkit/content/tests/widgets/test_videocontrols_onclickplay.html new file mode 100644 index 000000000..d681b3158 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_onclickplay.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <video id="video" controls mozNoDynamicControls preload="auto"></video> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +var video = document.getElementById("video"); + +function startMediaLoad() { + // Kick off test once video has loaded, in its canplaythrough event handler. + video.src = "seek_with_sound.ogg"; + video.addEventListener("canplaythrough", runTest, false); +} + +function loadevent(event) { + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startMediaLoad); +} + +window.addEventListener("load", loadevent, false); + +function runTest() { + video.addEventListener("click", function() { + this.play(); + }); + ok(video.paused, "video should be paused initially"); + + new Promise(resolve => { + let timeupdates = 0; + video.addEventListener("timeupdate", function timeupdate() { + ok(!video.paused, "video should not get paused after clicking in middle"); + + if (++timeupdates == 3) { + video.removeEventListener("timeupdate", timeupdate); + resolve(); + } + }); + + synthesizeMouseAtCenter(video, {}, window); + }).then(function() { + new Promise(resolve => { + video.addEventListener("pause", function onpause() { + setTimeout(() => { + ok(video.paused, "video should still be paused 200ms after pause request"); + // When the video reaches the end of playback it is automatically paused. + // Check during the pause event that the video has not reachd the end + // of playback. + ok(!video.ended, "video should not have paused due to playback ending"); + resolve(); + }, 200); + }); + + synthesizeMouse(video, 10, video.clientHeight - 10, {}, window); + }).then(SimpleTest.finish); + }); +} + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_standalone.html b/toolkit/content/tests/widgets/test_videocontrols_standalone.html new file mode 100644 index 000000000..8d1ce8984 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_standalone.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +const videoWidth = 320; +const videoHeight = 240; + +function getMediaElement(aWindow) { + return aWindow.document.getElementsByTagName("video")[0]; +} + +var popup = window.open("seek_with_sound.ogg"); +popup.addEventListener("load", function onLoad() { + popup.removeEventListener("load", onLoad); + var video = getMediaElement(popup); + if (!video.paused) + runTestVideo(video); + else { + video.addEventListener("play", function onPlay() { + video.removeEventListener("play", onPlay); + runTestVideo(video); + }); + } +}); + +function runTestVideo(aVideo) { + var condition = function() { + var boundingRect = aVideo.getBoundingClientRect(); + return boundingRect.width == videoWidth && + boundingRect.height == videoHeight; + }; + waitForCondition(condition, function() { + var boundingRect = aVideo.getBoundingClientRect(); + is(boundingRect.width, videoWidth, "Width of the video should match expectation"); + is(boundingRect.height, videoHeight, "Height of video should match expectation"); + popup.close(); + runTestAudioPre(); + }, "The media element should eventually be resized to match the intrinsic size of the video."); +} + +function runTestAudioPre() { + popup = window.open("audio.ogg"); + popup.addEventListener("load", function onLoad() { + popup.removeEventListener("load", onLoad); + var audio = getMediaElement(popup); + if (!audio.paused) + runTestAudio(audio); + else { + audio.addEventListener("play", function onPlay() { + audio.removeEventListener("play", onPlay); + runTestAudio(audio); + }) + } + }) +} + +function runTestAudio(aAudio) { + info("User agent (help diagnose bug #943556): " + navigator.userAgent); + var isAndroid = navigator.userAgent.includes("Android"); + var expectedHeight = isAndroid ? 103 : 28; + var condition = function () { + var boundingRect = aAudio.getBoundingClientRect(); + return boundingRect.height == expectedHeight; + }; + waitForCondition(condition, function () { + var boundingRect = aAudio.getBoundingClientRect(); + is(boundingRect.height, expectedHeight, + "Height of audio element should be " + expectedHeight + ", which is equal to the controls bar."); + popup.close(); + SimpleTest.finish(); + }, "The media element should eventually be resized to match the height of the audio controls."); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_video_direction.html b/toolkit/content/tests/widgets/test_videocontrols_video_direction.html new file mode 100644 index 000000000..54e0d5e72 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_video_direction.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls directionality test</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var tests = [ + {op: "==", test: "videocontrols_direction-1a.html", ref: "videocontrols_direction-1-ref.html"}, + {op: "==", test: "videocontrols_direction-1b.html", ref: "videocontrols_direction-1-ref.html"}, + {op: "==", test: "videocontrols_direction-1c.html", ref: "videocontrols_direction-1-ref.html"}, + {op: "==", test: "videocontrols_direction-1d.html", ref: "videocontrols_direction-1-ref.html"}, + {op: "==", test: "videocontrols_direction-1e.html", ref: "videocontrols_direction-1-ref.html"}, +]; + +</script> +<script type="text/javascript" src="videocontrols_direction_test.js"></script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_vtt.html b/toolkit/content/tests/widgets/test_videocontrols_vtt.html new file mode 100644 index 000000000..27052b770 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_vtt.html @@ -0,0 +1,133 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test - VTT</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <video id="video" controls preload="auto"></video> +</div> + +<pre id="test"> +<script clas="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + const domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]. + getService(SpecialPowers.Ci.inIDOMUtils); + const video = document.getElementById("video"); + const ccBtn = getElementByAttribute("class", "closedCaptionButton"); + const ttList = getElementByAttribute("class", "textTrackList"); + const testCases = []; + + testCases.push(() => new Promise(resolve => { + is(ccBtn.getAttribute("hidden"), "true", "CC button should hide"); + + resolve(); + })); + + testCases.push(() => new Promise(resolve => { + video.addTextTrack("descriptions", "English", "en"); + video.addTextTrack("chapters", "English", "en"); + video.addTextTrack("metadata", "English", "en"); + + SimpleTest.executeSoon(() => { + is(ccBtn.getAttribute("hidden"), "true", "CC button should hide if no supported tracks provided"); + + resolve(); + }); + })); + + testCases.push(() => new Promise(resolve => { + const sub = video.addTextTrack("subtitles", "English", "en"); + sub.mode = "disabled"; + + SimpleTest.executeSoon(() => { + is(ccBtn.getAttribute("hidden"), "", "CC button should show"); + is(ccBtn.getAttribute("enabled"), "", "CC button should be disabled"); + + resolve(); + }); + })); + + testCases.push(() => new Promise(resolve => { + const subtitle = video.addTextTrack("subtitles", "English", "en"); + subtitle.mode = "showing"; + + SimpleTest.executeSoon(() => { + is(ccBtn.getAttribute("enabled"), "true", "CC button should be enabled"); + subtitle.mode = "disabled"; + + resolve(); + }); + })); + + testCases.push(() => new Promise(resolve => { + const caption = video.addTextTrack("captions", "English", "en"); + caption.mode = "showing"; + + SimpleTest.executeSoon(() => { + is(ccBtn.getAttribute("enabled"), "true", "CC button should be enabled"); + + resolve(); + }); + })); + + testCases.push(() => new Promise(resolve => { + synthesizeMouseAtCenter(ccBtn, {}); + + SimpleTest.executeSoon(() => { + is(ttList.hasAttribute("hidden"), false, "Texttrack menu should show up"); + is(ttList.lastChild.getAttribute("on"), "true", "The last added item should be highlighted"); + + resolve(); + }); + })); + + testCases.push(() => new Promise(resolve => { + const tt = ttList.children[1]; + + isnot(tt.getAttribute("on"), "true", "Item should be off before click"); + synthesizeMouseAtCenter(tt, {}); + + SimpleTest.executeSoon(() => { + is(tt.getAttribute("on"), "true", "Selected item should be enabled"); + is(ttList.getAttribute("hidden"), "true", "Should hide texttrack menu once clicked on an item"); + + resolve(); + }); + })); + + function executeTestCases(tasks) { + return tasks.reduce((promise, task) => promise.then(task), Promise.resolve()); + } + + function getElementByAttribute(aName, aValue) { + const videoControl = domUtils.getChildrenForNode(video, true)[1]; + + return SpecialPowers.wrap(document) + .getAnonymousElementByAttribute(videoControl, aName, aValue); + } + + function loadedmetadata() { + executeTestCases(testCases).then(SimpleTest.finish); + } + + function startMediaLoad() { + video.src = "seek_with_sound.ogg"; + video.addEventListener("loadedmetadata", loadedmetadata, false); + } + + function loadevent() { + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startMediaLoad); + } + + window.addEventListener("load", loadevent, false); +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/tree_shared.js b/toolkit/content/tests/widgets/tree_shared.js new file mode 100644 index 000000000..b157bdf56 --- /dev/null +++ b/toolkit/content/tests/widgets/tree_shared.js @@ -0,0 +1,1405 @@ +var columns_simpletree = +[ + { name: "name", label: "Name", key: true, properties: "one two" }, + { name: "address", label: "Address" } +]; + +var columns_hiertree = +[ + { name: "name", label: "Name", primary: true, key: true, properties: "one two" }, + { name: "address", label: "Address" }, + { name: "planet", label: "Planet" }, + { name: "gender", label: "Gender", cycler: true } +]; + +// XXXndeakin still to add some tests for: +// cycler columns, checkbox cells, progressmeter cells + +// this test function expects a tree to have 8 rows in it when it isn't +// expanded. The tree should only display four rows at a time. If editable, +// the cell at row 1 and column 0 must be editable, and the cell at row 2 and +// column 1 must not be editable. +function testtag_tree(treeid, treerowinfoid, seltype, columnstype, testid) +{ + // Stop keystrokes that aren't handled by the tree from leaking out and + // scrolling the main Mochitests window! + function preventDefault(event) { + event.preventDefault(); + } + document.addEventListener("keypress", preventDefault, false); + + var multiple = (seltype == "multiple"); + + var tree = document.getElementById(treeid); + var treerowinfo = document.getElementById(treerowinfoid); + var rowInfo; + if (testid =="tree view") + rowInfo = getCustomTreeViewCellInfo(); + else + rowInfo = convertDOMtoTreeRowInfo(treerowinfo, 0, { value: -1 }); + var columnInfo = (columnstype == "simple") ? columns_simpletree : columns_hiertree; + + is(tree.view.selection.currentColumn, null, testid + " initial currentColumn"); + is(tree.selType, seltype == "multiple" ? "" : seltype, testid + " seltype"); + + // note: the functions below should be in this order due to changes made in later tests + + // select the first column in cell selection mode so that the selection + // functions can be tested + if (seltype == "cell") + tree.view.selection.currentColumn = tree.columns[0]; + + testtag_tree_columns(tree, columnInfo, testid); + testtag_tree_TreeSelection(tree, testid, multiple); + testtag_tree_TreeSelection_UI(tree, testid, multiple); + if (seltype == "cell") + testtag_tree_TreeSelection_UI_cell(tree, testid, rowInfo); + + testtag_tree_TreeView(tree, testid, rowInfo); + + is(tree.editable, false, "tree should not be editable"); + // currently, the editable flag means that tree editing cannot be invoked + // by the user. However, editing can still be started with a script. + is(tree.editingRow, -1, testid + " initial editingRow"); + is(tree.editingColumn, null, testid + " initial editingColumn"); + + testtag_tree_UI_editing(tree, testid, rowInfo); + + is(tree.editable, false, "tree should not be editable after testtag_tree_UI_editing"); + // currently, the editable flag means that tree editing cannot be invoked + // by the user. However, editing can still be started with a script. + is(tree.editingRow, -1, testid + " initial editingRow (continued)"); + is(tree.editingColumn, null, testid + " initial editingColumn (continued)"); + + var ecolumn = tree.columns[0]; + ok(!tree.startEditing(1, ecolumn), "non-editable trees shouldn't start editing"); + is(tree.editingRow, -1, testid + " failed startEditing shouldn't set editingRow"); + is(tree.editingColumn, null, testid + " failed startEditing shouldn't set editingColumn"); + + tree.editable = true; + + ok(tree.startEditing(1, ecolumn), "startEditing should have returned true"); + is(tree.editingRow, 1, testid + " startEditing editingRow"); + is(tree.editingColumn, ecolumn, testid + " startEditing editingColumn"); + is(tree.getAttribute("editing"), "true", testid + " startEditing editing attribute"); + + tree.stopEditing(true); + is(tree.editingRow, -1, testid + " stopEditing editingRow"); + is(tree.editingColumn, null, testid + " stopEditing editingColumn"); + is(tree.hasAttribute("editing"), false, testid + " stopEditing editing attribute"); + + tree.startEditing(-1, ecolumn); + is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing -1 editingRow"); + tree.startEditing(15, ecolumn); + is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing 15 editingRow"); + tree.startEditing(1, null); + is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing null column editingRow"); + tree.startEditing(2, tree.columns[1]); + is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing non editable cell editingRow"); + + tree.startEditing(1, ecolumn); + var inputField = tree.inputField; + is(inputField instanceof Components.interfaces.nsIDOMXULTextBoxElement, true, testid + "inputField"); + inputField.value = "Changed Value"; + tree.stopEditing(true); + is(tree.view.getCellText(1, ecolumn), "Changed Value", testid + "edit cell accept"); + + // this cell can be edited, but stopEditing(false) means don't accept the change. + tree.startEditing(1, ecolumn); + inputField.value = "Second Value"; + tree.stopEditing(false); + is(tree.view.getCellText(1, ecolumn), "Changed Value", testid + "edit cell no accept"); + + tree.editable = false; + + // do the sorting tests last as it will cause the rows to rearrange + // skip them for the custom tree view + if (testid !="tree view") + testtag_tree_TreeView_rows_sort(tree, testid, rowInfo); + + testtag_tree_wheel(tree); + + document.removeEventListener("keypress", preventDefault, false); + + SimpleTest.finish(); +} + +function testtag_tree_columns(tree, expectedColumns, testid) +{ + testid += " "; + + var columns = tree.columns; + + is(columns instanceof TreeColumns, true, testid + "columns is a TreeColumns"); + is(columns.count, expectedColumns.length, testid + "TreeColumns count"); + is(columns.length, expectedColumns.length, testid + "TreeColumns length"); + + var treecols = tree.getElementsByTagName("treecols")[0]; + var treecol = treecols.getElementsByTagName("treecol"); + + var x = 0; + var primary = null, sorted = null, key = null; + for (var c = 0; c < expectedColumns.length; c++) { + var adjtestid = testid + " column " + c + " "; + var column = columns[c]; + var expectedColumn = expectedColumns[c]; + is(columns.getColumnAt(c), column, adjtestid + "getColumnAt"); + is(columns.getNamedColumn(expectedColumn.name), column, adjtestid + "getNamedColumn"); + is(columns.getColumnFor(treecol[c]), column, adjtestid + "getColumnFor"); + if (expectedColumn.primary) + primary = column; + if (expectedColumn.sorted) + sorted = column; + if (expectedColumn.key) + key = column; + + // XXXndeakin on Windows and Linux, some columns are one pixel to the + // left of where they should be. Could just be a rounding issue. + var adj = 1; + is(column.x + adj >= x, true, adjtestid + "position is after last column " + + column.x + "," + column.width + "," + x); + is(column.width > 0, true, adjtestid + "width is greater than 0"); + x = column.x + column.width; + + // now check the TreeColumn properties + is(column instanceof TreeColumn, true, adjtestid + "is a TreeColumn"); + is(column.element, treecol[c], adjtestid + "element is treecol"); + is(column.columns, columns, adjtestid + "columns is TreeColumns"); + is(column.id, expectedColumn.name, adjtestid + "name"); + is(column.index, c, adjtestid + "index"); + is(column.primary, primary == column, adjtestid + "column is primary"); + + is(column.cycler, "cycler" in expectedColumn && expectedColumn.cycler, + adjtestid + "column is cycler"); + is(column.selectable, true, adjtestid + "column is selectable"); + is(column.editable, "editable" in expectedColumn && expectedColumn.editable, + adjtestid + "column is editable"); + + is(column.type, "type" in expectedColumn ? expectedColumn.type : 1, adjtestid + "type"); + + is(column.getPrevious(), c > 0 ? columns[c - 1] : null, adjtestid + "getPrevious"); + is(column.getNext(), c < columns.length - 1 ? columns[c + 1] : null, adjtestid + "getNext"); + + // check the view's getColumnProperties method + var properties = tree.view.getColumnProperties(column); + var expectedProperties = expectedColumn.properties; + is(properties, expectedProperties ? expectedProperties : "", adjtestid + "getColumnProperties"); + } + + is(columns.getFirstColumn(), columns[0], testid + "getFirstColumn"); + is(columns.getLastColumn(), columns[columns.length - 1], testid + "getLastColumn"); + is(columns.getPrimaryColumn(), primary, testid + "getPrimaryColumn"); + is(columns.getSortedColumn(), sorted, testid + "getSortedColumn"); + is(columns.getKeyColumn(), key, testid + "getKeyColumn"); + + is(columns.getColumnAt(-1), null, testid + "getColumnAt under"); + is(columns.getColumnAt(columns.length), null, testid + "getColumnAt over"); + is(columns.getNamedColumn(""), null, testid + "getNamedColumn null"); + is(columns.getNamedColumn("unknown"), null, testid + "getNamedColumn unknown"); + is(columns.getColumnFor(null), null, testid + "getColumnFor null"); + is(columns.getColumnFor(tree), null, testid + "getColumnFor other"); +} + +function testtag_tree_TreeSelection(tree, testid, multiple) +{ + testid += " selection "; + + var selection = tree.view.selection; + is(selection instanceof Components.interfaces.nsITreeSelection, true, + testid + "selection is a TreeSelection"); + is(selection.single, !multiple, testid + "single"); + + testtag_tree_TreeSelection_State(tree, testid + "initial", -1, []); + is(selection.shiftSelectPivot, -1, testid + "initial shiftSelectPivot"); + + selection.currentIndex = 2; + testtag_tree_TreeSelection_State(tree, testid + "set currentIndex", 2, []); + tree.currentIndex = 3; + testtag_tree_TreeSelection_State(tree, testid + "set tree.currentIndex", 3, []); + + // test the select() method, which should deselect all rows and select + // a single row + selection.select(1); + testtag_tree_TreeSelection_State(tree, testid + "select 1", 1, [1]); + selection.select(3); + testtag_tree_TreeSelection_State(tree, testid + "select 2", 3, [3]); + selection.select(3); + testtag_tree_TreeSelection_State(tree, testid + "select same", 3, [3]); + + selection.currentIndex = 1; + testtag_tree_TreeSelection_State(tree, testid + "set currentIndex with single selection", 1, [3]); + + tree.currentIndex = 2; + testtag_tree_TreeSelection_State(tree, testid + "set tree.currentIndex with single selection", 2, [3]); + + // check the toggleSelect method. In single selection mode, it only toggles on when + // there isn't currently a selection. + selection.toggleSelect(2); + testtag_tree_TreeSelection_State(tree, testid + "toggleSelect 1", 2, multiple ? [2, 3] : [3]); + selection.toggleSelect(2); + selection.toggleSelect(3); + testtag_tree_TreeSelection_State(tree, testid + "toggleSelect 2", 3, []); + + // the current index doesn't change after a selectAll, so it should still be set to 1 + // selectAll has no effect on single selection trees + selection.currentIndex = 1; + selection.selectAll(); + testtag_tree_TreeSelection_State(tree, testid + "selectAll 1", 1, multiple ? [0, 1, 2, 3, 4, 5, 6, 7] : []); + selection.toggleSelect(2); + testtag_tree_TreeSelection_State(tree, testid + "toggleSelect after selectAll", 2, + multiple ? [0, 1, 3, 4, 5, 6, 7] : [2]); + selection.clearSelection(); + testtag_tree_TreeSelection_State(tree, testid + "clearSelection", 2, []); + selection.toggleSelect(3); + selection.toggleSelect(1); + if (multiple) { + selection.selectAll(); + testtag_tree_TreeSelection_State(tree, testid + "selectAll 2", 1, [0, 1, 2, 3, 4, 5, 6, 7]); + } + selection.currentIndex = 2; + selection.clearSelection(); + testtag_tree_TreeSelection_State(tree, testid + "clearSelection after selectAll", 2, []); + + // XXXndeakin invertSelection isn't implemented + // selection.invertSelection(); + + is(selection.shiftSelectPivot, -1, testid + "shiftSelectPivot set to -1"); + + // rangedSelect and clearRange set the currentIndex to the endIndex. The + // shiftSelectPivot property will be set to startIndex. + selection.rangedSelect(1, 3, false); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect no augment", + multiple ? 3 : 2, multiple ? [1, 2, 3] : []); + is(selection.shiftSelectPivot, multiple ? 1 : -1, + testid + "shiftSelectPivot after rangedSelect no augment"); + if (multiple) { + selection.select(1); + selection.rangedSelect(0, 2, true); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment", 2, [0, 1, 2]); + is(selection.shiftSelectPivot, 0, testid + "shiftSelectPivot after rangedSelect augment"); + + selection.clearRange(1, 3); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment", 3, [0]); + + // check that rangedSelect can take a start value higher than end + selection.rangedSelect(3, 1, false); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect reverse", 1, [1, 2, 3]); + is(selection.shiftSelectPivot, 3, testid + "shiftSelectPivot after rangedSelect reverse"); + + // check that setting the current index doesn't change the selection + selection.currentIndex = 0; + testtag_tree_TreeSelection_State(tree, testid + "currentIndex with range selection", 0, [1, 2, 3]); + } + + // both values of rangedSelect may be the same + selection.rangedSelect(2, 2, false); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect one row", 2, [2]); + is(selection.shiftSelectPivot, 2, testid + "shiftSelectPivot after selecting one row"); + + if (multiple) { + selection.rangedSelect(2, 3, true); + + // a start index of -1 means from the last point + selection.rangedSelect(-1, 0, true); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect -1 existing selection", 0, [0, 1, 2, 3]); + is(selection.shiftSelectPivot, 2, testid + "shiftSelectPivot after -1 existing selection"); + + selection.currentIndex = 2; + selection.rangedSelect(-1, 0, false); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect -1 from currentIndex", 0, [0, 1, 2]); + is(selection.shiftSelectPivot, 2, testid + "shiftSelectPivot -1 from currentIndex"); + } + + // XXXndeakin need to test out of range values but these don't work properly +/* + selection.select(-1); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment -1", -1, []); + + selection.select(8); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment 8", 3, [0]); +*/ +} + +function testtag_tree_TreeSelection_UI(tree, testid, multiple) +{ + testid += " selection UI "; + + var selection = tree.view.selection; + selection.clearSelection(); + selection.currentIndex = 0; + tree.focus(); + + var keydownFired = 0; + var keypressFired = 0; + function keydownListener(event) + { + keydownFired++; + } + function keypressListener(event) { + keypressFired++; + } + + // check that cursor up and down keys navigate up and down + // select event fires after a delay so don't expect it. The reason it fires after a delay + // is so that cursor navigation allows quicking skimming over a set of items without + // actually firing events in-between, improving performance. The select event will only + // be fired on the row where the cursor stops. + window.addEventListener("keydown", keydownListener, false); + window.addEventListener("keypress", keypressListener, false); + + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down"); + testtag_tree_TreeSelection_State(tree, testid + "key down", 1, [1], 0); + + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up"); + testtag_tree_TreeSelection_State(tree, testid + "key up", 0, [0], 0); + + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up at start"); + testtag_tree_TreeSelection_State(tree, testid + "key up at start", 0, [0], 0); + + // pressing down while the last row is selected should not fire a select event, + // as the selection won't have changed. Also the view is not scrolled in this case. + selection.select(7); + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down at end"); + testtag_tree_TreeSelection_State(tree, testid + "key down at end", 7, [7], 0); + + // pressing keys while at the edge of the visible rows should scroll the list + tree.treeBoxObject.scrollToRow(4); + selection.select(4); + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up with scroll"); + is(tree.treeBoxObject.getFirstVisibleRow(), 3, testid + "key up with scroll"); + + tree.treeBoxObject.scrollToRow(0); + selection.select(3); + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down with scroll"); + is(tree.treeBoxObject.getFirstVisibleRow(), 1, testid + "key down with scroll"); + + // accel key and cursor movement adjust currentIndex but should not change + // the selection. In single selection mode, the selection will not change, + // but instead will just scroll up or down a line + tree.treeBoxObject.scrollToRow(0); + selection.select(1); + synthesizeKeyExpectEvent("VK_DOWN", { accelKey: true }, tree, "!select", "key down with accel"); + testtag_tree_TreeSelection_State(tree, testid + "key down with accel", multiple ? 2 : 1, [1]); + if (!multiple) + is(tree.treeBoxObject.getFirstVisibleRow(), 1, testid + "key down with accel and scroll"); + + tree.treeBoxObject.scrollToRow(4); + selection.select(4); + synthesizeKeyExpectEvent("VK_UP", { accelKey: true }, tree, "!select", "key up with accel"); + testtag_tree_TreeSelection_State(tree, testid + "key up with accel", multiple ? 3 : 4, [4]); + if (!multiple) + is(tree.treeBoxObject.getFirstVisibleRow(), 3, testid + "key up with accel and scroll"); + + // do this three times, one for each state of pageUpOrDownMovesSelection, + // and then once with the accel key pressed + for (let t = 0; t < 3; t++) { + let testidmod = ""; + if (t == 2) + testidmod = " with accel" + else if (t == 1) + testidmod = " rev"; + var keymod = (t == 2) ? { accelKey: true } : { }; + + var moveselection = tree.pageUpOrDownMovesSelection; + if (t == 2) + moveselection = !moveselection; + + tree.treeBoxObject.scrollToRow(4); + selection.currentIndex = 6; + selection.select(6); + var expected = moveselection ? 4 : 6; + synthesizeKeyExpectEvent("VK_PAGE_UP", keymod, tree, "!select", "key page up"); + testtag_tree_TreeSelection_State(tree, testid + "key page up" + testidmod, + expected, [expected], moveselection ? 4 : 0); + + expected = moveselection ? 0 : 6; + synthesizeKeyExpectEvent("VK_PAGE_UP", keymod, tree, "!select", "key page up again"); + testtag_tree_TreeSelection_State(tree, testid + "key page up again" + testidmod, + expected, [expected], 0); + + expected = moveselection ? 0 : 6; + synthesizeKeyExpectEvent("VK_PAGE_UP", keymod, tree, "!select", "key page up at start"); + testtag_tree_TreeSelection_State(tree, testid + "key page up at start" + testidmod, + expected, [expected], 0); + + tree.treeBoxObject.scrollToRow(0); + selection.currentIndex = 1; + selection.select(1); + expected = moveselection ? 3 : 1; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", keymod, tree, "!select", "key page down"); + testtag_tree_TreeSelection_State(tree, testid + "key page down" + testidmod, + expected, [expected], moveselection ? 0 : 4); + + expected = moveselection ? 7 : 1; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", keymod, tree, "!select", "key page down again"); + testtag_tree_TreeSelection_State(tree, testid + "key page down again" + testidmod, + expected, [expected], 4); + + expected = moveselection ? 7 : 1; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", keymod, tree, "!select", "key page down at start"); + testtag_tree_TreeSelection_State(tree, testid + "key page down at start" + testidmod, + expected, [expected], 4); + + if (t < 2) + tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection; + } + + tree.treeBoxObject.scrollToRow(4); + selection.select(6); + synthesizeKeyExpectEvent("VK_HOME", {}, tree, "!select", "key home"); + testtag_tree_TreeSelection_State(tree, testid + "key home", 0, [0], 0); + + tree.treeBoxObject.scrollToRow(0); + selection.select(1); + synthesizeKeyExpectEvent("VK_END", {}, tree, "!select", "key end"); + testtag_tree_TreeSelection_State(tree, testid + "key end", 7, [7], 4); + + // in single selection mode, the selection doesn't change in this case + tree.treeBoxObject.scrollToRow(4); + selection.select(6); + synthesizeKeyExpectEvent("VK_HOME", { accelKey: true }, tree, "!select", "key home with accel"); + testtag_tree_TreeSelection_State(tree, testid + "key home with accel", multiple ? 0 : 6, [6], 0); + + tree.treeBoxObject.scrollToRow(0); + selection.select(1); + synthesizeKeyExpectEvent("VK_END", { accelKey: true }, tree, "!select", "key end with accel"); + testtag_tree_TreeSelection_State(tree, testid + "key end with accel", multiple ? 7 : 1, [1], 4); + + // next, test cursor navigation with selection. Here the select event will be fired + selection.select(1); + var eventExpected = multiple ? "select" : "!select"; + synthesizeKeyExpectEvent("VK_DOWN", { shiftKey: true }, tree, eventExpected, "key shift down to select"); + testtag_tree_TreeSelection_State(tree, testid + "key shift down to select", + multiple ? 2 : 1, multiple ? [1, 2] : [1]); + is(selection.shiftSelectPivot, multiple ? 1 : -1, + testid + "key shift down to select shiftSelectPivot"); + synthesizeKeyExpectEvent("VK_UP", { shiftKey: true }, tree, eventExpected, "key shift up to unselect"); + testtag_tree_TreeSelection_State(tree, testid + "key shift up to unselect", 1, [1]); + is(selection.shiftSelectPivot, multiple ? 1 : -1, + testid + "key shift up to unselect shiftSelectPivot"); + if (multiple) { + synthesizeKeyExpectEvent("VK_UP", { shiftKey: true }, tree, "select", "key shift up to select"); + testtag_tree_TreeSelection_State(tree, testid + "key shift up to select", 0, [0, 1]); + is(selection.shiftSelectPivot, 1, testid + "key shift up to select shiftSelectPivot"); + synthesizeKeyExpectEvent("VK_DOWN", { shiftKey: true }, tree, "select", "key shift down to unselect"); + testtag_tree_TreeSelection_State(tree, testid + "key shift down to unselect", 1, [1]); + is(selection.shiftSelectPivot, 1, testid + "key shift down to unselect shiftSelectPivot"); + } + + // do this twice, one for each state of pageUpOrDownMovesSelection, however + // when selecting with the shift key, pageUpOrDownMovesSelection is ignored + // and the selection always changes + var lastidx = tree.view.rowCount - 1; + for (let t = 0; t < 2; t++) { + let testidmod = (t == 0) ? "" : " rev"; + + // If the top or bottom visible row is the current row, pressing shift and + // page down / page up selects one page up or one page down. Otherwise, the + // selection is made to the top or bottom of the visible area. + tree.treeBoxObject.scrollToRow(lastidx - 3); + selection.currentIndex = 6; + selection.select(6); + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, eventExpected, "key shift page up"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up" + testidmod, + multiple ? 4 : 6, multiple ? [4, 5, 6] : [6]); + if (multiple) { + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "select", "key shift page up again"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up again" + testidmod, + 0, [0, 1, 2, 3, 4, 5, 6]); + // no change in the selection, so no select event should be fired + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "!select", "key shift page up at start"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up at start" + testidmod, + 0, [0, 1, 2, 3, 4, 5, 6]); + // deselect by paging down again + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "select", "key shift page down deselect"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down deselect" + testidmod, + 3, [3, 4, 5, 6]); + } + + tree.treeBoxObject.scrollToRow(1); + selection.currentIndex = 2; + selection.select(2); + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, eventExpected, "key shift page down"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down" + testidmod, + multiple ? 4 : 2, multiple ? [2, 3, 4] : [2]); + if (multiple) { + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "select", "key shift page down again"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down again" + testidmod, + 7, [2, 3, 4, 5, 6, 7]); + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "!select", "key shift page down at start"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down at start" + testidmod, + 7, [2, 3, 4, 5, 6, 7]); + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "select", "key shift page up deselect"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up deselect" + testidmod, + 4, [2, 3, 4]); + } + + // test when page down / page up is pressed when the view is scrolled such + // that the selection is not visible + if (multiple) { + tree.treeBoxObject.scrollToRow(3); + selection.currentIndex = 1; + selection.select(1); + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, eventExpected, + "key shift page down with view scrolled down"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down with view scrolled down" + testidmod, + 6, [1, 2, 3, 4, 5, 6], 3); + + tree.treeBoxObject.scrollToRow(2); + selection.currentIndex = 6; + selection.select(6); + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, eventExpected, + "key shift page up with view scrolled up"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up with view scrolled up" + testidmod, + 2, [2, 3, 4, 5, 6], 2); + + tree.treeBoxObject.scrollToRow(2); + selection.currentIndex = 0; + selection.select(0); + // don't expect the select event, as the selection won't have changed + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "!select", + "key shift page up at start with view scrolled down"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up at start with view scrolled down" + testidmod, + 0, [0], 0); + + tree.treeBoxObject.scrollToRow(0); + selection.currentIndex = 7; + selection.select(7); + // don't expect the select event, as the selection won't have changed + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "!select", + "key shift page down at end with view scrolled up"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down at end with view scrolled up" + testidmod, + 7, [7], 4); + } + + tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection; + } + + tree.treeBoxObject.scrollToRow(4); + selection.select(5); + synthesizeKeyExpectEvent("VK_HOME", { shiftKey: true }, tree, eventExpected, "key shift home"); + testtag_tree_TreeSelection_State(tree, testid + "key shift home", + multiple ? 0 : 5, multiple ? [0, 1, 2, 3, 4, 5] : [5], multiple ? 0 : 4); + + tree.treeBoxObject.scrollToRow(0); + selection.select(3); + synthesizeKeyExpectEvent("VK_END", { shiftKey: true }, tree, eventExpected, "key shift end"); + testtag_tree_TreeSelection_State(tree, testid + "key shift end", + multiple ? 7 : 3, multiple ? [3, 4, 5, 6, 7] : [3], multiple ? 4 : 0); + + // pressing space selects a row, pressing accel + space unselects a row + selection.select(2); + selection.currentIndex = 4; + synthesizeKeyExpectEvent(" ", {}, tree, "select", "key space on"); + // in single selection mode, space shouldn't do anything + testtag_tree_TreeSelection_State(tree, testid + "key space on", 4, multiple ? [2, 4] : [2]); + + if (multiple) { + synthesizeKeyExpectEvent(" ", { accelKey: true }, tree, "select", "key space off"); + testtag_tree_TreeSelection_State(tree, testid + "key space off", 4, [2]); + } + + // check that clicking on a row selects it + tree.treeBoxObject.scrollToRow(0); + selection.select(2); + selection.currentIndex = 2; + if (0) { // XXXndeakin disable these tests for now + mouseOnCell(tree, 1, tree.columns[1], "mouse on row"); + testtag_tree_TreeSelection_State(tree, testid + "mouse on row", 1, [1], 0, + tree.selType == "cell" ? tree.columns[1] : null); + } + + // restore the scroll position to the start of the page + sendKey("HOME"); + + window.removeEventListener("keydown", keydownListener, false); + window.removeEventListener("keypress", keypressListener, false); + is(keydownFired, multiple ? 63 : 40, "keydown event wasn't fired properly"); + is(keypressFired, multiple ? 2 : 1, "keypress event wasn't fired properly"); +} + +function testtag_tree_UI_editing(tree, testid, rowInfo) +{ + testid += " editing UI "; + + // check editing UI + var ecolumn = tree.columns[0]; + var rowIndex = 2; + var inputField = tree.inputField; + + // temporary make the tree editable to test mouse double click + var wasEditable = tree.editable; + if (!wasEditable) + tree.editable = true; + + // if this is a container save its current open status + var row = rowInfo.rows[rowIndex]; + var wasOpen = null; + if (tree.view.isContainer(row)) + wasOpen = tree.view.isContainerOpen(row); + + // Test whether a keystroke can enter text entry, and another can exit. + if (tree.selType == "cell") + { + tree.stopEditing(false); + ok(!tree.editingColumn, "Should not be editing tree cell now"); + tree.view.selection.currentColumn = ecolumn; + tree.currentIndex = rowIndex; + + const isMac = (navigator.platform.indexOf("Mac") >= 0); + const StartEditingKey = isMac ? "RETURN" : "F2"; + sendKey(StartEditingKey); + is(tree.editingColumn, ecolumn, "Should be editing tree cell now"); + sendKey("ESCAPE"); + ok(!tree.editingColumn, "Should not be editing tree cell now"); + is(tree.currentIndex, rowIndex, "Current index should not have changed"); + is(tree.view.selection.currentColumn, ecolumn, "Current column should not have changed"); + } + + mouseDblClickOnCell(tree, rowIndex, ecolumn, testid + "edit on double click"); + is(tree.editingColumn, ecolumn, testid + "editing column"); + is(tree.editingRow, rowIndex, testid + "editing row"); + + // ensure that we don't expand an expandable container on edit + if (wasOpen != null) + is(tree.view.isContainerOpen(row), wasOpen, testid + "opened container node on edit"); + + // ensure to restore editable attribute + if (!wasEditable) + tree.editable = false; + + var ci = tree.currentIndex; + + // cursor navigation should not change the selection while editing + var testKey = function(key) { + synthesizeKeyExpectEvent(key, {}, tree, "!select", "key " + key + " with editing"); + is(tree.editingRow == rowIndex && tree.editingColumn == ecolumn && tree.currentIndex == ci, + true, testid + "key " + key + " while editing"); + } + + testKey("VK_DOWN"); + testKey("VK_UP"); + testKey("VK_PAGE_DOWN"); + testKey("VK_PAGE_UP"); + testKey("VK_HOME"); + testKey("VK_END"); + + // XXXndeakin figure out how to send characters to the textbox + // inputField.inputField.focus() + // synthesizeKeyExpectEvent(inputField.inputField, "b", null, ""); + // tree.stopEditing(true); + // is(tree.view.getCellText(0, ecolumn), "b", testid + "edit cell"); + + // Restore initial state. + tree.stopEditing(false); +} + +function testtag_tree_TreeSelection_UI_cell(tree, testid, rowInfo) +{ + testid += " selection UI cell "; + + var columns = tree.columns; + var firstcolumn = columns[0]; + var secondcolumn = columns[1]; + var lastcolumn = columns[columns.length - 1]; + var secondlastcolumn = columns[columns.length - 2]; + var selection = tree.view.selection; + + selection.clearSelection(); + selection.currentIndex = -1; + selection.currentColumn = firstcolumn; + is(selection.currentColumn, firstcolumn, testid + " first currentColumn"); + + // no selection yet so nothing should happen when the left and right cursor keys are pressed + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right no selection"); + testtag_tree_TreeSelection_State(tree, testid + "key right no selection", -1, [], null, firstcolumn); + + selection.currentColumn = secondcolumn; + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left no selection"); + testtag_tree_TreeSelection_State(tree, testid + "key left no selection", -1, [], null, secondcolumn); + + selection.select(2); + selection.currentIndex = 2; + if (0) { // XXXndeakin disable these tests for now + mouseOnCell(tree, 1, secondlastcolumn, "mouse on cell"); + testtag_tree_TreeSelection_State(tree, testid + "mouse on cell", 1, [1], null, secondlastcolumn); + } + + tree.focus(); + + // selection is set, so it should move when the left and right cursor keys are pressed + tree.treeBoxObject.scrollToRow(0); + selection.select(1); + selection.currentIndex = 1; + selection.currentColumn = secondcolumn; + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left in second column"); + testtag_tree_TreeSelection_State(tree, testid + "key left in second column", 1, [1], 0, firstcolumn); + + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left in first column"); + testtag_tree_TreeSelection_State(tree, testid + "key left in first column", 1, [1], 0, firstcolumn); + + selection.currentColumn = secondlastcolumn; + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right in second last column"); + testtag_tree_TreeSelection_State(tree, testid + "key right in second last column", 1, [1], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right in last column"); + testtag_tree_TreeSelection_State(tree, testid + "key right in last column", 1, [1], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up in second row"); + testtag_tree_TreeSelection_State(tree, testid + "key up in second row", 0, [0], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up in first row"); + testtag_tree_TreeSelection_State(tree, testid + "key up in first row", 0, [0], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down in first row"); + testtag_tree_TreeSelection_State(tree, testid + "key down in first row", 1, [1], 0, lastcolumn); + + var lastidx = tree.view.rowCount - 1; + tree.treeBoxObject.scrollToRow(lastidx - 3); + selection.select(lastidx); + selection.currentIndex = lastidx; + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down in last row"); + testtag_tree_TreeSelection_State(tree, testid + "key down in last row", lastidx, [lastidx], lastidx - 3, lastcolumn); + + synthesizeKeyExpectEvent("VK_HOME", {}, tree, "!select", "key home"); + testtag_tree_TreeSelection_State(tree, testid + "key home", 0, [0], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_END", {}, tree, "!select", "key end"); + testtag_tree_TreeSelection_State(tree, testid + "key end", lastidx, [lastidx], lastidx - 3, lastcolumn); + + for (var t = 0; t < 2; t++) { + var testidmod = (t == 0) ? "" : " rev"; + + // scroll to the end, subtract 3 because we want lastidx to appear + // at the end of view + tree.treeBoxObject.scrollToRow(lastidx - 3); + selection.select(lastidx); + selection.currentIndex = lastidx; + var expectedrow = tree.pageUpOrDownMovesSelection ? lastidx - 3 : lastidx; + synthesizeKeyExpectEvent("VK_PAGE_UP", {}, tree, "!select", "key page up"); + testtag_tree_TreeSelection_State(tree, testid + "key page up" + testidmod, + expectedrow, [expectedrow], + tree.pageUpOrDownMovesSelection ? lastidx - 3 : 0, lastcolumn); + + tree.treeBoxObject.scrollToRow(1); + selection.select(1); + selection.currentIndex = 1; + expectedrow = tree.pageUpOrDownMovesSelection ? 4 : 1; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", {}, tree, "!select", "key page down"); + testtag_tree_TreeSelection_State(tree, testid + "key page down" + testidmod, + expectedrow, [expectedrow], + tree.pageUpOrDownMovesSelection ? 1 : lastidx - 3, lastcolumn); + + tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection; + } + + // now check navigation when there is unselctable column + secondcolumn.element.setAttribute("selectable", "false"); + secondcolumn.invalidate(); + is(secondcolumn.selectable, false, testid + "set selectable attribute"); + + if (columns.length >= 3) { + selection.select(3); + selection.currentIndex = 3; + // check whether unselectable columns are skipped over + selection.currentColumn = firstcolumn; + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable column"); + testtag_tree_TreeSelection_State(tree, testid + "key right unselectable column", + 3, [3], null, secondcolumn.getNext()); + + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable column"); + testtag_tree_TreeSelection_State(tree, testid + "key left unselectable column", + 3, [3], null, firstcolumn); + } + + secondcolumn.element.removeAttribute("selectable"); + secondcolumn.invalidate(); + is(secondcolumn.selectable, true, testid + "clear selectable attribute"); + + // check to ensure that navigation isn't allowed if the first column is not selectable + selection.currentColumn = secondcolumn; + firstcolumn.element.setAttribute("selectable", "false"); + firstcolumn.invalidate(); + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable first column"); + testtag_tree_TreeSelection_State(tree, testid + "key left unselectable first column", + 3, [3], null, secondcolumn); + firstcolumn.element.removeAttribute("selectable"); + firstcolumn.invalidate(); + + // check to ensure that navigation isn't allowed if the last column is not selectable + selection.currentColumn = secondlastcolumn; + lastcolumn.element.setAttribute("selectable", "false"); + lastcolumn.invalidate(); + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable last column"); + testtag_tree_TreeSelection_State(tree, testid + "key right unselectable last column", + 3, [3], null, secondlastcolumn); + lastcolumn.element.removeAttribute("selectable"); + lastcolumn.invalidate(); + + // now check for cells with selectable false + if (!rowInfo.rows[4].cells[1].selectable && columns.length >= 3) { + // check whether unselectable cells are skipped over + selection.select(4); + selection.currentIndex = 4; + + selection.currentColumn = firstcolumn; + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key right unselectable cell", + 4, [4], null, secondcolumn.getNext()); + + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key left unselectable cell", + 4, [4], null, firstcolumn); + + tree.treeBoxObject.scrollToRow(1); + selection.select(3); + selection.currentIndex = 3; + selection.currentColumn = secondcolumn; + + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key down unselectable cell", + 5, [5], 2, secondcolumn); + + tree.treeBoxObject.scrollToRow(4); + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key up unselectable cell", + 3, [3], 3, secondcolumn); + } + + // restore the scroll position to the start of the page + sendKey("HOME"); +} + +function testtag_tree_TreeView(tree, testid, rowInfo) +{ + testid += " view "; + + var columns = tree.columns; + var view = tree.view; + + is(view instanceof Components.interfaces.nsITreeView, true, testid + "view is a TreeView"); + is(view.rowCount, rowInfo.rows.length, testid + "rowCount"); + + testtag_tree_TreeView_rows(tree, testid, rowInfo, 0); + + // note that this will only work for content trees currently + view.setCellText(0, columns[1], "Changed Value"); + is(view.getCellText(0, columns[1]), "Changed Value", "setCellText"); + + view.setCellValue(1, columns[0], "Another Changed Value"); + is(view.getCellValue(1, columns[0]), "Another Changed Value", "setCellText"); +} + +function testtag_tree_TreeView_rows(tree, testid, rowInfo, startRow) +{ + var r; + var columns = tree.columns; + var view = tree.view; + var length = rowInfo.rows.length; + + // methods to test along with the functions which determine the expected value + var checkRowMethods = + { + isContainer: function(row) { return row.container }, + isContainerOpen: function(row) { return false }, + isContainerEmpty: function(row) { return (row.children != null && row.children.rows.length == 0) }, + isSeparator: function(row) { return row.separator }, + getRowProperties: function(row) { return row.properties }, + getLevel: function(row) { return row.level }, + getParentIndex: function(row) { return row.parent }, + hasNextSibling: function(row) { return r < startRow + length - 1; } + }; + + var checkCellMethods = + { + getCellText: function(row, cell) { return cell.label }, + getCellValue: function(row, cell) { return cell.value }, + getCellProperties: function(row, cell) { return cell.properties }, + isEditable: function(row, cell) { return cell.editable }, + isSelectable: function(row, cell) { return cell.selectable }, + getImageSrc: function(row, cell) { return cell.image }, + getProgressMode: function(row, cell) { return cell.mode } + }; + + var failedMethods = { }; + var checkMethod, actual, expected; + var containerInfo = null; + var toggleOpenStateOK = true; + + for (r = startRow; r < length; r++) { + var row = rowInfo.rows[r]; + for (var c = 0; c < row.cells.length; c++) { + var cell = row.cells[c]; + + for (checkMethod in checkCellMethods) { + expected = checkCellMethods[checkMethod](row, cell); + actual = view[checkMethod](r, columns[c]); + if (actual !== expected) { + failedMethods[checkMethod] = true; + is(actual, expected, testid + "row " + r + " column " + c + " " + checkMethod + " is incorrect"); + } + } + } + + // compare row properties + for (checkMethod in checkRowMethods) { + expected = checkRowMethods[checkMethod](row, r); + if (checkMethod == "hasNextSibling") { + actual = view[checkMethod](r, r); + } + else { + actual = view[checkMethod](r); + } + if (actual !== expected) { + failedMethods[checkMethod] = true; + is(actual, expected, testid + "row " + r + " " + checkMethod + " is incorrect"); + } + } +/* + // open and recurse into containers + if (row.container) { + view.toggleOpenState(r); + if (!view.isContainerOpen(r)) { + toggleOpenStateOK = false; + is(view.isContainerOpen(r), true, testid + "row " + r + " toggleOpenState open"); + } + testtag_tree_TreeView_rows(tree, testid + "container " + r + " ", row.children, r + 1); + view.toggleOpenState(r); + if (view.isContainerOpen(r)) { + toggleOpenStateOK = false; + is(view.isContainerOpen(r), false, testid + "row " + r + " toggleOpenState close"); + } + } +*/ + } + + for (var failedMethod in failedMethods) { + if (failedMethod in checkRowMethods) + delete checkRowMethods[failedMethod]; + if (failedMethod in checkCellMethods) + delete checkCellMethods[failedMethod]; + } + + for (checkMethod in checkRowMethods) + is(checkMethod + " ok", checkMethod + " ok", testid + checkMethod); + for (checkMethod in checkCellMethods) + is(checkMethod + " ok", checkMethod + " ok", testid + checkMethod); + if (toggleOpenStateOK) + is("toggleOpenState ok", "toggleOpenState ok", testid + "toggleOpenState"); +} + +function testtag_tree_TreeView_rows_sort(tree, testid, rowInfo) +{ + // check if cycleHeader sorts the columns + var columnIndex = 0; + var view = tree.view; + var column = tree.columns[columnIndex]; + var columnElement = column.element; + var sortkey = columnElement.getAttribute("sort"); + if (sortkey) { + view.cycleHeader(column); + is(tree.getAttribute("sort"), sortkey, "cycleHeader sort"); + is(tree.getAttribute("sortDirection"), "ascending", "cycleHeader sortDirection ascending"); + is(columnElement.getAttribute("sortDirection"), "ascending", "cycleHeader column sortDirection"); + is(columnElement.getAttribute("sortActive"), "true", "cycleHeader column sortActive"); + view.cycleHeader(column); + is(tree.getAttribute("sortDirection"), "descending", "cycleHeader sortDirection descending"); + is(columnElement.getAttribute("sortDirection"), "descending", "cycleHeader column sortDirection descending"); + view.cycleHeader(column); + is(tree.getAttribute("sortDirection"), "", "cycleHeader sortDirection natural"); + is(columnElement.getAttribute("sortDirection"), "", "cycleHeader column sortDirection natural"); + // XXXndeakin content view isSorted needs to be tested + } + + // Check that clicking on column header sorts the column. + var columns = getSortedColumnArray(tree); + is(columnElement.getAttribute("sortDirection"), "", + "cycleHeader column sortDirection"); + + // Click once on column header and check sorting has cycled once. + mouseClickOnColumnHeader(columns, columnIndex, 0, 1); + is(columnElement.getAttribute("sortDirection"), "ascending", + "single click cycleHeader column sortDirection ascending"); + + // Now simulate a double click. + mouseClickOnColumnHeader(columns, columnIndex, 0, 2); + if (navigator.platform.indexOf("Win") == 0) { + // Windows cycles only once on double click. + is(columnElement.getAttribute("sortDirection"), "descending", + "double click cycleHeader column sortDirection descending"); + // 1 single clicks should restore natural sorting. + mouseClickOnColumnHeader(columns, columnIndex, 0, 1); + } + + // Check we have gone back to natural sorting. + is(columnElement.getAttribute("sortDirection"), "", + "cycleHeader column sortDirection"); + + columnElement.setAttribute("sorthints", "twostate"); + view.cycleHeader(column); + is(tree.getAttribute("sortDirection"), "ascending", "cycleHeader sortDirection ascending twostate"); + view.cycleHeader(column); + is(tree.getAttribute("sortDirection"), "descending", "cycleHeader sortDirection ascending twostate"); + view.cycleHeader(column); + is(tree.getAttribute("sortDirection"), "ascending", "cycleHeader sortDirection ascending twostate again"); + columnElement.removeAttribute("sorthints"); + view.cycleHeader(column); + view.cycleHeader(column); + + is(columnElement.getAttribute("sortDirection"), "", + "cycleHeader column sortDirection reset"); +} + +// checks if the current and selected rows are correct +// current is the index of the current row +// selected is an array of the indicies of the selected rows +// column is the selected column +// viewidx is the row that should be visible at the top of the tree +function testtag_tree_TreeSelection_State(tree, testid, current, selected, viewidx, column) +{ + var selection = tree.view.selection; + + if (!column) + column = (tree.selType == "cell") ? tree.columns[0] : null; + + is(selection.count, selected.length, testid + " count"); + is(tree.currentIndex, current, testid + " currentIndex"); + is(selection.currentIndex, current, testid + " TreeSelection currentIndex"); + is(selection.currentColumn, column, testid + " currentColumn"); + if (viewidx !== null && viewidx !== undefined) + is(tree.treeBoxObject.getFirstVisibleRow(), viewidx, testid + " first visible row"); + + var actualSelected = []; + var count = tree.view.rowCount; + for (var s = 0; s < count; s++) { + if (selection.isSelected(s)) + actualSelected.push(s); + } + + is(compareArrays(selected, actualSelected), true, testid + " selection [" + selected + "]"); + + actualSelected = []; + var rangecount = selection.getRangeCount(); + for (var r = 0; r < rangecount; r++) { + var start = {}, end = {}; + selection.getRangeAt(r, start, end); + for (var rs = start.value; rs <= end.value; rs++) + actualSelected.push(rs); + } + + is(compareArrays(selected, actualSelected), true, testid + " range selection [" + selected + "]"); +} + +function testtag_tree_column_reorder() +{ + // Make sure the tree is scrolled into the view, otherwise the test will + // fail + var testframe = window.parent.document.getElementById("testframe"); + if (testframe) { + testframe.scrollIntoView(); + } + + var tree = document.getElementById("tree-column-reorder"); + var numColumns = tree.columns.count; + + var reference = []; + for (let i = 0; i < numColumns; i++) { + reference.push("col_" + i); + } + + // Drag the first column to each position + for (let i = 0; i < numColumns - 1; i++) { + synthesizeColumnDrag(tree, i, i + 1, true); + arrayMove(reference, i, i + 1, true); + checkColumns(tree, reference, "drag first column right"); + } + + // And back + for (let i = numColumns - 1; i >= 1; i--) { + synthesizeColumnDrag(tree, i, i - 1, false); + arrayMove(reference, i, i - 1, false); + checkColumns(tree, reference, "drag last column left"); + } + + // Drag each column one column left + for (let i = 1; i < numColumns; i++) { + synthesizeColumnDrag(tree, i, i - 1, false); + arrayMove(reference, i, i - 1, false); + checkColumns(tree, reference, "drag each column left"); + } + + // And back + for (let i = numColumns - 2; i >= 0; i--) { + synthesizeColumnDrag(tree, i, i + 1, true); + arrayMove(reference, i, i + 1, true); + checkColumns(tree, reference, "drag each column right"); + } + + // Drag each column 5 to the right + for (let i = 0; i < numColumns - 5; i++) { + synthesizeColumnDrag(tree, i, i + 5, true); + arrayMove(reference, i, i + 5, true); + checkColumns(tree, reference, "drag each column 5 to the right"); + } + + // And to the left + for (let i = numColumns - 6; i >= 5; i--) { + synthesizeColumnDrag(tree, i, i - 5, false); + arrayMove(reference, i, i - 5, false); + checkColumns(tree, reference, "drag each column 5 to the left"); + } + + // Test that moving a column after itself does not move anything + synthesizeColumnDrag(tree, 0, 0, true); + checkColumns(tree, reference, "drag to itself"); + is(document.treecolDragging, null, "drag to itself completed"); + + // XXX roc should this be here??? + SimpleTest.finish(); +} + +function testtag_tree_wheel(aTree) +{ + const deltaModes = [ + WheelEvent.DOM_DELTA_PIXEL, // 0 + WheelEvent.DOM_DELTA_LINE, // 1 + WheelEvent.DOM_DELTA_PAGE // 2 + ]; + function helper(aStart, aDelta, aIntDelta, aDeltaMode) + { + aTree.treeBoxObject.scrollToRow(aStart); + var expected; + if (!aIntDelta) { + expected = aStart; + } + else if (aDeltaMode != WheelEvent.DOM_DELTA_PAGE) { + expected = aStart + aIntDelta; + } + else if (aIntDelta > 0) { + expected = aStart + aTree.treeBoxObject.getPageLength(); + } + else { + expected = aStart - aTree.treeBoxObject.getPageLength(); + } + + if (expected < 0) { + expected = 0; + } + if (expected > aTree.view.rowCount - aTree.treeBoxObject.getPageLength()) { + expected = aTree.view.rowCount - aTree.treeBoxObject.getPageLength(); + } + synthesizeWheel(aTree.body, 1, 1, + { deltaMode: aDeltaMode, deltaY: aDelta, + lineOrPageDeltaY: aIntDelta }); + is(aTree.treeBoxObject.getFirstVisibleRow(), expected, + "testtag_tree_wheel: vertical, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + aIntDelta + + " aDeltaMode " + aDeltaMode); + + aTree.treeBoxObject.scrollToRow(aStart); + // Check that horizontal scrolling has no effect + synthesizeWheel(aTree.body, 1, 1, + { deltaMode: aDeltaMode, deltaX: aDelta, + lineOrPageDeltaX: aIntDelta }); + is(aTree.treeBoxObject.getFirstVisibleRow(), aStart, + "testtag_tree_wheel: horizontal, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + aIntDelta + + " aDeltaMode " + aDeltaMode); + } + + var defaultPrevented = 0; + + function wheelListener(event) { + defaultPrevented++; + } + window.addEventListener("wheel", wheelListener, false); + + deltaModes.forEach(function(aDeltaMode) { + var delta = (aDeltaMode == WheelEvent.DOM_DELTA_PIXEL) ? 5.0 : 0.3; + helper(2, -delta, 0, aDeltaMode); + helper(2, -delta, -1, aDeltaMode); + helper(2, delta, 0, aDeltaMode); + helper(2, delta, 1, aDeltaMode); + helper(2, -2 * delta, 0, aDeltaMode); + helper(2, -2 * delta, -1, aDeltaMode); + helper(2, 2 * delta, 0, aDeltaMode); + helper(2, 2 * delta, 1, aDeltaMode); + }); + + window.removeEventListener("wheel", wheelListener, false); + is(defaultPrevented, 48, "wheel event default prevented"); +} + +function synthesizeColumnDrag(aTree, aMouseDownColumnNumber, aMouseUpColumnNumber, aAfter) +{ + var columns = getSortedColumnArray(aTree); + + var down = columns[aMouseDownColumnNumber].element; + var up = columns[aMouseUpColumnNumber].element; + + // Target the initial mousedown in the middle of the column header so we + // avoid the extra hit test space given to the splitter + var columnWidth = down.boxObject.width; + var splitterHitWidth = columnWidth / 2; + synthesizeMouse(down, splitterHitWidth, 3, { type: "mousedown"}); + + var offsetX = 0; + if (aAfter) { + offsetX = columnWidth; + } + + if (aMouseUpColumnNumber > aMouseDownColumnNumber) { + for (let i = aMouseDownColumnNumber; i <= aMouseUpColumnNumber; i++) { + let move = columns[i].element; + synthesizeMouse(move, offsetX, 3, { type: "mousemove"}); + } + } + else { + for (let i = aMouseDownColumnNumber; i >= aMouseUpColumnNumber; i--) { + let move = columns[i].element; + synthesizeMouse(move, offsetX, 3, { type: "mousemove"}); + } + } + + synthesizeMouse(up, offsetX, 3, { type: "mouseup"}); +} + +function arrayMove(aArray, aFrom, aTo, aAfter) +{ + var o = aArray.splice(aFrom, 1)[0]; + if (aTo > aFrom) { + aTo--; + } + + if (aAfter) { + aTo++; + } + + aArray.splice(aTo, 0, o); +} + +function getSortedColumnArray(aTree) +{ + var columns = aTree.columns; + var array = []; + for (let i = 0; i < columns.length; i++) { + array.push(columns.getColumnAt(i)); + } + + array.sort(function(a, b) { + var o1 = parseInt(a.element.getAttribute("ordinal")); + var o2 = parseInt(b.element.getAttribute("ordinal")); + return o1 - o2; + }); + return array; +} + +function checkColumns(aTree, aReference, aMessage) +{ + var columns = getSortedColumnArray(aTree); + var ids = []; + columns.forEach(function(e) { + ids.push(e.element.id); + }); + is(compareArrays(ids, aReference), true, aMessage); +} + +function mouseOnCell(tree, row, column, testname) +{ + var rect = tree.boxObject.getCoordsForCellItem(row, column, "text"); + + synthesizeMouseExpectEvent(tree.body, rect.x, rect.y, {}, tree, "select", testname); +} + +function mouseClickOnColumnHeader(aColumns, aColumnIndex, aButton, aClickCount) +{ + var columnHeader = aColumns[aColumnIndex].element; + var columnHeaderRect = columnHeader.getBoundingClientRect(); + var columnWidth = columnHeaderRect.right - columnHeaderRect.left; + // For multiple click we send separate click events, with increasing + // clickCount. This simulates the common behavior of multiple clicks. + for (let i = 1; i <= aClickCount; i++) { + // Target the middle of the column header. + synthesizeMouse(columnHeader, columnWidth / 2, 3, + { button: aButton, + clickCount: i }, null); + } +} + +function mouseDblClickOnCell(tree, row, column, testname) +{ + // select the row we will edit + var selection = tree.view.selection; + selection.select(row); + tree.treeBoxObject.ensureRowIsVisible(row); + + // get cell coordinates + var rect = tree.treeBoxObject.getCoordsForCellItem(row, column, "text"); + + synthesizeMouse(tree.body, rect.x, rect.y, { clickCount: 2 }, null); +} + +function compareArrays(arr1, arr2) +{ + if (arr1.length != arr2.length) + return false; + + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) + return false; + } + + return true; +} + +function convertProperties(arr) +{ + var results = []; + var count = arr.Count(); + for (let i = 0; i < count; i++) + results.push(arr.GetElementAt(i).QueryInterface(Components.interfaces.nsIAtom).toString()); + + results.sort(); + return results.join(" "); +} + +function convertDOMtoTreeRowInfo(treechildren, level, rowidx) +{ + var obj = { rows: [] }; + + var parentidx = rowidx.value; + + treechildren = treechildren.childNodes; + for (var r = 0; r < treechildren.length; r++) { + rowidx.value++; + + var treeitem = treechildren[r]; + if (treeitem.hasChildNodes()) { + var treerow = treeitem.firstChild; + var cellInfo = []; + for (var c = 0; c < treerow.childNodes.length; c++) { + var cell = treerow.childNodes[c]; + cellInfo.push({ label: "" + cell.getAttribute("label"), + value: cell.getAttribute("value"), + properties: cell.getAttribute("properties"), + editable: cell.getAttribute("editable") != "false", + selectable: cell.getAttribute("selectable") != "false", + image: cell.getAttribute("src"), + mode: cell.hasAttribute("mode") ? parseInt(cell.getAttribute("mode")) : 3 }); + } + + var descendants = treeitem.lastChild; + var children = (treerow == descendants) ? null : + convertDOMtoTreeRowInfo(descendants, level + 1, rowidx); + obj.rows.push({ cells: cellInfo, + properties: treerow.getAttribute("properties"), + container: treeitem.getAttribute("container") == "true", + separator: treeitem.localName == "treeseparator", + children: children, + level: level, + parent: parentidx }); + } + } + + return obj; +} diff --git a/toolkit/content/tests/widgets/video.ogg b/toolkit/content/tests/widgets/video.ogg Binary files differnew file mode 100644 index 000000000..ac7ece351 --- /dev/null +++ b/toolkit/content/tests/widgets/video.ogg diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1-ref.html b/toolkit/content/tests/widgets/videocontrols_direction-1-ref.html new file mode 100644 index 000000000..1f7e76a7d --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<video controls preload="none" id="av" source="audio.wav"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1a.html b/toolkit/content/tests/widgets/videocontrols_direction-1a.html new file mode 100644 index 000000000..a4d354629 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1a.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html dir="rtl"> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body> +<video controls preload="none" id="av" source="audio.wav"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1b.html b/toolkit/content/tests/widgets/videocontrols_direction-1b.html new file mode 100644 index 000000000..a14b11d5f --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1b.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html style="direction: rtl"> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body> +<video controls preload="none" id="av" source="audio.wav"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1c.html b/toolkit/content/tests/widgets/videocontrols_direction-1c.html new file mode 100644 index 000000000..0885ebd89 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1c.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="direction: rtl"> +<video controls preload="none" id="av" source="audio.wav"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1d.html b/toolkit/content/tests/widgets/videocontrols_direction-1d.html new file mode 100644 index 000000000..a39accec7 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1d.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<video controls preload="none" id="av" source="audio.wav" dir="rtl"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1e.html b/toolkit/content/tests/widgets/videocontrols_direction-1e.html new file mode 100644 index 000000000..25e7c2c1f --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1e.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<video controls preload="none" id="av" source="audio.wav" style="direction: rtl;"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2-ref.html b/toolkit/content/tests/widgets/videocontrols_direction-2-ref.html new file mode 100644 index 000000000..630177883 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<audio controls preload="none" id="av" source="audio.wav"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2a.html b/toolkit/content/tests/widgets/videocontrols_direction-2a.html new file mode 100644 index 000000000..2e40cdc1a --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2a.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html dir="rtl"> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body> +<audio controls preload="none" id="av" source="audio.wav"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2b.html b/toolkit/content/tests/widgets/videocontrols_direction-2b.html new file mode 100644 index 000000000..2e4dadb6f --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2b.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html style="direction: rtl"> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body> +<audio controls preload="none" id="av" source="audio.wav"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2c.html b/toolkit/content/tests/widgets/videocontrols_direction-2c.html new file mode 100644 index 000000000..a43b03e8f --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2c.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="direction: rtl"> +<audio controls preload="none" id="av" source="audio.wav"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2d.html b/toolkit/content/tests/widgets/videocontrols_direction-2d.html new file mode 100644 index 000000000..52d56f1cc --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2d.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<audio controls preload="none" id="av" source="audio.wav" dir="rtl"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2e.html b/toolkit/content/tests/widgets/videocontrols_direction-2e.html new file mode 100644 index 000000000..58bc30e2b --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2e.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<audio controls preload="none" id="av" source="audio.wav" style="direction: rtl;"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction_test.js b/toolkit/content/tests/widgets/videocontrols_direction_test.js new file mode 100644 index 000000000..8ad76c064 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction_test.js @@ -0,0 +1,90 @@ +var RemoteCanvas = function(url, id) { + this.url = url; + this.id = id; + this.snapshot = null; +}; + +RemoteCanvas.CANVAS_WIDTH = 200; +RemoteCanvas.CANVAS_HEIGHT = 200; + +RemoteCanvas.prototype.compare = function(otherCanvas, expected) { + return compareSnapshots(this.snapshot, otherCanvas.snapshot, expected)[0]; +} + +RemoteCanvas.prototype.load = function(callback) { + var iframe = document.createElement("iframe"); + iframe.id = this.id + "-iframe"; + iframe.width = RemoteCanvas.CANVAS_WIDTH + "px"; + iframe.height = RemoteCanvas.CANVAS_HEIGHT + "px"; + iframe.src = this.url; + var me = this; + iframe.addEventListener("load", function() { + info("iframe loaded"); + var m = iframe.contentDocument.getElementById("av"); + m.addEventListener("suspend", function(aEvent) { + m.removeEventListener("suspend", arguments.callee, false); + setTimeout(function() { + me.remotePageLoaded(callback); + }, 0); + }, false); + m.src = m.getAttribute("source"); + }, false); + window.document.body.appendChild(iframe); +}; + +RemoteCanvas.prototype.remotePageLoaded = function(callback) { + var ldrFrame = document.getElementById(this.id + "-iframe"); + this.snapshot = snapshotWindow(ldrFrame.contentWindow); + this.snapshot.id = this.id + "-canvas"; + window.document.body.appendChild(this.snapshot); + callback(this); +}; + +RemoteCanvas.prototype.cleanup = function() { + var iframe = document.getElementById(this.id + "-iframe"); + iframe.parentNode.removeChild(iframe); + var canvas = document.getElementById(this.id + "-canvas"); + canvas.parentNode.removeChild(canvas); +}; + +function runTest(index) { + var canvases = []; + function testCallback(canvas) { + canvases.push(canvas); + + if (canvases.length == 2) { // when both canvases are loaded + var expectedEqual = currentTest.op == "=="; + var result = canvases[0].compare(canvases[1], expectedEqual); + ok(result, "Rendering of reftest " + currentTest.test + " should " + + (expectedEqual ? "not " : "") + "be different to the reference"); + + if (result) { + canvases[0].cleanup(); + canvases[1].cleanup(); + } + else { + info("Snapshot of canvas 1: " + canvases[0].snapshot.toDataURL()); + info("Snapshot of canvas 2: " + canvases[1].snapshot.toDataURL()); + } + + if (index < tests.length - 1) + runTest(index + 1); + else + SimpleTest.finish(); + } + } + + var currentTest = tests[index]; + var testCanvas = new RemoteCanvas(currentTest.test, "test-" + index); + testCanvas.load(testCallback); + + var refCanvas = new RemoteCanvas(currentTest.ref, "ref-" + index); + refCanvas.load(testCallback); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestCompleteLog(); + +window.addEventListener("load", function() { + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, function() { runTest(0); }); +}, true); diff --git a/toolkit/content/tests/widgets/videomask.css b/toolkit/content/tests/widgets/videomask.css new file mode 100644 index 000000000..066d44138 --- /dev/null +++ b/toolkit/content/tests/widgets/videomask.css @@ -0,0 +1,23 @@ +html, body { + margin: 0; + padding: 0; +} + +audio, video { + width: 140px; + height: 100px; + background-color: black; +} + +/** + * Create a mask for the video direction tests which covers up the throbber. + */ +#mask { + position: absolute; + z-index: 3; + width: 140px; + height: 72px; + background-color: green; + top: 0; + right: 0; +} diff --git a/toolkit/content/tests/widgets/window_menubar.xul b/toolkit/content/tests/widgets/window_menubar.xul new file mode 100644 index 000000000..b7669e0b3 --- /dev/null +++ b/toolkit/content/tests/widgets/window_menubar.xul @@ -0,0 +1,820 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<!-- the condition in the focus event handler is because pressing Tab + unfocuses and refocuses the window on Windows --> + +<window title="Popup Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="popup_shared.js"></script> + +<!-- + Need to investigate these tests a bit more. Some of the accessibility events + are firing multiple times or in different orders in different circumstances. + Note that this was also the case before bug 279703. + --> + +<hbox style="margin-left: 275px; margin-top: 275px;"> +<menubar id="menubar"> + <menu id="filemenu" label="File" accesskey="F"> + <menupopup id="filepopup"> + <menuitem id="item1" label="Open" accesskey="O"/> + <menuitem id="item2" label="Save" accesskey="S"/> + <menuitem id="item3" label="Close" accesskey="C"/> + </menupopup> + </menu> + <menu id="secretmenu" label="Secret Menu" accesskey="S" disabled="true"> + <menupopup> + <menuitem label="Secret Command" accesskey="S"/> + </menupopup> + </menu> + <menu id="editmenu" label="Edit" accesskey="E"> + <menupopup id="editpopup"> + <menuitem id="cut" label="Cut" accesskey="t" disabled="true"/> + <menuitem id="copy" label="Copy" accesskey="C"/> + <menuitem id="paste" label="Paste" accesskey="P"/> + </menupopup> + </menu> + <menu id="viewmenu" label="View" accesskey="V"> + <menupopup id="viewpopup"> + <menu id="toolbar" label="Toolbar" accesskey="T"> + <menupopup id="toolbarpopup"> + <menuitem id="navigation" label="Navigation" accesskey="N" disabled="true"/> + <menuitem label="Bookmarks" accesskey="B" disabled="true"/> + </menupopup> + </menu> + <menuitem label="Status Bar" accesskey="S"/> + <menu label="Sidebar" accesskey="d"> + <menupopup> + <menuitem label="Bookmarks" accesskey="B"/> + <menuitem label="History" accesskey="H"/> + </menupopup> + </menu> + </menupopup> + </menu> + <menu id="helpmenu" label="Help" accesskey="H"> + <menupopup id="helppopup" > + <label value="Unselectable"/> + <menuitem id="contents" label="Contents" accesskey="C"/> + <menuitem label="More Info" accesskey="I"/> + <menuitem id="amenu" label="A Menu" accesskey="M"/> + <menuitem label="Another Menu"/> + <menuitem id="one" label="One"/> + <menu id="only" label="Only Menu"> + <menupopup> + <menuitem label="Test Submenu"/> + </menupopup> + </menu> + <menuitem label="Second Menu"/> + <menuitem id="other" disabled="true" label="Other Menu"/> + <menuitem id="third" label="Third Menu"/> + <menuitem label="One Other Menu"/> + <label value="Unselectable"/> + <menuitem id="about" label="About" accesskey="A"/> + </menupopup> + </menu> +</menubar> +</hbox> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +window.opener.SimpleTest.waitForFocus(function () { + gFilePopup = document.getElementById("filepopup"); + var filemenu = document.getElementById("filemenu"); + filemenu.focus(); + is(filemenu.openedWithKey, false, "initial openedWithKey"); + startPopupTests(popupTests); +}, window); + +// On Linux, the first menu opens when F10 is pressed, but on other platforms +// the menubar is focused but no menu is opened. This means that different events +// fire. +function pressF10Events() +{ + return navigator.platform.indexOf("Linux") >= 0 ? + [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu", "popupshowing filepopup", "DOMMenuItemActive item1", "popupshown filepopup"] : + [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ]; +} + +function closeAfterF10Events(extraInactive) +{ + if (navigator.platform.indexOf("Linux") >= 0) { + var events = [ "popuphiding filepopup", "popuphidden filepopup", "DOMMenuItemInactive item1", + "DOMMenuInactive filepopup", "DOMMenuBarInactive menubar", + "DOMMenuItemInactive filemenu" ]; + if (extraInactive) + events.push("DOMMenuItemInactive filemenu"); + return events; + } + + return [ "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu" ]; +} + +var popupTests = [ +{ + testname: "press on menu", + events: [ "popupshowing filepopup", "DOMMenuBarActive menubar", + "DOMMenuItemActive filemenu", "popupshown filepopup" ], + test: function() { synthesizeMouse(document.getElementById("filemenu"), 8, 8, { }); }, + result: function (testname) { + checkActive(gFilePopup, "", testname); + checkOpen("filemenu", testname); + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + // check that pressing cursor down while there is no selection + // highlights the first item + testname: "cursor down no selection", + events: [ "DOMMenuItemActive item1" ], + test: function() { sendKey("DOWN"); }, + result: function(testname) { checkActive(gFilePopup, "item1", testname); } +}, +{ + // check that pressing cursor up wraps and highlights the last item + testname: "cursor up wrap", + events: [ "DOMMenuItemInactive item1", "DOMMenuItemActive item3" ], + test: function() { sendKey("UP"); }, + result: function(testname) { checkActive(gFilePopup, "item3", testname); } +}, +{ + // check that pressing cursor down wraps and highlights the first item + testname: "cursor down wrap", + events: [ "DOMMenuItemInactive item3", "DOMMenuItemActive item1" ], + test: function() { sendKey("DOWN"); }, + result: function(testname) { checkActive(gFilePopup, "item1", testname); } +}, +{ + // check that pressing cursor down highlights the second item + testname: "cursor down", + events: [ "DOMMenuItemInactive item1", "DOMMenuItemActive item2" ], + test: function() { sendKey("DOWN"); }, + result: function(testname) { checkActive(gFilePopup, "item2", testname); } +}, +{ + // check that pressing cursor up highlights the second item + testname: "cursor up", + events: [ "DOMMenuItemInactive item2", "DOMMenuItemActive item1" ], + test: function() { sendKey("UP"); }, + result: function(testname) { checkActive(gFilePopup, "item1", testname); } +}, + +{ + // cursor right should skip the disabled menu and move to the edit menu + testname: "cursor right skip disabled", + events: function() { + var elist = [ + // the file menu gets deactivated, the file menu gets hidden, then + // the edit menu is activated + "DOMMenuItemInactive filemenu", "DOMMenuItemActive editmenu", + "popuphiding filepopup", "popuphidden filepopup", + // the popupshowing event gets fired when showing the edit menu. + // The item from the file menu doesn't get deactivated until the + // next item needs to be selected + "popupshowing editpopup", "DOMMenuItemInactive item1", + // not sure why the menu inactivated event is firing so late + "DOMMenuInactive filepopup" + ]; + // finally, the first item is activated and popupshown is fired. + // On Windows, don't skip disabled items. + if (navigator.platform.indexOf("Win") == 0) + elist.push("DOMMenuItemActive cut"); + else + elist.push("DOMMenuItemActive copy"); + elist.push("popupshown editpopup"); + return elist; + }, + test: function() { sendKey("RIGHT"); }, + result: function(testname) { + var expected = (navigator.platform.indexOf("Win") == 0) ? "cut" : "copy"; + checkActive(document.getElementById("editpopup"), expected, testname); + checkClosed("filemenu", testname); + checkOpen("editmenu", testname); + is(document.getElementById("editmenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + // on Windows, a disabled item is selected, so pressing RETURN should close + // the menu but not fire a command event + testname: "enter on disabled", + events: function() { + if (navigator.platform.indexOf("Win") == 0) + return [ "popuphiding editpopup", "popuphidden editpopup", + "DOMMenuItemInactive cut", "DOMMenuInactive editpopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive editmenu", "DOMMenuItemInactive editmenu" ]; + else + return [ "DOMMenuItemInactive copy", "DOMMenuInactive editpopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive editmenu", "DOMMenuItemInactive editmenu", + "command copy", "popuphiding editpopup", "popuphidden editpopup", + "DOMMenuItemInactive copy" ]; + }, + test: function() { sendKey("RETURN"); }, + result: function(testname) { + checkClosed("editmenu", testname); + is(document.getElementById("editmenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + // pressing Alt + a key should open the corresponding menu + testname: "open with accelerator", + events: function() { + return [ "DOMMenuBarActive menubar", + "popupshowing viewpopup", "DOMMenuItemActive viewmenu", + "DOMMenuItemActive toolbar", "popupshown viewpopup" ]; + }, + test: function() { synthesizeKey("V", { altKey: true }); }, + result: function(testname) { + checkOpen("viewmenu", testname); + is(document.getElementById("viewmenu").openedWithKey, true, testname + " openedWithKey"); + } +}, +{ + // open the submenu with the cursor right key + testname: "open submenu with cursor right", + events: function() { + // on Windows, the disabled 'navigation' item can stll be highlihted + if (navigator.platform.indexOf("Win") == 0) + return [ "popupshowing toolbarpopup", "DOMMenuItemActive navigation", + "popupshown toolbarpopup" ]; + else + return [ "popupshowing toolbarpopup", "popupshown toolbarpopup" ]; + }, + test: function() { sendKey("RIGHT"); }, + result: function(testname) { + checkOpen("viewmenu", testname); + checkOpen("toolbar", testname); + } +}, +{ + // close the submenu with the cursor left key + testname: "close submenu with cursor left", + events: function() { + if (navigator.platform.indexOf("Win") == 0) + return [ "popuphiding toolbarpopup", "popuphidden toolbarpopup", + "DOMMenuItemInactive navigation", "DOMMenuInactive toolbarpopup", + "DOMMenuItemActive toolbar" ]; + else + return [ "popuphiding toolbarpopup", "popuphidden toolbarpopup", + "DOMMenuInactive toolbarpopup", + "DOMMenuItemActive toolbar" ]; + }, + test: function() { sendKey("LEFT"); }, + result: function(testname) { + checkOpen("viewmenu", testname); + checkClosed("toolbar", testname); + } +}, +{ + // open the submenu with the enter key + testname: "open submenu with enter", + events: function() { + // on Windows, the disabled 'navigation' item can stll be highlighted + if (navigator.platform.indexOf("Win") == 0) + return [ "popupshowing toolbarpopup", "DOMMenuItemActive navigation", + "popupshown toolbarpopup" ]; + else + return [ "popupshowing toolbarpopup", "popupshown toolbarpopup" ]; + }, + test: function() { sendKey("RETURN"); }, + result: function(testname) { + checkOpen("viewmenu", testname); + checkOpen("toolbar", testname); + }, +}, +{ + // close the submenu with the escape key + testname: "close submenu with escape", + events: function() { + if (navigator.platform.indexOf("Win") == 0) + return [ "popuphiding toolbarpopup", "popuphidden toolbarpopup", + "DOMMenuItemInactive navigation", "DOMMenuInactive toolbarpopup", + "DOMMenuItemActive toolbar" ]; + else + return [ "popuphiding toolbarpopup", "popuphidden toolbarpopup", + "DOMMenuInactive toolbarpopup", + "DOMMenuItemActive toolbar" ]; + }, + test: function() { sendKey("ESCAPE"); }, + result: function(testname) { + checkOpen("viewmenu", testname); + checkClosed("toolbar", testname); + }, +}, +{ + // open the submenu with the enter key again + testname: "open submenu with enter again", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + // on Windows, the disabled 'navigation' item can stll be highlighted + if (navigator.platform.indexOf("Win") == 0) + return [ "popupshowing toolbarpopup", "DOMMenuItemActive navigation", + "popupshown toolbarpopup" ]; + else + return [ "popupshowing toolbarpopup", "popupshown toolbarpopup" ]; + }, + test: function() { sendKey("RETURN"); }, + result: function(testname) { + checkOpen("viewmenu", testname); + checkOpen("toolbar", testname); + }, +}, +{ + // while a submenu is open, switch to the next toplevel menu with the cursor right key + testname: "while a submenu is open, switch to the next menu with the cursor right", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuItemInactive viewmenu", "DOMMenuItemActive helpmenu", + "popuphiding toolbarpopup", "popuphidden toolbarpopup", + "popuphiding viewpopup", "popuphidden viewpopup", + "popupshowing helppopup", "DOMMenuItemInactive navigation", + "DOMMenuInactive toolbarpopup", "DOMMenuItemInactive toolbar", + "DOMMenuInactive viewpopup", "DOMMenuItemActive contents", + "popupshown helppopup" ], + test: function() { sendKey("RIGHT"); }, + result: function(testname) { + checkOpen("helpmenu", testname); + checkClosed("toolbar", testname); + checkClosed("viewmenu", testname); + } +}, +{ + // close the main menu with the escape key + testname: "close menubar menu with escape", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "popuphiding helppopup", "popuphidden helppopup", + "DOMMenuItemInactive contents", "DOMMenuInactive helppopup", + "DOMMenuBarInactive menubar", "DOMMenuItemInactive helpmenu" ], + test: function() { sendKey("ESCAPE"); }, + result: function(testname) { checkClosed("viewmenu", testname); }, +}, +{ + // close the main menu with the escape key + testname: "close menubar menu with escape", + condition: function() { return (navigator.platform.indexOf("Win") != 0) }, + events: [ "popuphiding viewpopup", "popuphidden viewpopup", + "DOMMenuItemInactive toolbar", "DOMMenuInactive viewpopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive viewmenu" ], + test: function() { sendKey("ESCAPE"); }, + result: function(testname) { checkClosed("viewmenu", testname); }, +}, +{ + // Pressing Alt should highlight the first menu but not open it, + // but it should be ignored if the alt keydown event is consumed. + testname: "alt shouldn't activate menubar if keydown event is consumed", + test: function() { + document.addEventListener("keydown", function (aEvent) { + document.removeEventListener("keydown", arguments.callee, true); + aEvent.preventDefault(); + }, true); + sendKey("ALT"); + }, + result: function(testname) { + ok(!document.getElementById("filemenu").openedWithKey, testname); + checkClosed("filemenu", testname); + }, +}, +{ + // Pressing Alt should highlight the first menu but not open it, + // but it should be ignored if the alt keyup event is consumed. + testname: "alt shouldn't activate menubar if keyup event is consumed", + test: function() { + document.addEventListener("keyup", function (aEvent) { + document.removeEventListener("keyup", arguments.callee, true); + aEvent.preventDefault(); + }, true); + sendKey("ALT"); + }, + result: function(testname) { + ok(!document.getElementById("filemenu").openedWithKey, testname); + checkClosed("filemenu", testname); + }, +}, +{ + // Pressing Alt should highlight the first menu but not open it. + testname: "alt to activate menubar", + events: [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ], + test: function() { sendKey("ALT"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, true, testname + " openedWithKey"); + checkClosed("filemenu", testname); + }, +}, +{ + // pressing cursor left should select the previous menu but not open it + testname: "cursor left on active menubar", + events: [ "DOMMenuItemInactive filemenu", "DOMMenuItemActive helpmenu" ], + test: function() { sendKey("LEFT"); }, + result: function(testname) { checkClosed("helpmenu", testname); }, +}, +{ + // pressing cursor right should select the previous menu but not open it + testname: "cursor right on active menubar", + events: [ "DOMMenuItemInactive helpmenu", "DOMMenuItemActive filemenu" ], + test: function() { sendKey("RIGHT"); }, + result: function(testname) { checkClosed("filemenu", testname); }, +}, +{ + // pressing a character should act as an accelerator and open the menu + testname: "accelerator on active menubar", + events: [ "popupshowing helppopup", + "DOMMenuItemInactive filemenu", "DOMMenuItemActive helpmenu", + "DOMMenuItemActive contents", "popupshown helppopup" ], + test: function() { sendChar("h"); }, + result: function(testname) { + checkOpen("helpmenu", testname); + is(document.getElementById("helpmenu").openedWithKey, true, testname + " openedWithKey"); + }, +}, +{ + // check that pressing cursor up skips non menuitems + testname: "cursor up wrap", + events: [ "DOMMenuItemInactive contents", "DOMMenuItemActive about" ], + test: function() { sendKey("UP"); }, + result: function(testname) { } +}, +{ + // check that pressing cursor down skips non menuitems + testname: "cursor down wrap", + events: [ "DOMMenuItemInactive about", "DOMMenuItemActive contents" ], + test: function() { sendKey("DOWN"); }, + result: function(testname) { } +}, +{ + // check that pressing a menuitem's accelerator selects it + testname: "menuitem accelerator", + events: [ "DOMMenuItemInactive contents", "DOMMenuItemActive amenu", + "DOMMenuItemInactive amenu", "DOMMenuInactive helppopup", + "DOMMenuBarInactive menubar", "DOMMenuItemInactive helpmenu", + "DOMMenuItemInactive helpmenu", + "command amenu", "popuphiding helppopup", "popuphidden helppopup", + "DOMMenuItemInactive amenu", + ], + test: function() { sendChar("m"); }, + result: function(testname) { checkClosed("helpmenu", testname); } +}, +{ + // pressing F10 should highlight the first menu. On Linux, the menu is opened. + testname: "F10 to activate menubar", + events: pressF10Events(), + test: function() { sendKey("F10"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, true, testname + " openedWithKey"); + if (navigator.platform.indexOf("Linux") >= 0) + checkOpen("filemenu", testname); + else + checkClosed("filemenu", testname); + }, +}, +{ + // pressing cursor left then down should open a menu + testname: "cursor down on menu", + events: (navigator.platform.indexOf("Linux") >= 0) ? + [ "DOMMenuItemInactive filemenu", "DOMMenuItemActive helpmenu", + // This is in a different order than the + // "accelerator on active menubar" because menus opened from a + // shortcut key are fired asynchronously + "popuphiding filepopup", "popuphidden filepopup", + "popupshowing helppopup", "DOMMenuItemInactive item1", + "DOMMenuItemActive item2", "DOMMenuItemInactive item2", + "DOMMenuInactive filepopup", "DOMMenuItemActive contents", + "popupshown helppopup" ] : + [ "popupshowing helppopup", "DOMMenuItemInactive filemenu", + "DOMMenuItemActive helpmenu", + // This is in a different order than the + // "accelerator on active menubar" because menus opened from a + // shortcut key are fired asynchronously + "DOMMenuItemActive contents", "popupshown helppopup" ], + test: function() { sendKey("LEFT"); sendKey("DOWN"); }, + result: function(testname) { + is(document.getElementById("helpmenu").openedWithKey, true, testname + " openedWithKey"); + } +}, +{ + // pressing a letter that doesn't correspond to an accelerator. The menu + // should not close because there is more than one item corresponding to + // that letter + testname: "menuitem with no accelerator", + events: [ "DOMMenuItemInactive contents", "DOMMenuItemActive one" ], + test: function() { sendChar("o"); }, + result: function(testname) { checkOpen("helpmenu", testname); } +}, +{ + // pressing the letter again should select the next one that starts with + // that letter + testname: "menuitem with no accelerator again", + events: [ "DOMMenuItemInactive one", "DOMMenuItemActive only" ], + test: function() { sendChar("o"); }, + result: function(testname) { + // 'only' is a menu but it should not be open + checkOpen("helpmenu", testname); + checkClosed("only", testname); + } +}, +{ + // pressing the letter again when the next item is disabled should still + // select the disabled item + testname: "menuitem with no accelerator disabled", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuItemInactive only", "DOMMenuItemActive other" ], + test: function() { sendChar("o"); }, + result: function(testname) { } +}, +{ + // when only one menuitem starting with that letter exists, it should be + // selected and the menu closed + testname: "menuitem with no accelerator single", + events: function() { + var elist = [ "DOMMenuItemInactive other", "DOMMenuItemActive third", + "DOMMenuItemInactive third", "DOMMenuInactive helppopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive helpmenu", + "DOMMenuItemInactive helpmenu", + "command third", "popuphiding helppopup", + "popuphidden helppopup", "DOMMenuItemInactive third", + ]; + if (navigator.platform.indexOf("Win") == -1) + elist[0] = "DOMMenuItemInactive only"; + return elist; + }, + test: function() { sendChar("t"); }, + result: function(testname) { checkClosed("helpmenu", testname); } +}, +{ + // pressing F10 should highlight the first menu but not open it + testname: "F10 to activate menubar again", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ], + test: function() { sendKey("F10"); }, + result: function(testname) { checkClosed("filemenu", testname); }, +}, +{ + // pressing an accelerator for a disabled item should deactivate the menubar + testname: "accelerator for disabled menu", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuItemInactive filemenu", "DOMMenuBarInactive menubar" ], + test: function() { sendChar("s"); }, + result: function(testname) { + checkClosed("secretmenu", testname); + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + }, +}, +{ + testname: "press on disabled menu", + test: function() { + synthesizeMouse(document.getElementById("secretmenu"), 8, 8, { }); + }, + result: function (testname) { + checkClosed("secretmenu", testname); + } +}, +{ + testname: "press on second menu with shift", + events: [ "popupshowing editpopup", "DOMMenuBarActive menubar", + "DOMMenuItemActive editmenu", "popupshown editpopup" ], + test: function() { + synthesizeMouse(document.getElementById("editmenu"), 8, 8, { shiftKey : true }); + }, + result: function (testname) { + checkOpen("editmenu", testname); + checkActive(document.getElementById("menubar"), "editmenu", testname); + } +}, +{ + testname: "press on disabled menuitem", + test: function() { + synthesizeMouse(document.getElementById("cut"), 8, 8, { }); + }, + result: function (testname) { + checkOpen("editmenu", testname); + } +}, +{ + testname: "press on menuitem", + events: [ "DOMMenuInactive editpopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive editmenu", + "DOMMenuItemInactive editmenu", + "command copy", "popuphiding editpopup", "popuphidden editpopup", + "DOMMenuItemInactive copy", + ], + test: function() { + synthesizeMouse(document.getElementById("copy"), 8, 8, { }); + }, + result: function (testname) { + checkClosed("editmenu", testname); + } +}, +{ + // this test ensures that the menu can still be opened by clicking after selecting + // a menuitem from the menu. See bug 399350. + testname: "press on menu after menuitem selected", + events: [ "popupshowing editpopup", "DOMMenuBarActive menubar", + "DOMMenuItemActive editmenu", "popupshown editpopup" ], + test: function() { synthesizeMouse(document.getElementById("editmenu"), 8, 8, { }); }, + result: function (testname) { + checkActive(document.getElementById("editpopup"), "", testname); + checkOpen("editmenu", testname); + } +}, +{ // try selecting a different command + testname: "press on menuitem again", + events: [ "DOMMenuInactive editpopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive editmenu", + "DOMMenuItemInactive editmenu", + "command paste", "popuphiding editpopup", "popuphidden editpopup", + "DOMMenuItemInactive paste", + ], + test: function() { + synthesizeMouse(document.getElementById("paste"), 8, 8, { }); + }, + result: function (testname) { + checkClosed("editmenu", testname); + } +}, +{ + testname: "F10 to activate menubar for tab deactivation", + events: pressF10Events(), + test: function() { sendKey("F10"); }, +}, +{ + testname: "Deactivate menubar with tab key", + events: closeAfterF10Events(true), + test: function() { sendKey("TAB"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + testname: "F10 to activate menubar for escape deactivation", + events: pressF10Events(), + test: function() { sendKey("F10"); }, +}, +{ + testname: "Deactivate menubar with escape key", + events: closeAfterF10Events(false), + test: function() { sendKey("ESCAPE"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + testname: "F10 to activate menubar for f10 deactivation", + events: pressF10Events(), + test: function() { sendKey("F10"); }, +}, +{ + testname: "Deactivate menubar with f10 key", + events: closeAfterF10Events(true), + test: function() { sendKey("F10"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + testname: "F10 to activate menubar for alt deactivation", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ], + test: function() { sendKey("F10"); }, +}, +{ + testname: "Deactivate menubar with alt key", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu" ], + test: function() { sendKey("ALT"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + testname: "Don't activate menubar with mousedown during alt key auto-repeat", + test: function() { + synthesizeKey("VK_ALT", { type: "keydown" }); + synthesizeMouse(document.getElementById("menubar"), 8, -30, { type: "mousedown", altKey: true }); + synthesizeKey("VK_ALT", { type: "keydown" }); + synthesizeMouse(document.getElementById("menubar"), 8, -30, { type: "mouseup", altKey: true }); + synthesizeKey("VK_ALT", { type: "keydown" }); + synthesizeKey("VK_ALT", { type: "keyup" }); + }, + result: function (testname) { + checkActive(document.getElementById("menubar"), "", testname); + } +}, + +{ + testname: "Open menu and press alt key by itself - open menu", + events: [ "DOMMenuBarActive menubar", + "popupshowing filepopup", "DOMMenuItemActive filemenu", + "DOMMenuItemActive item1", "popupshown filepopup" ], + test: function() { synthesizeKey("F", { altKey: true }); }, + result: function (testname) { + checkOpen("filemenu", testname); + } +}, +{ + testname: "Open menu and press alt key by itself - close menu", + events: [ "popuphiding filepopup", "popuphidden filepopup", + "DOMMenuItemInactive item1", "DOMMenuInactive filepopup", + "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu", + "DOMMenuItemInactive filemenu" ], + test: function() { + synthesizeKey("VK_ALT", { }); + }, + result: function (testname) { + checkClosed("filemenu", testname); + } +}, + +// Fllowing 4 tests are a test of bug 616797, don't insert any new tests +// between them. +{ + testname: "Open file menu by accelerator", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + return [ "DOMMenuBarActive menubar", "popupshowing filepopup", + "DOMMenuItemActive filemenu", "DOMMenuItemActive item1", + "popupshown filepopup" ]; + }, + test: function() { + synthesizeKey("VK_ALT", { type: "keydown" }); + synthesizeKey("F", { altKey: true }); + synthesizeKey("VK_ALT", { type: "keyup" }); + } +}, +{ + testname: "Close file menu by click at outside of popup menu", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + return [ "popuphiding filepopup", "popuphidden filepopup", + "DOMMenuItemInactive item1", "DOMMenuInactive filepopup", + "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu", + "DOMMenuItemInactive filemenu" ]; + }, + test: function() { + // XXX hidePopup() causes DOMMenuItemInactive event to be fired twice. + document.getElementById("filepopup").hidePopup(); + } +}, +{ + testname: "Alt keydown set focus the menubar", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + return [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ]; + }, + test: function() { + sendKey("ALT"); + }, + result: function (testname) { + checkClosed("filemenu", testname); + } +}, +{ + testname: "unset focus the menubar", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + return [ "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu" ]; + }, + test: function() { + sendKey("ALT"); + } +}, + +// bug 625151 +{ + testname: "Alt key state before deactivating the window shouldn't prevent " + + "next Alt key handling", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + return [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ]; + }, + test: function() { + synthesizeKey("VK_ALT", { type: "keydown" }); + synthesizeKey("VK_TAB", { type: "keydown" }); // cancels the Alt key + var thisWindow = window; + var newWindow = + window.open("data:text/html,", "_blank", "width=100,height=100"); + newWindow.addEventListener("focus", function () { + newWindow.removeEventListener("focus", arguments.callee, false); + thisWindow.addEventListener("focus", function () { + thisWindow.removeEventListener("focus", arguments.callee, false); + setTimeout(function () { + sendKey("ALT", thisWindow); + }, 0); + }, false); + newWindow.close(); + thisWindow.focus(); + }, false); + } +} + +]; + +]]> +</script> + +</window> |