diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /accessible/tests/mochitest | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/tests/mochitest')
381 files changed, 62042 insertions, 0 deletions
diff --git a/accessible/tests/mochitest/.eslintrc.js b/accessible/tests/mochitest/.eslintrc.js new file mode 100644 index 0000000000..2ce1c5017a --- /dev/null +++ b/accessible/tests/mochitest/.eslintrc.js @@ -0,0 +1,24 @@ +"use strict"; + +module.exports = { + rules: { + // XXX These are rules that are enabled in the recommended configuration, but + // disabled here due to failures when initially implemented. They should be + // removed (and hence enabled) at some stage. + "no-nested-ternary": "off", + }, + + overrides: [ + { + files: [ + // Bug 1602061 TODO: These tests access DOM elements via + // id-as-variable-name, which eslint doesn't have support for yet. + "attributes/test_listbox.html", + "treeupdate/test_ariaowns.html", + ], + rules: { + "no-undef": "off", + }, + }, + ], +}; diff --git a/accessible/tests/mochitest/a11y.ini b/accessible/tests/mochitest/a11y.ini new file mode 100644 index 0000000000..885cb56e4a --- /dev/null +++ b/accessible/tests/mochitest/a11y.ini @@ -0,0 +1,21 @@ +[DEFAULT] +support-files = + ../../../dom/media/test/bug461281.ogg + ../../../dom/security/test/csp/dummy.pdf + ../../../image/test/mochitest/animated-gif-finalframe.gif + ../../../image/test/mochitest/animated-gif.gif + dumbfile.zip + formimage.png + letters.gif + moz.png + longdesc_src.html + *.js + treeview.css + +[test_aria_token_attrs.html] +[test_bug420863.html] +[test_custom_element_accessibility_defaults.html] +[test_descr.html] +[test_nsIAccessibleDocument.html] +[test_nsIAccessibleImage.html] +[test_OuterDocAccessible.html] diff --git a/accessible/tests/mochitest/actions.js b/accessible/tests/mochitest/actions.js new file mode 100644 index 0000000000..dc2f7d929d --- /dev/null +++ b/accessible/tests/mochitest/actions.js @@ -0,0 +1,231 @@ +/* import-globals-from common.js */ +/* import-globals-from events.js */ + +// ////////////////////////////////////////////////////////////////////////////// +// Event constants + +const MOUSEDOWN_EVENT = 1; +const MOUSEUP_EVENT = 2; +const CLICK_EVENT = 4; +const COMMAND_EVENT = 8; +const FOCUS_EVENT = 16; + +const CLICK_EVENTS = MOUSEDOWN_EVENT | MOUSEUP_EVENT | CLICK_EVENT; +const XUL_EVENTS = CLICK_EVENTS | COMMAND_EVENT; + +// ////////////////////////////////////////////////////////////////////////////// +// Public functions + +/** + * Test default accessible actions. + * + * Action tester interface is: + * + * var actionObj = { + * // identifier of accessible to perform an action on + * get ID() {}, + * + * // index of the action + * get actionIndex() {}, + * + * // name of the action + * get actionName() {}, + * + * // DOM events (see constants defined above) + * get events() {}, + * + * // [optional] identifier of target DOM events listeners are registered on, + * // used with 'events', if missing then 'ID' is used instead. + * get targetID() {}, + * + * // [optional] true to match DOM events bubbled up to the target, + * // false (default) to only match events fired directly on the target. + * get allowBubbling() {}, + * + * // [optional] perform checks when 'click' event is handled if 'events' + * // is used. + * checkOnClickEvent: function() {}, + * + * // [optional] an array of invoker's checker objects (see eventQueue + * // constructor events.js) + * get eventSeq() {} + * }; + * + * + * @param aArray [in] an array of action cheker objects + */ +function testActions(aArray) { + gActionsQueue = new eventQueue(); + + for (var idx = 0; idx < aArray.length; idx++) { + var actionObj = aArray[idx]; + var accOrElmOrID = actionObj.ID; + var actionIndex = actionObj.actionIndex; + var actionName = actionObj.actionName; + var events = actionObj.events; + var accOrElmOrIDOfTarget = actionObj.targetID + ? actionObj.targetID + : accOrElmOrID; + + var eventSeq = []; + if (events) { + var elm = getNode(accOrElmOrIDOfTarget); + if (events & MOUSEDOWN_EVENT) { + eventSeq.push(new checkerOfActionInvoker("mousedown", elm, actionObj)); + } + + if (events & MOUSEUP_EVENT) { + eventSeq.push(new checkerOfActionInvoker("mouseup", elm, actionObj)); + } + + if (events & CLICK_EVENT) { + eventSeq.push(new checkerOfActionInvoker("click", elm, actionObj)); + } + + if (events & COMMAND_EVENT) { + eventSeq.push(new checkerOfActionInvoker("command", elm, actionObj)); + } + + if (events & FOCUS_EVENT) { + eventSeq.push(new focusChecker(elm)); + } + } + + if (actionObj.eventSeq) { + eventSeq = eventSeq.concat(actionObj.eventSeq); + } + + var invoker = new actionInvoker( + accOrElmOrID, + actionIndex, + actionName, + eventSeq + ); + gActionsQueue.push(invoker); + } + + gActionsQueue.invoke(); +} + +/** + * Test action names and descriptions. + */ +function testActionNames(aID, aActions) { + var actions = typeof aActions == "string" ? [aActions] : aActions || []; + + var acc = getAccessible(aID); + is(acc.actionCount, actions.length, "Wong number of actions."); + for (var i = 0; i < actions.length; i++) { + is( + acc.getActionName(i), + actions[i], + "Wrong action name at " + i + " index." + ); + is( + acc.getActionDescription(0), + gActionDescrMap[actions[i]], + "Wrong action description at " + i + "index." + ); + } +} + +// ////////////////////////////////////////////////////////////////////////////// +// Private + +var gActionsQueue = null; + +function actionInvoker(aAccOrElmOrId, aActionIndex, aActionName, aEventSeq) { + this.invoke = function actionInvoker_invoke() { + var acc = getAccessible(aAccOrElmOrId); + if (!acc) { + return INVOKER_ACTION_FAILED; + } + + var isThereActions = acc.actionCount > 0; + ok( + isThereActions, + "No actions on the accessible for " + prettyName(aAccOrElmOrId) + ); + + if (!isThereActions) { + return INVOKER_ACTION_FAILED; + } + + is( + acc.getActionName(aActionIndex), + aActionName, + "Wrong action name of the accessible for " + prettyName(aAccOrElmOrId) + ); + + try { + acc.doAction(aActionIndex); + } catch (e) { + ok(false, "doAction(" + aActionIndex + ") failed with: " + e.name); + return INVOKER_ACTION_FAILED; + } + return null; + }; + + this.eventSeq = aEventSeq; + + this.getID = function actionInvoker_getID() { + return ( + "invoke an action " + + aActionName + + " at index " + + aActionIndex + + " on " + + prettyName(aAccOrElmOrId) + ); + }; +} + +function checkerOfActionInvoker(aType, aTarget, aActionObj) { + this.type = aType; + + this.target = aTarget; + + if (aActionObj && "eventTarget" in aActionObj) { + this.eventTarget = aActionObj.eventTarget; + } + + if (aActionObj && aActionObj.allowBubbling) { + // Normally, we add event listeners on the document. To catch bubbled + // events, we need to add the listener on the target itself. + this.eventTarget = "element"; + // Normally, we only match an event fired directly on the target. Override + // this to match a bubbled event. + this.match = function (aEvent) { + return aEvent.currentTarget == aTarget; + }; + } + + this.phase = false; + + this.getID = function getID() { + return aType + " event handling"; + }; + + this.check = function check(aEvent) { + if (aType == "click" && aActionObj && "checkOnClickEvent" in aActionObj) { + aActionObj.checkOnClickEvent(aEvent); + } + }; +} + +var gActionDescrMap = { + jump: "Jump", + press: "Press", + check: "Check", + uncheck: "Uncheck", + select: "Select", + open: "Open", + close: "Close", + switch: "Switch", + click: "Click", + collapse: "Collapse", + expand: "Expand", + activate: "Activate", + cycle: "Cycle", + "click ancestor": "Click ancestor", +}; diff --git a/accessible/tests/mochitest/actions/a11y.ini b/accessible/tests/mochitest/actions/a11y.ini new file mode 100644 index 0000000000..5669e8d963 --- /dev/null +++ b/accessible/tests/mochitest/actions/a11y.ini @@ -0,0 +1,17 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/dom/media/test/bug461281.ogg + +[test_anchors.html] +[test_aria.html] +[test_controls.html] +[test_general.html] +[test_general.xhtml] +[test_keys.html] +[test_keys.xhtml] +[test_link.html] +[test_media.html] +[test_select.html] +[test_tree.xhtml] +[test_treegrid.xhtml] diff --git a/accessible/tests/mochitest/actions/test_anchors.html b/accessible/tests/mochitest/actions/test_anchors.html new file mode 100644 index 0000000000..6ee1e0c450 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_anchors.html @@ -0,0 +1,146 @@ +<html> + +<head> + <title>nsIAccessible actions testing for HTML links that + scroll the page to named anchors</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../actions.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Event checkers + + function scrollingChecker(aAcc) { + this.type = EVENT_SCROLLING_START; + this.target = aAcc; + this.getID = function scrollingChecker_getID() { + return "scrolling start handling for " + prettyName(aAcc); + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "debug"; // debug stuff + // gA11yEventDumpToConsole = true; // debug stuff + + function doTest() { + var actionsArray = [ + { + ID: "anchor1", + actionName: "jump", + actionIndex: 0, + events: CLICK_EVENTS, + eventSeq: [ + new scrollingChecker(getAccessible("bottom1")), + ], + }, + { // jump again (test for bug 437607) + ID: "anchor1", + actionName: "jump", + actionIndex: 0, + events: CLICK_EVENTS, + eventSeq: [ + new scrollingChecker(getAccessible("bottom1")), + ], + }, + { + ID: "anchor2", + actionName: "jump", + actionIndex: 0, + events: CLICK_EVENTS, + eventSeq: [ + new scrollingChecker(getAccessible("bottom2")), + ], + }, + ]; + + testActions(actionsArray); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=506389" + title="Some same page links do not fire EVENT_SYSTEM_SCROLLINGSTART"> + Mozilla Bug 506389 + </a><br> + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=437607" + title="Clicking the 'Skip to main content' link once works, second time fails to initiate a V cursor jump"> + Mozilla Bug 437607 + </a><br> + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=519303" + title="Same page links to targets with content fires scrolling start accessible event on leaf text node"> + Mozilla Bug 519303 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="debug"></div> + + <h1>This is a test page for anchors</h1> + This is a top anchor<a name="Top"> + </a><a id="anchor1" href="#bottom1">Link to anchor</a> + <a id="anchor2" href="#bottom2">Link to div</a> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br>This is some text in the middle<br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + This is some text. + This is a bottom anchor<a id="bottom1"></a> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <div id="bottom2">This is a div</div> +</body> +</html> diff --git a/accessible/tests/mochitest/actions/test_aria.html b/accessible/tests/mochitest/actions/test_aria.html new file mode 100644 index 0000000000..7ec0f8ed35 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_aria.html @@ -0,0 +1,200 @@ +<html> + +<head> + <title>nsIAccessible actions testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../actions.js"></script> + + <script type="application/javascript"> + function doTest() { + var actionsArray = [ + { + ID: "clickable", + actionName: "click", + events: CLICK_EVENTS, + }, + { + ID: "button", + actionName: "press", + events: CLICK_EVENTS, + }, + { + ID: "checkbox_unchecked", + actionName: "check", + events: CLICK_EVENTS, + }, + { + ID: "checkbox_checked", + actionName: "uncheck", + events: CLICK_EVENTS, + }, + { + ID: "checkbox_mixed", + actionName: "cycle", + events: CLICK_EVENTS, + }, + { + ID: "combobox_collapsed", + actionName: "open", + events: CLICK_EVENTS, + }, + { + ID: "combobox_expanded", + actionName: "close", + events: CLICK_EVENTS, + }, + { + ID: "link", + actionName: "jump", + events: CLICK_EVENTS, + }, + { + ID: "menuitem", + actionName: "click", + events: CLICK_EVENTS, + }, + { + ID: "menuitemcheckbox", + actionName: "click", + events: CLICK_EVENTS, + }, + { + ID: "menuitemradio", + actionName: "click", + events: CLICK_EVENTS, + }, + { + ID: "option", + actionName: "select", + events: CLICK_EVENTS, + }, + { + ID: "radio", + actionName: "select", + events: CLICK_EVENTS, + }, + { + ID: "switch_unchecked", + actionName: "check", + events: CLICK_EVENTS, + }, + { + ID: "switch_checked", + actionName: "uncheck", + events: CLICK_EVENTS, + }, + { + ID: "tab", + actionName: "switch", + events: CLICK_EVENTS, + }, + { + ID: "textbox", + actionName: "activate", + events: CLICK_EVENTS, + }, + { + ID: "treeitem", + actionName: "activate", + events: CLICK_EVENTS, + }, + { + ID: "sortable", + actionName: "sort", + events: CLICK_EVENTS, + }, + { + ID: "expandable", + actionName: "expand", + events: CLICK_EVENTS, + }, + { + ID: "collapseable", + actionName: "collapse", + events: CLICK_EVENTS, + }, + ]; + testActions(actionsArray); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=410765" + title="nsIAccessible actions testing"> + Mozilla Bug 410765 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="clickable" onclick="">Clickable text</div> + + <div id="button" role="button">Button</div> + + <div id="checkbox_unchecked" role="checkbox">Checkbox</div> + + <div id="checkbox_checked" role="checkbox" aria-checked="true">Checkbox</div> + + <div id="checkbox_mixed" role="checkbox" aria-checked="mixed">Checkbox</div> + + <div id="combobox_collapsed" role="combobox"> + <div id="option" role="option">Option of collapsed combobox</div> + </div> + + <div id="combobox_expanded" role="combobox" aria-expanded="true"> + <div role="option">Option of expanded combobox</div> + </div> + + <div id="link" role="link">Link</div> + + <div role="menu"> + <div id="menuitem" role="menuitem">Menuitem</div> + <div id="menuitemcheckbox" role="menuitemcheckbox">Menuitem checkbox</div> + <div id="menuitemradio" role="menuitemradio">Menuitem radio</div> + </div> + + <div role="radiogroup"> + <div id="radio" role="radio">Radio</div> + </div> + + <div id="switch_unchecked" role="switch">Switch</div> + + <div id="switch_checked" role="switch" aria-checked="true">Switch</div> + + <div role="tablist"> + <div id="tab" role="tab">Tab</div> + </div> + + <div id="textbox" role="textbox">Textbox</div> + + <div role="tree"> + <div id="treeitem" role="treeitem">Treeitem</div> + </div> + + <div role="grid"> + <div id="sortable" role="columnheader" aria-sort="ascending"> + Columnheader + </div> + </div> + + <div id="expandable" aria-expanded="false">collapsed</div> + <div id="collapseable" aria-expanded="true">expanded</div> +</body> +</html> diff --git a/accessible/tests/mochitest/actions/test_controls.html b/accessible/tests/mochitest/actions/test_controls.html new file mode 100644 index 0000000000..8b6f413619 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_controls.html @@ -0,0 +1,107 @@ +<html> + +<head> + <title>nsIAccessible actions testing for inputs</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../actions.js"></script> + + <script type="application/javascript"> + function doTest() { + var actionsArray = [ + { + ID: "button", + actionName: "press", + events: CLICK_EVENTS, + }, + { + ID: "input_button", + actionName: "press", + events: CLICK_EVENTS, + }, + { + ID: "checkbox_unchecked", + actionName: "check", + events: CLICK_EVENTS, + }, + { + ID: "checkbox_checked", + actionName: "uncheck", + events: CLICK_EVENTS, + }, + { + ID: "checkbox_mixed", + actionName: "cycle", + events: CLICK_EVENTS, + }, + { + ID: "radio", + actionName: "select", + events: CLICK_EVENTS, + }, + { + ID: "textarea", + actionName: "activate", + events: FOCUS_EVENT, + }, + { + ID: "textinput", + actionName: "activate", + events: FOCUS_EVENT, + }, + + ]; + document.getElementById("checkbox_mixed").indeterminate = true; + + testActions(actionsArray); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=477975" + title="nsIAccessible actions testing"> + Mozilla Bug 477975 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <button id="button">Button</button> + + <input id="input_button" type="button" value="normal"> + + <input id="checkbox_unchecked" type="checkbox">Checkbox</input> + + <input id="checkbox_checked" type="checkbox" checked="true">Checkbox</input> + + <input id="checkbox_mixed" type="checkbox">Checkbox</input> + + <fieldset> + <input id="radio" type="radio">Radio</input> + </fieldset> + + <textarea id="textarea" placeholder="What's happening?"></textarea> + + <input id="textinput" type="text"> +</body> +</html> diff --git a/accessible/tests/mochitest/actions/test_general.html b/accessible/tests/mochitest/actions/test_general.html new file mode 100644 index 0000000000..025b18f175 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_general.html @@ -0,0 +1,105 @@ +<html> + +<head> + <title>nsIAccessible actions testing on HTML elements</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../actions.js"></script> + + <script type="application/javascript"> + function doTest() { + var actionsArray = [ + { + ID: "li_clickable1", + actionName: "click", + events: CLICK_EVENTS, + }, + { + ID: "li_clickable2", + actionName: "click", + events: CLICK_EVENTS, + }, + { + ID: "li_clickable3", + actionName: "click", + events: CLICK_EVENTS, + }, + { + ID: "onclick_img", + actionName: "click", + events: CLICK_EVENTS, + }, + { + ID: "label1", + actionName: "click", + events: CLICK_EVENTS, + }, + + ]; + + testActions(actionsArray); + + is(getAccessible("label1").firstChild.actionCount, 1, "label text should have 1 action"); + + getAccessible("onclick_img").takeFocus(); + is(getAccessible("link1").actionCount, 1, "links should have one action"); + is(getAccessible("link2").actionCount, 1, "link with onclick handler should have 1 action"); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=523789" + title="nsHTMLLiAccessible shouldn't be inherited from linkable accessible"> + Mozilla Bug 523789 + </a><br> + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409" + title="Expose click action if mouseup and mousedown are registered"> + Mozilla Bug 423409 + </a> + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=659620" + title="hang when trying to edit a page on wikimo with NVDA running"> + Mozilla Bug 659620 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <ul> + <li id="li_clickable1" onclick="">Clickable list item</li> + <li id="li_clickable2" onmousedown="">Clickable list item</li> + <li id="li_clickable3" onmouseup="">Clickable list item</li> + </ul> + + <!-- linkable accessibles --> + <img id="onclick_img" onclick="" src="../moz.png"> + + <a id="link1" href="www">linkable textleaf accessible</a> + <div id="link2" onclick="">linkable textleaf accessible</div> + + <div> + <label for="TextBox_t2" id="label1"> + <span>Explicit</span> + </label> + <input name="in2" id="TextBox_t2" type="text" maxlength="17"> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/actions/test_general.xhtml b/accessible/tests/mochitest/actions/test_general.xhtml new file mode 100644 index 0000000000..5b376b9624 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_general.xhtml @@ -0,0 +1,167 @@ +<?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"?> +<?xml-stylesheet href="../nsIAccessible_name.css" + type="text/css"?> + + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="nsIAccessible actions testing"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../actions.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + + <script type="application/javascript"> + <![CDATA[ + //gA11yEventDumpToConsole = true; + //enableLogging("tree,verbose"); // debug + + SimpleTest.expectAssertions(0, 1); + + function doTest() + { + var actionsArray = [ + { + ID: "menu", + actionName: "click", + events: CLICK_EVENTS, + // Wait for the submenu to show up. + eventSeq: [ + new invokerChecker(EVENT_SHOW, getNode("submenu")) + ] + }, + { + ID: "submenu", + actionName: "click", + events: CLICK_EVENTS + }, + { + ID: "menuitem", + actionName: "click", + events: XUL_EVENTS + }, + { + ID: "button", + actionName: "press", + events: XUL_EVENTS + }, + { + ID: "buttonmenu", + actionName: "press", + events: CLICK_EVENTS + }, + { + ID: "name_entry_label", + actionName: "click", + events: CLICK_EVENTS + }, + { + ID: "labelWithPopup", + actionName: "click", + events: CLICK_EVENTS + }, + { + ID: "toolbarbutton_label", + actionName: "click", + targetID: "toolbarbutton", + events: XUL_EVENTS, + allowBubbling: true + }, + { + ID: "menulist_label", + actionName: "click", + // focusChecker expects a unique focus event. However, there might + // still be pending focus events not caught by previous tests. + eventSeq: [ + new invokerChecker(EVENT_FOCUS, getNode("menulist")) + ] + }/*, // XXX: bug 490288 + { + ID: "buttonmenu_item", + actionName: "click", + events: CLICK_EVENTS + }*/ + ]; + + is(getAccessible("name_entry_label").firstChild.actionCount, 1, "label text should have 1 action"); + + testActions(actionsArray); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=410765" + title="nsIAccessible actions testing"> + Mozilla Bug 410765 + </a> + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=504252" + title="Expose STATE_HASPOPUP on XUL elements that have an @popup attribute"> + Mozilla Bug 504252 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <menubar> + <menu label="menu" id="menu"> + <menupopup> + <menuitem label="menu item" id="menuitem"/> + <menu label="submenu" id="submenu"> + <menupopup> + <menuitem label="menu item"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menubar> + + <button label="button" id="button"/> + + <button type="menu" id="buttonmenu" label="button"> + <menupopup> + <menuitem label="item1" id="buttonmenu_item"/> + <menuitem label="item1"/> + </menupopup> + </button> + + <label id="labelWithPopup" value="file name" + popup="fileContext" + tabindex="0"/> + <hbox> + <label id="name_entry_label" value="Name" control="name_entry"/> + <html:input id="name_entry"/> + </hbox> + <toolbarbutton id="toolbarbutton"> + <label id="toolbarbutton_label">toolbarbutton</label> + </toolbarbutton> + <hbox> + <label id="menulist_label" control="menulist">menulist</label> + <menulist id="menulist"/> + </hbox> + </vbox> + </hbox> +</window> + diff --git a/accessible/tests/mochitest/actions/test_keys.html b/accessible/tests/mochitest/actions/test_keys.html new file mode 100644 index 0000000000..acacb34c09 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_keys.html @@ -0,0 +1,57 @@ +<html> + +<head> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> + <title>Keyboard shortcuts tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + + <script type="application/javascript"> + function testAcessKey(aAccOrElmOrID, aKey) { + var acc = getAccessible(aAccOrElmOrID); + if (!acc) + return; + + is(acc.accessKey, aKey, + "Wrong keyboard shortcut on " + prettyName(aAccOrElmOrID)); + } + + function doTest() { + testAcessKey("input1", ""); + testAcessKey("input2", MAC ? "⌃⌥b" : "Alt+Shift+b"); + testAcessKey("link", MAC ? "⌃⌥l" : "Alt+Shift+l"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=381599" + title="Inverse relations cache"> + Mozilla Bug 381599 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <label accesskey="a"> + <input id="input1"/> + </label> + <label accesskey="b" for="input2"> + <input id="input2"/> + <a id="link" accesskey="l">link</a> +</body> +</html> diff --git a/accessible/tests/mochitest/actions/test_keys.xhtml b/accessible/tests/mochitest/actions/test_keys.xhtml new file mode 100644 index 0000000000..46d936166a --- /dev/null +++ b/accessible/tests/mochitest/actions/test_keys.xhtml @@ -0,0 +1,124 @@ +<?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" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Accessible XUL access keys and shortcut keys tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + function openMenu(aMenuID, aMenuitemID) + { + this.menuNode = getNode(aMenuID); + this.menuitemNode = getNode(aMenuitemID); + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, this.menuNode) + ]; + + this.invoke = function openMenu_invoke() + { + // Show menu. + this.menuNode.open = true; + } + + this.finalCheck = function openMenu_finalCheck() + { + var menu = getAccessible(aMenuID); + is(menu.accessKey, (MAC ? "u" : "Alt+u"), + "Wrong accesskey on " + prettyName(this.menuitemNode)); + + var menuitem = getAccessible(aMenuitemID); + is(menuitem.accessKey, "p", + "Wrong accesskey on " + prettyName(this.menuitemNode)); + is(menuitem.keyboardShortcut, (MAC ? "⌃l" : "Ctrl+l"), + "Wrong keyboard shortcut on " + prettyName(this.menuitemNode)); + } + + this.getID = function openMenu_getID() + { + return "menuitem accesskey and shortcut test " + + prettyName(this.menuItemNode); + } + } + + var gQueue = null; + function doTest() + { + // HTML element should get accessKey from associated XUL label. + let input = getAccessible("input"); + is(input.accessKey, (MAC ? "⌃⌥i" : "Alt+Shift+i"), + "Wrong accessKey on input"); + + // Test accessKey on HTML element inside shadow DOM. + let shadowButton = getAccessible( + document.getElementById("buttonShadow").shadowRoot.firstElementChild); + is(shadowButton.accessKey, (MAC ? "⌃⌥t" : "Alt+Shift+t"), + "Wrong accessKey on shadow button"); + + gQueue = new eventQueue(); + gQueue.push(new openMenu("menu", "menuitem")); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=672092" + title="Reorganize access key and keyboard shortcut handling code"> + Mozilla Bug 672092 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <label control="input" accesskey="i">input</label> + <html:input id="input"/> + + <html:div id="buttonShadow"/> + <script> + <![CDATA[ + let host = document.getElementById("buttonShadow"); + let shadow = host.attachShadow({mode: "open"}); + let button = document.createElement("button"); + button.setAttribute("accesskey", "t"); + shadow.append(button); + ]]> + </script> + + <keyset> + <key key="l" modifiers="control" id="key1"/> + </keyset> + + <menubar> + <menu label="menu" id="menu" accesskey="u"> + <menupopup> + <menuitem accesskey="p" key="key1" label="item1" id="menuitem"/> + </menupopup> + </menu> + </menubar> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/actions/test_link.html b/accessible/tests/mochitest/actions/test_link.html new file mode 100644 index 0000000000..cf10f4bec0 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_link.html @@ -0,0 +1,145 @@ +<html> + +<head> + <title>nsIAccessible actions testing on HTML links (HTML:a)</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../actions.js"></script> + + <script type="application/javascript"> + function getAnchorTargetDocumentAcc() { + var thisTabDocAcc = getTabDocAccessible(); + var thisDocTabPanelAcc = thisTabDocAcc.parent.parent; + var tabPanelsAcc = thisDocTabPanelAcc.parent; + var newDocTabPanelAcc = tabPanelsAcc.firstChild; + var nextAcc = newDocTabPanelAcc; + + while ((nextAcc = nextAcc.nextSibling)) { + // Find the last accessible for a browser with about:mozilla loaded. + if (nextAcc.firstChild.DOMNode.currentURI.spec == "about:mozilla") { + newDocTabPanelAcc = nextAcc; + } + } + + return newDocTabPanelAcc.firstChild.firstChild; + } + + function linkChecker(aID) { + this.type = EVENT_DOCUMENT_LOAD_COMPLETE; + this.__defineGetter__("target", getAnchorTargetDocumentAcc); + + this.check = function linkChecker_check() { + var anchorTargetWindow = + getAccessible(getAnchorTargetDocumentAcc(), [nsIAccessibleDocument]). + window; + anchorTargetWindow.close(); + }; + + this.getID = function linkChecker_getID() { + return "link '" + aID + "' states check "; + }; + } + + // gA11yEventDumpToConsole = true; + // enableLogging("tree,eventTree,verbose"); + + function doTest() { + var actionsArray = [ + { + ID: "link1", + actionName: "jump", + events: CLICK_EVENTS, + eventSeq: [ + new linkChecker("link1"), + ], + }, + { + ID: "img1", + targetID: "link1", + actionName: "click ancestor", + events: CLICK_EVENTS, + allowBubbling: true, + eventSeq: [ + new linkChecker("link1"), + ], + }, + { + ID: "link2", + actionName: "click", + events: CLICK_EVENTS, + }, + { + ID: "img2", + targetID: "link2", + actionName: "click ancestor", + events: CLICK_EVENTS, + allowBubbling: true, + }, + { + ID: "link3", + actionName: "click", + events: CLICK_EVENTS, + }, + { + ID: "img3", + targetID: "link3", + actionName: "click ancestor", + events: CLICK_EVENTS, + allowBubbling: true, + }, + { + ID: "link4", + actionName: "click", + events: CLICK_EVENTS, + }, + { + ID: "img4", + targetID: "link4", + actionName: "click ancestor", + events: CLICK_EVENTS, + allowBubbling: true, + }, + ]; + testActions(actionsArray); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409" + title="Expose click action if mouseup and mousedown are registered"> + Mozilla Bug 423409 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <a href="about:mozilla" id="link1" target="_blank" rel="opener"> + <img src="../moz.png" id="img1"> + </a> + <a id="link2" onmousedown=""> + <img src="../moz.png" id="img2"> + </a> + <a id="link3" onclick=""> + <img src="../moz.png" id="img3"> + </a> + <a id="link4" onmouseup=""> + <img src="../moz.png" id="img4"> + </a> +</body> +</html> diff --git a/accessible/tests/mochitest/actions/test_media.html b/accessible/tests/mochitest/actions/test_media.html new file mode 100644 index 0000000000..f6232e51e9 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_media.html @@ -0,0 +1,130 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=483573 +--> +<head> + <title>HTML5 audio/video tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../actions.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + + // gA11yEventDumpID = "eventDump"; + // gA11yEventDumpToConsole = true; // debug stuff + + function focusChecker(aAcc) { + this.type = EVENT_FOCUS; + this.target = aAcc; + this.getID = function focusChecker_getID() { + return "focus handling"; + }; + this.check = function focusChecker_check(aEvent) { + testStates(this.target, STATE_FOCUSED); + }; + } + + function nameChecker(aAcc, aName) { + this.type = EVENT_NAME_CHANGE; + this.target = aAcc; + this.getID = function nameChecker_getID() { + return "name change handling"; + }; + this.check = function nameChecker_check(aEvent) { + is(aEvent.accessible.name, aName, + "Wrong name of " + prettyName(aEvent.accessible) + " on focus"); + }; + } + + async function loadAudioSource() { + /** + * Setting the source dynamically and wait for it to load, + * so we can test the accessibility tree of the control in its ready and + * stable state. + * + * See bug 1484048 comment 25 for discussion on how it switches UI when + * loading a statically declared source. + */ + await new Promise(resolve => { + let el = document.getElementById("audio"); + el.addEventListener("canplaythrough", resolve, {once: true}); + el.src = "../bug461281.ogg"; + }); + + doTest(); + } + + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // test actions of audio controls + + todo(false, "Focus test are disabled until bug 494175 is fixed."); + + var audioElm = getAccessible("audio"); + var playBtn = audioElm.firstChild; + // var scrubber = playBtn.nextSibling.nextSibling.nextSibling; + var muteBtn = audioElm.lastChild.previousSibling; + + var actions = [ + { + ID: muteBtn, + actionName: "press", + eventTarget: "element", + eventSeq: [ + // new focusChecker(muteBtn), + new nameChecker(muteBtn, "Unmute"), + ], + }, + // { + // ID: scrubber, + // actionName: "activate", + // events: null, + // eventSeq: [ + // new focusChecker(scrubber) + // ] + // }, + { + ID: playBtn, + actionName: "press", + eventTarget: "element", + eventSeq: [ + // new focusChecker(playBtn), + new nameChecker(playBtn, "Pause"), + ], + }, + ]; + + testActions(actions); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(loadAudioSource); + </script> +</head> +<body> + + <a target="_blank" rel="opener" + title="Expose HTML5 video and audio elements' embedded controls through accessibility APIs" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=483573">Mozilla Bug 483573</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <audio id="audio" controls="true"></audio> + + <div id="eventDump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/actions/test_select.html b/accessible/tests/mochitest/actions/test_select.html new file mode 100644 index 0000000000..7f55dfc0ef --- /dev/null +++ b/accessible/tests/mochitest/actions/test_select.html @@ -0,0 +1,67 @@ +<html> + +<head> + <title>nsIAccessible actions testing for HTML select</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../actions.js"></script> + + <script type="application/javascript"> + // gA11yEventDumpToConsole = true; // debugging + function doTest() { + var actionsArray = [ + { + ID: "lb_apple", + actionIndex: 0, + actionName: "select", + events: CLICK_EVENTS, + eventSeq: [ + new focusChecker("lb_apple"), + ], + }, + ]; + + testActions(actionsArray); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select id="listbox" size="2"> + <option id="lb_orange">orange</option> + <option id="lb_apple">apple</option> + </select> + + <select id="combobox"> + <option id="cb_orange">orange</option> + <option id="cb_apple">apple</option> + </select> +</body> +</html> diff --git a/accessible/tests/mochitest/actions/test_tree.xhtml b/accessible/tests/mochitest/actions/test_tree.xhtml new file mode 100644 index 0000000000..17710cbdce --- /dev/null +++ b/accessible/tests/mochitest/actions/test_tree.xhtml @@ -0,0 +1,127 @@ +<?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="Accessible XUL tree actions tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../actions.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Accessible tree testers + + function stateFocusChecker(aAcc, aStates) + { + this.__proto__ = new focusChecker(aAcc); + + this.check = function focusChecker_check(aEvent) + { + var states = aStates ? aStates : 0; + testStates(this.target, STATE_FOCUSED | STATE_SELECTED | states); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "debug"; + //gA11yEventDumpToConsole = true; // debug + + function doTest() + { + var treeNode = getNode("tree"); + + var treeBodyNode = treeNode.treeBody; + + var tree = getAccessible(treeNode); + var expandedTreeItem = tree.getChildAt(2); + var collapsedTreeItem = tree.getChildAt(5); + + var actions = [ + { + ID: expandedTreeItem, + actionName: "activate", + actionIndex: 0, + events: CLICK_EVENTS, + targetID: treeBodyNode, + eventSeq: [ + new stateFocusChecker(expandedTreeItem, STATE_EXPANDED) + ] + }, + { + ID: collapsedTreeItem, + actionName: "expand", + actionIndex: 1, + events: CLICK_EVENTS, + targetID: treeBodyNode, + checkOnClickEvent: function check(aEvent) + { + testStates(this.ID, STATE_EXPANDED); + } + }, + { + ID: collapsedTreeItem, + actionName: "collapse", + actionIndex: 1, + events: CLICK_EVENTS, + targetID: treeBodyNode, + checkOnClickEvent: function check(aEvent) + { + testStates(this.ID, STATE_COLLAPSED); + } + } + ]; + + testActions(actions); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yXULTreeLoadEvent(doTest, "tree", new nsTreeTreeView()); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727" + title="Reorganize implementation of XUL tree accessibility"> + Mozilla Bug 503727 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tree id="tree" flex="1" minheight="100px"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + </treecols> + <treechildren/> + </tree> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/actions/test_treegrid.xhtml b/accessible/tests/mochitest/actions/test_treegrid.xhtml new file mode 100644 index 0000000000..1c6e1bb8aa --- /dev/null +++ b/accessible/tests/mochitest/actions/test_treegrid.xhtml @@ -0,0 +1,190 @@ +<?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"?> +<?xml-stylesheet href="../treeview.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL tree actions tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../actions.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Accessible tree testers + + function focusChecker(aAcc, aStates) + { + this.type = EVENT_FOCUS; + this.target = aAcc; + this.getID = function focusChecker_getID() + { + return "focus handling"; + } + this.check = function focusChecker_check(aEvent) + { + var states = aStates ? aStates : 0; + testStates(this.target, STATE_FOCUSED | STATE_SELECTED | states); + } + } + + function stateChangeChecker(aAcc, aIsEnabled) + { + this.type = EVENT_STATE_CHANGE; + this.target = aAcc; + this.getID = function stateChangeChecker_getID() + { + return "state change handling"; + } + this.check = function stateChangeChecker_check(aEvent) + { + if (aIsEnabled) + testStates(this.target, STATE_CHECKED); + else + testStates(this.target, STATE_CHECKABLE, 0, STATE_CHECKED); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTestActions() + { + var treeNode = getNode("tabletree"); + + var treeBodyNode = treeNode.treeBody; + treeNode.focus(); + + var tree = getAccessible(treeNode); + + var expandedTreeItem = tree.getChildAt(2); + var collapsedTreeItem = tree.getChildAt(5); + var cycleCell = expandedTreeItem.getChildAt(0); + var checkableCell = expandedTreeItem.getChildAt(3); + + var actions = [ + { + ID: expandedTreeItem, + actionName: "activate", + actionIndex: 0, + events: CLICK_EVENTS, + targetID: treeBodyNode, + eventSeq: [ + new focusChecker(expandedTreeItem, STATE_EXPANDED) + ] + }, + { + ID: collapsedTreeItem, + actionName: "expand", + actionIndex: 1, + events: CLICK_EVENTS, + targetID: treeBodyNode, + check: function check(aEvent) + { + testStates(this.ID, STATE_EXPANDED); + } + }, + { + ID: collapsedTreeItem, + actionName: "collapse", + actionIndex: 1, + events: CLICK_EVENTS, + targetID: treeBodyNode, + check: function check(aEvent) + { + testStates(this.ID, STATE_COLLAPSED); + } + }, + { + ID: cycleCell, + actionName: "cycle", + actionIndex: 0, + events: CLICK_EVENTS, + targetID: treeBodyNode + }, + { + ID: checkableCell, + actionName: "uncheck", + actionIndex: 0, + events: CLICK_EVENTS, + targetID: treeBodyNode, + eventSeq: [ + new stateChangeChecker(checkableCell, false) + ] + }, + { + ID: checkableCell, + actionName: "check", + actionIndex: 0, + events: CLICK_EVENTS, + targetID: treeBodyNode, + eventSeq: [ + new stateChangeChecker(checkableCell, true) + ] + } + ]; + + testActions(actions); // Will call SimpleTest.finish(); + } + + // gA11yEventDumpID = "debug"; + + function doTest() + { + var treeNode = getNode("tabletree"); + waitForEvent(EVENT_REORDER, treeNode, doTestActions); + treeNode.view = new nsTreeTreeView(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" rel="opener" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727" + title="Reorganize implementation of XUL tree accessibility"> + Mozilla Bug 503727 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tree id="tabletree" flex="1" editable="true"> + <treecols> + <treecol id="tabletree_col1" cycler="true" label="cycler"/> + <treecol id="tabletree_col2" flex="1" primary="true" label="column1"/> + <treecol id="tabletree_col3" flex="1" label="column2"/> + <treecol id="tabletree_col4" flex="1" label="checker" + type="checkbox" editable="true"/> + </treecols> + <treechildren/> + </tree> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/aom/a11y.ini b/accessible/tests/mochitest/aom/a11y.ini new file mode 100644 index 0000000000..03085c1deb --- /dev/null +++ b/accessible/tests/mochitest/aom/a11y.ini @@ -0,0 +1,3 @@ +[DEFAULT] + +[test_general.html] diff --git a/accessible/tests/mochitest/aom/test_general.html b/accessible/tests/mochitest/aom/test_general.html new file mode 100644 index 0000000000..dc63fb659b --- /dev/null +++ b/accessible/tests/mochitest/aom/test_general.html @@ -0,0 +1,208 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Accessibility API: generic</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script> + "use strict"; + + SimpleTest.waitForExplicitFinish(); + const finish = SimpleTest.finish.bind(SimpleTest); + enablePref() + .then(createIframe) + .then(checkImplementation) + .catch(err => { + dump(`${err}: ${err.stack}`); + finish(); + }); + + function enablePref() { + const ops = { + "set": [ + [ "accessibility.AOM.enabled", true ], + ], + }; + return SpecialPowers.pushPrefEnv(ops); + } + + // WebIDL conditional annotations for an interface are evaluated once per + // global, so we need to create an iframe to see the effects of calling + // enablePref(). + function createIframe() { + return new Promise((resolve) => { + let iframe = document.createElement("iframe"); + iframe.src = `data:text/html,<html><body>hey</body></html>`; + iframe.onload = () => resolve(iframe.contentDocument); + document.body.appendChild(iframe); + document.body.offsetTop; // We rely on the a11y tree being created + // already, and it's created off layout. + }); + } + + function testStringProp(anode, prop) { + is(anode[prop], null, `anode.${prop} should be null`); + let text = "This is a string test"; + anode[prop] = text; + is(anode[prop], text, `anode.${prop} was assigned "${text}"`); + anode[prop] = null; + is(anode[prop], null, `anode.${prop} was assigned null`); + } + + function testBoolProp(anode, prop) { + is(anode[prop], null, `anode.${prop} should be null`); + anode[prop] = true; + is(anode[prop], true, `anode.${prop} was assigned true`); + anode[prop] = false; + is(anode[prop], false, `anode.${prop} was assigned false`); + anode[prop] = null; + is(anode[prop], null, `anode.${prop} was assigned null`); + } + + function testDoubleProp(anode, prop) { + is(anode[prop], null, `anode.${prop} should be null`); + anode[prop] = Number.MAX_VALUE; + is(anode[prop], Number.MAX_VALUE, `anode.${prop} was assigned ${Number.MAX_VALUE}`); + anode[prop] = null; + is(anode[prop], null, `anode.${prop} was assigned null`); + } + + function testIntProp(anode, prop) { + is(anode[prop], null, `anode.${prop} should be null`); + anode[prop] = -1; + is(anode[prop], -1, `anode.${prop} was assigned -1`); + anode[prop] = null; + is(anode[prop], null, `anode.${prop} was assigned null`); + } + + function testUIntProp(anode, prop) { + is(anode[prop], null, `anode.${prop} should be null`); + anode[prop] = 4294967295; + is(anode[prop], 4294967295, `anode.${prop} was assigned 4294967295`); + anode[prop] = null; + is(anode[prop], null, `anode.${prop} was assigned null`); + } + + function testRelationProp(anode, node, prop) { + is(anode[prop], null, `anode.${prop} should be null`); + anode[prop] = node.accessibleNode; + is(anode[prop], node.accessibleNode, `anode.${prop} was assigned AccessibleNode`); + anode[prop] = null; + is(anode[prop], null, `anode.${prop} was assigned null`); + } + // Check that the WebIDL is as expected. + function checkImplementation(ifrDoc) { + let anode = ifrDoc.accessibleNode; + ok(anode, "DOM document has accessible node"); + + is(anode.computedRole, "document", "correct role of a document accessible node"); + is(anode.DOMNode, ifrDoc, "correct DOM Node of a document accessible node"); + + // States may differ depending on the document state, for example, if it is + // loaded or is loading still. + var states = null; + switch (anode.states.length) { + case 5: + states = [ + "readonly", "focusable", "opaque", "enabled", "sensitive", + ]; + break; + case 6: + states = [ + "readonly", "busy", "focusable", "opaque", "enabled", "sensitive", + ]; + break; + case 7: + states = [ + "readonly", "busy", "focusable", "opaque", "stale", "enabled", "sensitive", + ]; + break; + default: + ok(false, "Unexpected amount of states: " + JSON.stringify(anode.states)); + } + if (states) { + for (let i = 0; i < states.length; i++) { + is(anode.states[i], states[i], `${states[i]} state is expected at ${i}th index`); + } + } + + ok(anode.is("document", "focusable"), + "correct role and state on an accessible node"); + + is(anode.get("explicit-name"), "true", + "correct object attribute value on an accessible node"); + + ok(anode.has("explicit-name"), + "object attributes are present"); + + var attrs = [ "explicit-name" ]; + if (anode.attributes.length > 1) { + attrs = [ + "margin-left", "text-align", "text-indent", "margin-right", + "tag", "margin-top", "margin-bottom", "display", + "explicit-name", + ]; + } + + is(anode.attributes.length, attrs.length, "correct number of attributes"); + for (let i = 0; i < attrs.length; i++) { + ok(attrs.includes(anode.attributes[i]), + `${anode.attributes[i]} attribute is expected and found`); + } + + const strProps = ["autocomplete", "checked", "current", "hasPopUp", "invalid", + "keyShortcuts", "label", "live", "orientation", "placeholder", + "pressed", "relevant", "role", "roleDescription", "sort", + "valueText"]; + + for (const strProp of strProps) { + testStringProp(anode, strProp); + } + + const boolProps = ["atomic", "busy", "disabled", "expanded", "hidden", "modal", + "multiline", "multiselectable", "readOnly", "required", "selected"]; + + for (const boolProp of boolProps) { + testBoolProp(anode, boolProp); + } + + const doubleProps = ["valueMax", "valueMin", "valueNow"]; + + for (const doubleProp of doubleProps) { + testDoubleProp(anode, doubleProp); + } + + const intProps = ["colCount", "rowCount", "setSize"]; + + for (const intProp of intProps) { + testIntProp(anode, intProp); + } + + const uintProps = ["colIndex", "colSpan", "level", "posInSet", "rowIndex", "rowSpan"]; + + for (const uintProp of uintProps) { + testUIntProp(anode, uintProp); + } + + // Check if an AccessibleNode is properly cached. + let node = ifrDoc.createElement("div"); + anode = node.accessibleNode; + is(anode, node.accessibleNode, "an AccessibleNode is properly cached"); + + // Adopting node to another document doesn't change .accessibleNode + let anotherDoc = ifrDoc.implementation.createDocument("", "", null); + let adopted_node = anotherDoc.adoptNode(node); + is(anode, adopted_node.accessibleNode, "adopting node to another document doesn't change node.accessibleNode"); + + const relationProps = ["activeDescendant", "details", "errorMessage"]; + + for (const relationProp of relationProps) { + testRelationProp(anode, node, relationProp); + } + + finish(); + } + </script> +</head> diff --git a/accessible/tests/mochitest/attributes.js b/accessible/tests/mochitest/attributes.js new file mode 100644 index 0000000000..ebb5a54b85 --- /dev/null +++ b/accessible/tests/mochitest/attributes.js @@ -0,0 +1,516 @@ +/* import-globals-from common.js */ + +// ////////////////////////////////////////////////////////////////////////////// +// Object attributes. + +/** + * Test object attributes. + * + * @param aAccOrElmOrID [in] the accessible identifier + * @param aAttrs [in] the map of expected object attributes + * (name/value pairs) + * @param aSkipUnexpectedAttrs [in] points this function doesn't fail if + * unexpected attribute is encountered + * @param aTodo [in] true if this is a 'todo' + */ +function testAttrs(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs, aTodo) { + testAttrsInternal(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs, null, aTodo); +} + +/** + * Test object attributes that must not be present. + * + * @param aAccOrElmOrID [in] the accessible identifier + * @param aAbsentAttrs [in] map of attributes that should not be + * present (name/value pairs) + * @param aTodo [in] true if this is a 'todo' + */ +function testAbsentAttrs(aAccOrElmOrID, aAbsentAttrs, aTodo) { + testAttrsInternal(aAccOrElmOrID, {}, true, aAbsentAttrs, aTodo); +} + +/** + * Test object attributes that aren't right, but should be (todo) + * + * @param aAccOrElmOrID [in] the accessible identifier + * @param aKey [in] attribute name + * @param aExpectedValue [in] expected attribute value + */ +function todoAttr(aAccOrElmOrID, aKey, aExpectedValue) { + testAttrs( + aAccOrElmOrID, + Object.fromEntries([[aKey, aExpectedValue]]), + true, + true + ); +} + +/** + * Test CSS based object attributes. + */ +function testCSSAttrs(aID) { + var node = document.getElementById(aID); + var computedStyle = document.defaultView.getComputedStyle(node); + + var attrs = { + display: computedStyle.display, + "text-align": computedStyle.textAlign, + "text-indent": computedStyle.textIndent, + "margin-left": computedStyle.marginLeft, + "margin-right": computedStyle.marginRight, + "margin-top": computedStyle.marginTop, + "margin-bottom": computedStyle.marginBottom, + }; + testAttrs(aID, attrs, true); +} + +/** + * Test the accessible that it doesn't have CSS-based object attributes. + */ +function testAbsentCSSAttrs(aID) { + var attrs = { + display: "", + "text-align": "", + "text-indent": "", + "margin-left": "", + "margin-right": "", + "margin-top": "", + "margin-bottom": "", + }; + testAbsentAttrs(aID, attrs); +} + +/** + * Test group object attributes (posinset, setsize and level) and + * nsIAccessible::groupPosition() method. + * + * @param aAccOrElmOrID [in] the ID, DOM node or accessible + * @param aPosInSet [in] the value of 'posinset' attribute + * @param aSetSize [in] the value of 'setsize' attribute + * @param aLevel [in, optional] the value of 'level' attribute + */ +function testGroupAttrs(aAccOrElmOrID, aPosInSet, aSetSize, aLevel, aTodo) { + var acc = getAccessible(aAccOrElmOrID); + var levelObj = {}, + posInSetObj = {}, + setSizeObj = {}; + acc.groupPosition(levelObj, setSizeObj, posInSetObj); + + let groupPos = {}, + expectedGroupPos = {}; + + if (aPosInSet && aSetSize) { + groupPos.setsize = String(setSizeObj.value); + groupPos.posinset = String(posInSetObj.value); + + expectedGroupPos.setsize = String(aSetSize); + expectedGroupPos.posinset = String(aPosInSet); + } + + if (aLevel) { + groupPos.level = String(levelObj.value); + + expectedGroupPos.level = String(aLevel); + } + + compareSimpleObjects( + groupPos, + expectedGroupPos, + false, + "wrong groupPos", + aTodo + ); + + testAttrs(aAccOrElmOrID, expectedGroupPos, true, aTodo); +} + +function testGroupParentAttrs( + aAccOrElmOrID, + aChildItemCount, + aIsHierarchical, + aTodo +) { + testAttrs( + aAccOrElmOrID, + { "child-item-count": String(aChildItemCount) }, + true, + aTodo + ); + + if (aIsHierarchical) { + testAttrs(aAccOrElmOrID, { tree: "true" }, true, aTodo); + } else { + testAbsentAttrs(aAccOrElmOrID, { tree: "true" }); + } +} + +// ////////////////////////////////////////////////////////////////////////////// +// Text attributes. + +/** + * Test text attributes. + * + * @param aID [in] the ID of DOM element having text + * accessible + * @param aOffset [in] the offset inside text accessible to fetch + * text attributes + * @param aAttrs [in] the map of expected text attributes + * (name/value pairs) exposed at the offset + * @param aDefAttrs [in] the map of expected text attributes + * (name/value pairs) exposed on hyper text + * accessible + * @param aStartOffset [in] expected start offset where text attributes + * are applied + * @param aEndOffset [in] expected end offset where text attribute + * are applied + * @param aSkipUnexpectedAttrs [in] points the function doesn't fail if + * unexpected attribute is encountered + */ +function testTextAttrs( + aID, + aOffset, + aAttrs, + aDefAttrs, + aStartOffset, + aEndOffset, + aSkipUnexpectedAttrs +) { + var accessible = getAccessible(aID, [nsIAccessibleText]); + if (!accessible) { + return; + } + + var startOffset = { value: -1 }; + var endOffset = { value: -1 }; + + // do not include attributes exposed on hyper text accessible + var attrs = getTextAttributes( + aID, + accessible, + false, + aOffset, + startOffset, + endOffset + ); + + if (!attrs) { + return; + } + + var errorMsg = " for " + aID + " at offset " + aOffset; + + is(startOffset.value, aStartOffset, "Wrong start offset" + errorMsg); + is(endOffset.value, aEndOffset, "Wrong end offset" + errorMsg); + + compareAttrs(errorMsg, attrs, aAttrs, aSkipUnexpectedAttrs); + + // include attributes exposed on hyper text accessible + var expectedAttrs = {}; + for (let name in aAttrs) { + expectedAttrs[name] = aAttrs[name]; + } + + for (let name in aDefAttrs) { + if (!(name in expectedAttrs)) { + expectedAttrs[name] = aDefAttrs[name]; + } + } + + attrs = getTextAttributes( + aID, + accessible, + true, + aOffset, + startOffset, + endOffset + ); + + if (!attrs) { + return; + } + + compareAttrs(errorMsg, attrs, expectedAttrs, aSkipUnexpectedAttrs); +} + +/** + * Test default text attributes. + * + * @param aID [in] the ID of DOM element having text + * accessible + * @param aDefAttrs [in] the map of expected text attributes + * (name/value pairs) + * @param aSkipUnexpectedAttrs [in] points the function doesn't fail if + * unexpected attribute is encountered + */ +function testDefaultTextAttrs(aID, aDefAttrs, aSkipUnexpectedAttrs) { + var accessible = getAccessible(aID, [nsIAccessibleText]); + if (!accessible) { + return; + } + + var defAttrs = null; + try { + defAttrs = accessible.defaultTextAttributes; + } catch (e) {} + + if (!defAttrs) { + ok(false, "Can't get default text attributes for " + aID); + return; + } + + var errorMsg = ". Getting default text attributes for " + aID; + compareAttrs(errorMsg, defAttrs, aDefAttrs, aSkipUnexpectedAttrs); +} + +/** + * Test text attributes for wrong offset. + */ +function testTextAttrsWrongOffset(aID, aOffset) { + var res = false; + try { + var s = {}, + e = {}; + // Bug 1602031 + // eslint-disable-next-line no-undef + var acc = getAccessible(ID, [nsIAccessibleText]); + acc.getTextAttributes(false, 157, s, e); + } catch (ex) { + res = true; + } + + ok( + res, + "text attributes are calculated successfully at wrong offset " + + aOffset + + " for " + + prettyName(aID) + ); +} + +const kNormalFontWeight = function equalsToNormal(aWeight) { + return aWeight <= 400; +}; + +const kBoldFontWeight = function equalsToBold(aWeight) { + return aWeight > 400; +}; + +let isNNT = SpecialPowers.getBoolPref("widget.non-native-theme.enabled"); +// The pt font size of the input element can vary by Linux distro. +const kInputFontSize = + WIN || (MAC && isNNT) + ? "10pt" + : MAC + ? "8pt" + : function () { + return true; + }; + +const kAbsentFontFamily = function (aFontFamily) { + return aFontFamily != "sans-serif"; +}; +const kInputFontFamily = function (aFontFamily) { + return aFontFamily != "sans-serif"; +}; + +const kMonospaceFontFamily = function (aFontFamily) { + return aFontFamily != "monospace"; +}; +const kSansSerifFontFamily = function (aFontFamily) { + return aFontFamily != "sans-serif"; +}; +const kSerifFontFamily = function (aFontFamily) { + return aFontFamily != "serif"; +}; + +const kCursiveFontFamily = LINUX ? "DejaVu Serif" : "Comic Sans MS"; + +/** + * Return used font from the given computed style. + */ +function fontFamily(aComputedStyle) { + var name = aComputedStyle.fontFamily; + switch (name) { + case "monospace": + return kMonospaceFontFamily; + case "sans-serif": + return kSansSerifFontFamily; + case "serif": + return kSerifFontFamily; + default: + return name; + } +} + +/** + * Returns a computed system color for this document. + */ +function getSystemColor(aColor) { + let { r, g, b, a } = InspectorUtils.colorToRGBA(aColor, document); + return a == 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${a})`; +} + +/** + * Build an object of default text attributes expected for the given accessible. + * + * @param aID [in] identifier of accessible + * @param aFontSize [in] font size + * @param aFontWeight [in, optional] kBoldFontWeight or kNormalFontWeight, + * default value is kNormalFontWeight + */ +function buildDefaultTextAttrs(aID, aFontSize, aFontWeight, aFontFamily) { + var elm = getNode(aID); + var computedStyle = document.defaultView.getComputedStyle(elm); + var bgColor = + computedStyle.backgroundColor == "rgba(0, 0, 0, 0)" + ? getSystemColor("Canvas") + : computedStyle.backgroundColor; + + var defAttrs = { + "font-style": computedStyle.fontStyle, + "font-size": aFontSize, + "background-color": bgColor, + "font-weight": aFontWeight ? aFontWeight : kNormalFontWeight, + color: computedStyle.color, + "font-family": aFontFamily ? aFontFamily : fontFamily(computedStyle), + "text-position": computedStyle.verticalAlign, + }; + + return defAttrs; +} + +// ////////////////////////////////////////////////////////////////////////////// +// Private. + +function getTextAttributes( + aID, + aAccessible, + aIncludeDefAttrs, + aOffset, + aStartOffset, + aEndOffset +) { + // This function expects the passed in accessible to already be queried for + // nsIAccessibleText. + var attrs = null; + try { + attrs = aAccessible.getTextAttributes( + aIncludeDefAttrs, + aOffset, + aStartOffset, + aEndOffset + ); + } catch (e) {} + + if (attrs) { + return attrs; + } + + ok(false, "Can't get text attributes for " + aID); + return null; +} + +function testAttrsInternal( + aAccOrElmOrID, + aAttrs, + aSkipUnexpectedAttrs, + aAbsentAttrs, + aTodo +) { + var accessible = getAccessible(aAccOrElmOrID); + if (!accessible) { + return; + } + + var attrs = null; + try { + attrs = accessible.attributes; + } catch (e) {} + + if (!attrs) { + ok(false, "Can't get object attributes for " + prettyName(aAccOrElmOrID)); + return; + } + + var errorMsg = " for " + prettyName(aAccOrElmOrID); + compareAttrs( + errorMsg, + attrs, + aAttrs, + aSkipUnexpectedAttrs, + aAbsentAttrs, + aTodo + ); +} + +function compareAttrs( + aErrorMsg, + aAttrs, + aExpectedAttrs, + aSkipUnexpectedAttrs, + aAbsentAttrs, + aTodo +) { + // Check if all obtained attributes are expected and have expected value. + let attrObject = {}; + for (let prop of aAttrs.enumerate()) { + attrObject[prop.key] = prop.value; + } + + // Create expected attributes set by using the return values from + // embedded functions to determine the entry's value. + let expectedObj = Object.fromEntries( + Object.entries(aExpectedAttrs).map(([k, v]) => { + if (v instanceof Function) { + // If value is a function that returns true given the received + // attribute value, assign the attribute value to the entry. + // If it is false, stringify the function for good error reporting. + let value = v(attrObject[k]) ? attrObject[k] : v.toString(); + return [k, value]; + } + + return [k, v]; + }) + ); + + compareSimpleObjects( + attrObject, + expectedObj, + aSkipUnexpectedAttrs, + aErrorMsg, + aTodo + ); + + // Check if all unexpected attributes are absent. + if (aAbsentAttrs) { + let presentAttrs = Object.keys(attrObject).filter( + k => aAbsentAttrs[k] !== undefined + ); + if (presentAttrs.length) { + (aTodo ? todo : ok)( + false, + `There were unexpected attributes: ${presentAttrs}` + ); + } + } +} + +function compareSimpleObjects( + aObj, + aExpectedObj, + aSkipUnexpectedAttrs, + aMessage, + aTodo +) { + let keys = aSkipUnexpectedAttrs + ? Object.keys(aExpectedObj).sort() + : Object.keys(aObj).sort(); + let o1 = JSON.stringify(aObj, keys); + let o2 = JSON.stringify(aExpectedObj, keys); + + if (aTodo) { + todo_is(o1, o2, `${aMessage} - Got ${o1}, expected ${o2}`); + } else { + is(o1, o2, aMessage); + } +} diff --git a/accessible/tests/mochitest/attributes/a11y.ini b/accessible/tests/mochitest/attributes/a11y.ini new file mode 100644 index 0000000000..ab880e4fcb --- /dev/null +++ b/accessible/tests/mochitest/attributes/a11y.ini @@ -0,0 +1,14 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_dpub_aria_xml-roles.html] +[test_graphics_aria_xml-roles.html] +[test_listbox.html] +[test_obj.html] +[test_obj_css.html] +[test_obj_group.html] +[test_obj_group.xhtml] +[test_obj_group_tree.xhtml] +[test_tag.html] +[test_xml-roles.html] diff --git a/accessible/tests/mochitest/attributes/test_dpub_aria_xml-roles.html b/accessible/tests/mochitest/attributes/test_dpub_aria_xml-roles.html new file mode 100644 index 0000000000..a6b4dd4840 --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_dpub_aria_xml-roles.html @@ -0,0 +1,120 @@ +<!DOCTYPE html> +<html> +<head> + <title>XML roles tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + + function doTest() { + // DPub ARIA roles should be exposed via the xml-roles object attribute. + let dpub_attrs = [ + "doc-abstract", + "doc-acknowledgments", + "doc-afterword", + "doc-appendix", + "doc-backlink", + "doc-biblioentry", + "doc-bibliography", + "doc-biblioref", + "doc-chapter", + "doc-colophon", + "doc-conclusion", + "doc-cover", + "doc-credit", + "doc-credits", + "doc-dedication", + "doc-endnote", + "doc-endnotes", + "doc-epigraph", + "doc-epilogue", + "doc-errata", + "doc-example", + "doc-footnote", + "doc-foreword", + "doc-glossary", + "doc-glossref", + "doc-index", + "doc-introduction", + "doc-noteref", + "doc-notice", + "doc-pagebreak", + "doc-pagelist", + "doc-part", + "doc-preface", + "doc-prologue", + "doc-pullquote", + "doc-qna", + "doc-subtitle", + "doc-tip", + "doc-toc", + ]; + for (let attr of dpub_attrs) { + testAttrs(attr, {"xml-roles": attr}, true); + } + SimpleTest.finish(); + } + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1343537" + title="implement ARIA DPUB extension"> + Bug 1343537 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + <div id="doc-abstract" role="doc-abstract">abstract</div> + <div id="doc-acknowledgments" role="doc-acknowledgments">acknowledgments</div> + <div id="doc-afterword" role="doc-afterword">afterword</div> + <div id="doc-appendix" role="doc-appendix">appendix</div> + <div id="doc-backlink" role="doc-backlink">backlink</div> + <div id="doc-biblioentry" role="doc-biblioentry">biblioentry</div> + <div id="doc-bibliography" role="doc-bibliography">bibliography</div> + <div id="doc-biblioref" role="doc-biblioref">biblioref</div> + <div id="doc-chapter" role="doc-chapter">chapter</div> + <div id="doc-colophon" role="doc-colophon">colophon</div> + <div id="doc-conclusion" role="doc-conclusion">conclusion</div> + <div id="doc-cover" role="doc-cover">cover</div> + <div id="doc-credit" role="doc-credit">credit</div> + <div id="doc-credits" role="doc-credits">credits</div> + <div id="doc-dedication" role="doc-dedication">dedication</div> + <div id="doc-endnote" role="doc-endnote">endnote</div> + <div id="doc-endnotes" role="doc-endnotes">endnotes</div> + <div id="doc-epigraph" role="doc-epigraph">epigraph</div> + <div id="doc-epilogue" role="doc-epilogue">epilogue</div> + <div id="doc-errata" role="doc-errata">errata</div> + <div id="doc-example" role="doc-example">example</div> + <div id="doc-footnote" role="doc-footnote">footnote</div> + <div id="doc-foreword" role="doc-foreword">foreword</div> + <div id="doc-glossary" role="doc-glossary">glossary</div> + <div id="doc-glossref" role="doc-glossref">glossref</div> + <div id="doc-index" role="doc-index">index</div> + <div id="doc-introduction" role="doc-introduction">introduction</div> + <div id="doc-noteref" role="doc-noteref">noteref</div> + <div id="doc-notice" role="doc-notice">notice</div> + <div id="doc-pagebreak" role="doc-pagebreak">pagebreak</div> + <div id="doc-pagelist" role="doc-pagelist">pagelist</div> + <div id="doc-part" role="doc-part">part</div> + <div id="doc-preface" role="doc-preface">preface</div> + <div id="doc-prologue" role="doc-prologue">prologue</div> + <div id="doc-pullquote" role="doc-pullquote">pullquote</div> + <div id="doc-qna" role="doc-qna">qna</div> + <div id="doc-subtitle" role="doc-subtitle">subtitle</div> + <div id="doc-tip" role="doc-tip">tip</div> + <div id="doc-toc" role="doc-toc">toc</div> +</body> +</html> diff --git a/accessible/tests/mochitest/attributes/test_graphics_aria_xml-roles.html b/accessible/tests/mochitest/attributes/test_graphics_aria_xml-roles.html new file mode 100644 index 0000000000..45d5e2fa0b --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_graphics_aria_xml-roles.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> +<head> + <title>XML roles tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + + function doTest() { + // Graphics ARIA roles should be exposed via the xml-roles object attribute. + let graphics_attrs = [ + "graphics-document", + "graphics-object", + "graphics-symbol", + ]; + for (let attr of graphics_attrs) { + testAttrs(attr, {"xml-roles": attr}, true); + } + SimpleTest.finish(); + } + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432513" + title="implement ARIA Graphics roles"> + Bug 1432513 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + <div id="graphics-document" role="graphics-document">document</div> + <div id="graphics-object" role="graphics-object">object</div> + <div id="graphics-symbol" role="graphics-symbol">symbol</div> +</body> +</html> diff --git a/accessible/tests/mochitest/attributes/test_listbox.html b/accessible/tests/mochitest/attributes/test_listbox.html new file mode 100644 index 0000000000..5489e74b74 --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_listbox.html @@ -0,0 +1,82 @@ +<html> + +<head> + <title>Listbox group attribute tests</title> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + async function doTest() { + // First test the whole lot. + testGroupAttrs("a", 1, 6); + testGroupAttrs("b", 2, 6); + testGroupAttrs("c", 3, 6); + testGroupAttrs("d", 4, 6); + testGroupAttrs("e", 5, 6); + testGroupAttrs("f", 6, 6); + // Remove c, reducing the set to 5. + let listbox = getAccessible("listbox"); + let updated = waitForEvent(EVENT_REORDER, listbox); + c.remove(); + await updated; + testGroupAttrs("a", 1, 5); + testGroupAttrs("b", 2, 5); + testGroupAttrs("d", 3, 5); + testGroupAttrs("e", 4, 5); + testGroupAttrs("f", 5, 5); + // Now, remove the first element. + updated = waitForEvent(EVENT_REORDER, listbox); + a.remove(); + await updated; + testGroupAttrs("b", 1, 4); + testGroupAttrs("d", 2, 4); + testGroupAttrs("e", 3, 4); + testGroupAttrs("f", 4, 4); + // Remove the last item. + updated = waitForEvent(EVENT_REORDER, listbox); + f.remove(); + await updated; + testGroupAttrs("b", 1, 3); + testGroupAttrs("d", 2, 3); + testGroupAttrs("e", 3, 3); + // Finally, remove the middle item. + updated = waitForEvent(EVENT_REORDER, listbox); + d.remove(); + await updated; + testGroupAttrs("b", 1, 2); + testGroupAttrs("e", 2, 2); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- Group information updated after removal of list items, bug 1515186 --> + <div id="listbox" role="listbox"> + <div id="a" role="option">Option a</div> + <div id="b" role="option">Option b</div> + <div id="c" role="option">Option c</div> + <div id="d" role="option">Option d</div> + <div id="e" role="option">Option e</div> + <div id="f" role="option">Option f</div> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/attributes/test_obj.html b/accessible/tests/mochitest/attributes/test_obj.html new file mode 100644 index 0000000000..fedf6a1b7b --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_obj.html @@ -0,0 +1,292 @@ +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=475006 +https://bugzilla.mozilla.org/show_bug.cgi?id=391829 +https://bugzilla.mozilla.org/show_bug.cgi?id=581952 +https://bugzilla.mozilla.org/show_bug.cgi?id=558036 +--> +<head> + <title>Group attributes tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + function doTest() { + // aria + testAttrs("atomic", {"atomic": "true", "container-atomic": "true"}, true); + testAttrs(getNode("atomic").firstChild, {"container-atomic": "true"}, true); + testAbsentAttrs("atomic_false", {"atomic": "false", "container-atomic": "false"}); + testAbsentAttrs(getNode("atomic_false").firstChild, {"container-atomic": "false"}); + + testAttrs("autocomplete", {"autocomplete": "true"}, true); + testAttrs("checkbox", {"checkable": "true"}, true); + testAttrs("checkedCheckbox", {"checkable": "true"}, true); + testAbsentAttrs("checkedMenuitem", {"checkable": "true"}); + testAttrs("checkedMenuitemCheckbox", {"checkable": "true"}, true); + testAttrs("checkedMenuitemRadio", {"checkable": "true"}, true); + testAttrs("checkedOption", {"checkable": "true"}, true); + testAttrs("checkedRadio", {"checkable": "true"}, true); + testAttrs("checkedTreeitem", {"checkable": "true"}, true); + testAttrs("dropeffect", {"dropeffect": "copy"}, true); + testAttrs("grabbed", {"grabbed": "true"}, true); + testAttrs("haspopupTrue", { "haspopup": "true" }, true); + testAbsentAttrs("haspopupFalse", { "haspopup": "false" }); + testAbsentAttrs("haspopupEmpty", { "haspopup": "" }); + testAttrs("haspopupDialog", { "haspopup": "dialog" }, true); + testAttrs("haspopupListbox", { "haspopup": "listbox" }, true); + testAttrs("haspopupMenu", { "haspopup": "menu" }, true); + testAttrs("haspopupTree", { "haspopup": "tree" }, true); + testAbsentAttrs("modal", {"modal": "true"}); + testAttrs("sortAscending", {"sort": "ascending"}, true); + testAttrs("sortDescending", {"sort": "descending"}, true); + testAttrs("sortNone", {"sort": "none"}, true); + testAttrs("sortOther", {"sort": "other"}, true); + testAttrs("roledescr", {"roledescription": "spreadshit"}, true); + testAttrs("currentPage", {"current": "page"}, true); + testAttrs("currentStep", {"current": "step"}, true); + testAttrs("currentLocation", {"current": "location"}, true); + testAttrs("currentDate", {"current": "date"}, true); + testAttrs("currentTime", {"current": "time"}, true); + testAttrs("currentTrue", {"current": "true"}, true); + testAttrs("currentOther", {"current": "true"}, true); + testAbsentAttrs("currentFalse", {"current": "true"}); + testAttrs("currentSpan", {"current": "page"}, true); + + // live object attribute + + // HTML + testAttrs("output", {"live": "polite"}, true); + + // ARIA + testAttrs("live", {"live": "polite"}, true); + testAttrs("live2", {"live": "polite"}, true); + testAbsentAttrs("live3", {"live": ""}); + if (MAC) { + testAttrs("alert", {"live": "assertive"}, true); + } else { + testAbsentAttrs("alert", {"live": "assertive"}); + } + testAttrs("log", {"live": "polite"}, true); + testAttrs("logAssertive", {"live": "assertive"}, true); + testAttrs("marquee", {"live": "off"}, true); + testAttrs("status", {"live": "polite"}, true); + testAttrs("timer", {"live": "off"}, true); + testAbsentAttrs("tablist", {"live": "polite"}); + + // container-live object attribute + testAttrs("liveChild", {"container-live": "polite"}, true); + testAttrs("live2Child", {"container-live": "polite"}, true); + if (MAC) { + testAttrs("alertChild", {"container-live": "assertive"}, true); + } else { + testAbsentAttrs("alertChild", {"container-live": "assertive"}); + } + testAttrs("logChild", {"container-live": "polite"}, true); + testAttrs("logAssertiveChild", {"container-live": "assertive"}, true); + testAttrs("marqueeChild", {"container-live": "off"}, true); + testAttrs("statusChild", {"container-live": "polite"}, true); + testAttrs("timerChild", {"container-live": "off"}, true); + testAbsentAttrs("tablistChild", {"container-live": "polite"}); + testAttrs("containerLiveOutput", {"container-live": "polite"}, true); + testAttrs("containerLiveOutput1", {"container-live": "polite"}, true); + testAttrs("containerLiveOutput2", {"container-live": "polite"}, true); + + // container-live-role object attribute + testAttrs("log", {"container-live-role": "log"}, true); + testAttrs("logAssertive", {"container-live-role": "log"}, true); + testAttrs("marquee", {"container-live-role": "marquee"}, true); + testAttrs("status", {"container-live-role": "status"}, true); + testAttrs("timer", {"container-live-role": "timer"}, true); + testAttrs("logChild", {"container-live-role": "log"}, true); + testAttrs("logAssertive", {"container-live-role": "log"}, true); + testAttrs("logAssertiveChild", {"container-live-role": "log"}, true); + testAttrs("marqueeChild", {"container-live-role": "marquee"}, true); + testAttrs("statusChild", {"container-live-role": "status"}, true); + testAttrs("timerChild", {"container-live-role": "timer"}, true); + testAbsentAttrs("tablistChild", {"container-live-role": "tablist"}); + + // absent aria-label and aria-labelledby object attribute + testAbsentAttrs("label", {"label": "foo"}); + testAbsentAttrs("labelledby", {"labelledby": "label"}); + + // container that has no default live attribute + testAttrs("liveGroup", {"live": "polite"}, true); + testAttrs("liveGroupChild", {"container-live": "polite"}, true); + testAttrs("liveGroup", {"container-live-role": "group"}, true); + testAttrs("liveGroupChild", {"container-live-role": "group"}, true); + + // text input type + testAbsentAttrs("button", { "text-input-type": "button"}); + testAbsentAttrs("checkbox", { "text-input-type": "checkbox"}); + testAbsentAttrs("radio", { "text-input-type": "radio"}); + testAttrs("email", {"text-input-type": "email"}, true); + testAttrs("search", {"text-input-type": "search"}, true); + testAttrs("tel", {"text-input-type": "tel"}, true); + testAttrs("url", {"text-input-type": "url"}, true); + testAttrs("number", {"text-input-type": "number"}, true); + + // ARIA + testAttrs("searchbox", {"text-input-type": "search"}, true); + + // html + testAttrs("radio", {"checkable": "true"}, true); + testAttrs("checkbox", {"checkable": "true"}, true); + testAttrs("draggable", {"draggable": "true"}, true); + testAttrs("th1", { "abbr": "SS#" }, true); + testAttrs("th2", { "abbr": "SS#" }, true); + testAttrs("th2", { "axis": "social" }, true); + + // don't barf on an empty abbr element. + testAbsentAttrs("th3", { "abbr": "" }); + + // application accessible + if (WIN) { + var gfxInfo = Cc["@mozilla.org/gfx/info;1"]. + getService(Ci.nsIGfxInfo); + var attrs = { + "D2D": (gfxInfo.D2DEnabled ? "true" : "false"), + }; + testAttrs(getApplicationAccessible(), attrs, false); + } + + // no object attributes + testAbsentAttrs(getAccessible("listitem").firstChild, { "tag": "" }); + + // experimental aria + testAttrs("experimental", {"blah": "true"}, true); + + // HTML5 aside element xml-roles + testAttrs("aside0", {"xml-roles": "note"}, true); + testAttrs("aside1", {"xml-roles": "group"}, true); + testAttrs("aside2", {"xml-roles": "complementary"}, true); + + // non-standard data-at-shortcutkeys attribute: + testAttrs("shortcuts", {'data-at-shortcutkeys': '{"n":"New message","r":"Reply to message"}'}, true); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- container live --> + <output id="containerLiveOutput"><div id="containerLiveOutput1"><div id="containerLiveOutput2">Test</div></div></output> + + <!-- aria --> + <div id="atomic" aria-atomic="true">live region</div> + <div id="atomic_false" aria-atomic="false">live region</div> + <div id="autocomplete" role="textbox" aria-autocomplete="true"></div> + <div id="checkbox" role="checkbox"></div> + <div id="checkedCheckbox" role="checkbox" aria-checked="true"></div> + <div id="checkedMenuitem" role="menuitem" aria-checked="true"></div> + <div id="checkedMenuitemCheckbox" role="menuitemcheckbox" aria-checked="true"></div> + <div id="checkedMenuitemRadio" role="menuitemradio" aria-checked="true"></div> + <div id="checkedOption" role="option" aria-checked="true"></div> + <div id="checkedRadio" role="radio" aria-checked="true"></div> + <div id="checkedTreeitem" role="treeitem" aria-checked="true"></div> + <div id="dropeffect" aria-dropeffect="copy"></div> + <div id="grabbed" aria-grabbed="true"></div> + <div id="haspopupTrue" aria-haspopup="true"></div> + <div id="haspopupFalse" aria-haspopup="false"></div> + <div id="haspopupEmpty" aria-haspopup=""></div> + <div id="haspopupDialog" aria-haspopup="dialog"></div> + <div id="haspopupListbox" aria-haspopup="listbox"></div> + <div id="haspopupMenu" aria-haspopup="menu"></div> + <div id="haspopupTree" aria-haspopup="tree"></div> + <div id="modal" aria-modal="true"></div> + <div id="sortAscending" role="columnheader" aria-sort="ascending"></div> + <div id="sortDescending" role="columnheader" aria-sort="descending"></div> + <div id="sortNone" role="columnheader" aria-sort="none"></div> + <div id="sortOther" role="columnheader" aria-sort="other"></div> + <div id="roledescr" aria-roledescription="spreadshit"></div> + <div id="currentPage" aria-current="page"></div> + <div id="currentStep" aria-current="step"></div> + <div id="currentLocation" aria-current="location"></div> + <div id="currentDate" aria-current="date"></div> + <div id="currentTime" aria-current="time"></div> + <div id="currentTrue" aria-current="true"></div> + <div id="currentOther" aria-current="other"></div> + <div id="currentFalse" aria-current="false"></div> + + <!-- aria-current on a span which must create an accessible --> + <ol> + <li><a href="...">Page 1</a></li> + <li><a href="...">Page 2</a></li> + <li><span id="currentSpan" aria-current="page">This page</span></li> + </ol> + + <!-- html --> + <output id="output"></output> + + <!-- back to aria --> + <div id="live" aria-live="polite">excuse <div id="liveChild">me</div></div> + <div id="live2" role="marquee" aria-live="polite">excuse <div id="live2Child">me</div></div> + <div id="live3" role="region">excuse</div> + <div id="alert" role="alert">excuse <div id="alertChild">me</div></div> + <div id="log" role="log">excuse <div id="logChild">me</div></div> + <div id="logAssertive" role="log" aria-live="assertive">excuse <div id="logAssertiveChild">me</div></div> + <div id="marquee" role="marquee">excuse <div id="marqueeChild">me</div></div> + <div id="status" role="status">excuse <div id="statusChild">me</div></div> + <div id="tablist" role="tablist">tablist <div id="tablistChild">tab</div></div> + <div id="timer" role="timer">excuse <div id="timerChild">me</div></div> + + <!-- aria-label[ledby] should not be an object attribute --> + <div id="label" role="checkbox" aria-label="foo"></div> + <div id="labelledby" role="checkbox" aria-labelledby="label"></div> + + <!-- unusual live case --> + <div id="liveGroup" role="group" aria-live="polite"> + excuse <div id="liveGroupChild">me</div> + </div> + + <!-- text input type --> + <input id="button" type="button"/> + <input id="email" type="email"/> + <input id="search" type="search"/> + <input id="tel" type="tel"/> + <input id="url" type="url"/> + <input id="number" type="number"/> + <div id="searchbox" role="searchbox"></div> + + <!-- html --> + <input id="radio" type="radio"/> + <input id="checkbox" type="checkbox"/> + <div id="draggable" draggable="true">Draggable div</div> + <table> + <tr> + <th id="th1"><abbr title="Social Security Number">SS#</abbr></th> + <th id="th2" abbr="SS#" axis="social">Social Security Number</th> + <th id="th3"><abbr></abbr></th> + </tr> + </table> + + <ul> + <li id="listitem">item + </ul> + + <!-- experimental aria --> + <div id="experimental" aria-blah="true">Fake beer</div> + + <!-- HTML5 aside elements --> + <aside id="aside0" role="note">aside 0</aside> + <aside id="aside1" role="group">aside 1</aside> + <aside id="aside2">aside 2</aside> + + <!-- Shortcuts for web applications --> + <div id="shortcuts" data-at-shortcutkeys='{"n":"New message","r":"Reply to message"}'></div> +</body> +</html> diff --git a/accessible/tests/mochitest/attributes/test_obj_css.html b/accessible/tests/mochitest/attributes/test_obj_css.html new file mode 100644 index 0000000000..6c702ba5a6 --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_obj_css.html @@ -0,0 +1,225 @@ +<html> +<head> + <title>CSS-like attributes tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + var gQueue = null; + + function removeElm(aID) { + this.node = getNode(aID); + this.accessible = getAccessible(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.accessible), + ]; + + this.invoke = function removeElm_invoke() { + this.node.remove(); + }; + + this.check = function removeElm_check() { + testAbsentCSSAttrs(this.accessible); + }; + + this.getID = function removeElm_getID() { + return "test CSS-based attributes on removed accessible"; + }; + } + + function doTest() { + // CSS display + testCSSAttrs("display_block"); + testCSSAttrs("display_inline"); + testCSSAttrs("display_inline-block"); + testCSSAttrs("display_list-item"); + testCSSAttrs("display_table"); + testCSSAttrs("display_inline-table"); + testCSSAttrs("display_table-row-group"); + testCSSAttrs("display_table-column"); + testCSSAttrs("display_table-column-group"); + testCSSAttrs("display_table-header-group"); + testCSSAttrs("display_table-footer-group"); + testCSSAttrs("display_table-row"); + testCSSAttrs("display_table-cell"); + testCSSAttrs("display_table-caption"); + + // CSS text-align + testCSSAttrs("text-align_left"); + testCSSAttrs("text-align_right"); + testCSSAttrs("text-align_center"); + testCSSAttrs("text-align_justify"); + testCSSAttrs("text-align_inherit"); + + // CSS text-indent + testCSSAttrs("text-indent_em"); + testCSSAttrs("text-indent_ex"); + testCSSAttrs("text-indent_in"); + testCSSAttrs("text-indent_cm"); + testCSSAttrs("text-indent_mm"); + testCSSAttrs("text-indent_pt"); + testCSSAttrs("text-indent_pc"); + testCSSAttrs("text-indent_px"); + testCSSAttrs("text-indent_percent"); + testCSSAttrs("text-indent_inherit"); + + // CSS margin + testCSSAttrs("margin_em"); + testCSSAttrs("margin_ex"); + testCSSAttrs("margin_in"); + testCSSAttrs("margin_cm"); + testCSSAttrs("margin_mm"); + testCSSAttrs("margin_pt"); + testCSSAttrs("margin_pc"); + testCSSAttrs("margin_px"); + testCSSAttrs("margin_percent"); + testCSSAttrs("margin_auto"); + testCSSAttrs("margin_inherit"); + + testCSSAttrs("margin-left"); + testCSSAttrs("margin-right"); + testCSSAttrs("margin-top"); + testCSSAttrs("margin-bottom"); + + // Elements + testCSSAttrs("span"); + testCSSAttrs("div"); + testCSSAttrs("p"); + testCSSAttrs("input"); + testCSSAttrs("table"); + testCSSAttrs("tr"); + testCSSAttrs("td"); + + // no CSS-based object attributes + testAbsentCSSAttrs(getAccessible("listitem").firstChild); + + gQueue = new eventQueue(); + gQueue.push(new removeElm("div")); + gQueue.invoke(); // SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=439566" + title="Include the css display property as an IAccessible2 object attribute"> + Mozilla Bug 439566 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=460932" + title="text-indent and text-align should really be object attribute"> + Mozilla Bug 460932 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=689540" + title="Expose IA2 margin- object attributes"> + Mozilla Bug 689540 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=714579" + title="Don't use GetComputedStyle for object attribute calculation"> + Mozilla Bug 714579 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=729831" + title="Don't expose CSS-based object attributes on not in tree accessible and accessible having no DOM element"> + Mozilla Bug 729831 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="display_block" role="img" + style="display: block;">display: block</div> + <div id="display_inline" role="img" + style="display: inline;">display: inline</div> + <div id="display_inline-block" role="img" + style="display: inline-block;">display: inline-block</div> + <div id="display_list-item" role="img" + style="display: list-item;">display: list-item</div> + <div id="display_table" role="img" + style="display: table;">display: table</div> + <div id="display_inline-table" role="img" + style="display: inline-table;">display: inline-table</div> + <div id="display_table-row-group" role="img" + style="display: table-row-group;">display: table-row-group</div> + <div id="display_table-column" role="img" + style="display: table-column;">display: table-column</div> + <div id="display_table-column-group" role="img" + style="display: table-column-group;">display: table-column-group</div> + <div id="display_table-header-group" role="img" + style="display: table-header-group;">display: table-header-group</div> + <div id="display_table-footer-group" role="img" + style="display: table-footer-group;">display: table-footer-group</div> + <div id="display_table-row" role="img" + style="display: table-row;">display: table-row</div> + <div id="display_table-cell" role="img" + style="display: table-cell;">display: table-cell</div> + <div id="display_table-caption" role="img" + style="display: table-caption;">display: table-caption</div> + + <p id="text-align_left" style="text-align: left;">text-align: left</p> + <p id="text-align_right" style="text-align: right;">text-align: right</p> + <p id="text-align_center" style="text-align: center;">text-align: center</p> + <p id="text-align_justify" style="text-align: justify;">text-align: justify</p> + <p id="text-align_inherit" style="text-align: inherit;">text-align: inherit</p> + + <p id="text-indent_em" style="text-indent: 0.5em;">text-indent: 0.5em</p> + <p id="text-indent_ex" style="text-indent: 1ex;">text-indent: 1ex</p> + <p id="text-indent_in" style="text-indent: 0.5in;">text-indent: 0.5in</p> + <p id="text-indent_cm" style="text-indent: 2cm;">text-indent: 2cm</p> + <p id="text-indent_mm" style="text-indent: 10mm;">text-indent: 10mm</p> + <p id="text-indent_pt" style="text-indent: 30pt;">text-indent: 30pt</p> + <p id="text-indent_pc" style="text-indent: 2pc;">text-indent: 2pc</p> + <p id="text-indent_px" style="text-indent: 5px;">text-indent: 5px</p> + <p id="text-indent_percent" style="text-indent: 10%;">text-indent: 10%</p> + <p id="text-indent_inherit" style="text-indent: inherit;">text-indent: inherit</p> + + <p id="margin_em" style="margin: 0.5em;">margin: 0.5em</p> + <p id="margin_ex" style="margin: 1ex;">margin: 1ex</p> + <p id="margin_in" style="margin: 0.5in;">margin: 0.5in</p> + <p id="margin_cm" style="margin: 2cm;">margin: 2cm</p> + <p id="margin_mm" style="margin: 10mm;">margin: 10mm</p> + <p id="margin_pt" style="margin: 30pt;">margin: 30pt</p> + <p id="margin_pc" style="margin: 2pc;">margin: 2pc</p> + <p id="margin_px" style="margin: 5px;">margin: 5px</p> + <p id="margin_percent" style="margin: 10%;">margin: 10%</p> + <p id="margin_auto" style="margin: auto;">margin: auto</p> + <p id="margin_inherit" style="margin: inherit;">margin: inherit</p> + + <p id="margin-left" style="margin-left: 11px;">margin-left: 11px</p> + <p id="margin-right" style="margin-right: 21px;">margin-right</p> + <p id="margin-top" style="margin-top: 31px;">margin-top: 31px</p> + <p id="margin-bottom" style="margin-bottom: 41px;">margin-bottom: 41px</p> + + <span id="span" role="group">It's span</span> + <div id="div">It's div</div> + <p id="p">It's paragraph"</p> + <input id="input"/> + <table id="table" style="margin: 2px; text-align: center; text-indent: 10%;"> + <tr id="tr" role="group"> + <td id="td">td</td> + </tr> + </table> + + <ul> + <li id="listitem">item + </ul> +</body> +</html> diff --git a/accessible/tests/mochitest/attributes/test_obj_group.html b/accessible/tests/mochitest/attributes/test_obj_group.html new file mode 100644 index 0000000000..f245d485c7 --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_obj_group.html @@ -0,0 +1,564 @@ +<html> + +<head> + <title>Group attributes tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // HTML select with no size attribute. + testGroupAttrs("opt1-nosize", 1, 4); + testGroupAttrs("opt2-nosize", 2, 4); + testGroupAttrs("opt3-nosize", 3, 4); + testGroupAttrs("opt4-nosize", 4, 4); + + // Container should have item count and not hierarchical + testGroupParentAttrs(getAccessible("opt1-nosize").parent, 4, false); + + // //////////////////////////////////////////////////////////////////////// + // HTML select + testGroupAttrs("opt1", 1, 2); + testGroupAttrs("opt2", 2, 2); + + // //////////////////////////////////////////////////////////////////////// + // HTML select with optgroup + testGroupAttrs("select2_opt3", 1, 2, 1); + testGroupAttrs("select2_opt4", 2, 2, 1); + testGroupAttrs("select2_opt1", 1, 2, 2); + testGroupAttrs("select2_opt2", 2, 2, 2); + + // //////////////////////////////////////////////////////////////////////// + // HTML input@type="radio" within form + testGroupAttrs("radio1", 1, 2); + testGroupAttrs("radio2", 2, 2); + + // //////////////////////////////////////////////////////////////////////// + // HTML input@type="radio" within document + testGroupAttrs("radio3", 1, 2); + testGroupAttrs("radio4", 2, 2); + + // //////////////////////////////////////////////////////////////////////// + // Hidden HTML input@type="radio" + testGroupAttrs("radio5", 1, 1); + + // //////////////////////////////////////////////////////////////////////// + // HTML ul/ol + testGroupAttrs("li1", 1, 3); + testGroupAttrs("li2", 2, 3); + testGroupAttrs("li3", 3, 3); + + // ul should have item count and not hierarchical + testGroupParentAttrs("ul", 3, false); + + // //////////////////////////////////////////////////////////////////////// + // HTML ul/ol (nested lists) + + testGroupAttrs("li4", 1, 3, 1); + testGroupAttrs("li5", 2, 3, 1); + testGroupAttrs("li6", 3, 3, 1); + // ol with nested list should have 1st level item count and be hierarchical + testGroupParentAttrs("ol", 3, true); + + testGroupAttrs("n_li4", 1, 3, 2); + testGroupAttrs("n_li5", 2, 3, 2); + testGroupAttrs("n_li6", 3, 3, 2); + // nested ol should have item count and be hierarchical + testGroupParentAttrs("ol_nested", 3, true); + + // //////////////////////////////////////////////////////////////////////// + // ARIA list + testGroupAttrs("li7", 1, 3); + testGroupAttrs("li8", 2, 3); + testGroupAttrs("li9", 3, 3); + // simple flat aria list + testGroupParentAttrs("aria-list_1", 3, false); + + // //////////////////////////////////////////////////////////////////////// + // ARIA list (nested lists: list -> listitem -> list -> listitem) + testGroupAttrs("li10", 1, 3, 1); + testGroupAttrs("li11", 2, 3, 1); + testGroupAttrs("li12", 3, 3, 1); + // aria list with nested list + testGroupParentAttrs("aria-list_2", 3, true); + + testGroupAttrs("n_li10", 1, 3, 2); + testGroupAttrs("n_li11", 2, 3, 2); + testGroupAttrs("n_li12", 3, 3, 2); + // nested aria list. + testGroupParentAttrs("aria-list_2_1", 3, true); + + // //////////////////////////////////////////////////////////////////////// + // ARIA list (nested lists: list -> listitem -> group -> listitem) + testGroupAttrs("lgt_li1", 1, 2, 1); + testGroupAttrs("lgt_li1_nli1", 1, 2, 2); + testGroupAttrs("lgt_li1_nli2", 2, 2, 2); + testGroupAttrs("lgt_li2", 2, 2, 1); + testGroupAttrs("lgt_li2_nli1", 1, 2, 2); + testGroupAttrs("lgt_li2_nli2", 2, 2, 2); + // aria list with nested list + testGroupParentAttrs("aria-list_3", 2, true); + + // //////////////////////////////////////////////////////////////////////// + // ARIA menu (menuitem, separator, menuitemradio and menuitemcheckbox) + testGroupAttrs("menu_item1", 1, 2); + testGroupAttrs("menu_item2", 2, 2); + testGroupAttrs("menu_item1.1", 1, 2); + testGroupAttrs("menu_item1.2", 2, 2); + testGroupAttrs("menu_item1.3", 1, 3); + testGroupAttrs("menu_item1.4", 2, 3); + testGroupAttrs("menu_item1.5", 3, 3); + // menu bar item count + testGroupParentAttrs("menubar", 2, false); + // Bug 1492529. Menu should have total number of items 5 from both sets, + // but only has the first 2 item set. + todoAttr("menu", "child-item-count", "5"); + + // //////////////////////////////////////////////////////////////////////// + // ARIA tab + testGroupAttrs("tab_1", 1, 3); + testGroupAttrs("tab_2", 2, 3); + testGroupAttrs("tab_3", 3, 3); + // tab list tab count + testGroupParentAttrs("tablist_1", 3, false); + + // //////////////////////////////////////////////////////////////////////// + // ARIA radio + testGroupAttrs("r1", 1, 3); + testGroupAttrs("r2", 2, 3); + testGroupAttrs("r3", 3, 3); + // explicit aria radio group + testGroupParentAttrs("rg1", 3, false); + + // //////////////////////////////////////////////////////////////////////// + // ARIA tree + testGroupAttrs("ti1", 1, 3, 1); + testGroupAttrs("ti2", 1, 2, 2); + testGroupAttrs("ti3", 2, 2, 2); + testGroupAttrs("ti4", 2, 3, 1); + testGroupAttrs("ti5", 1, 3, 2); + testGroupAttrs("ti6", 2, 3, 2); + testGroupAttrs("ti7", 3, 3, 2); + testGroupAttrs("ti8", 3, 3, 1); + testGroupParentAttrs("tree_1", 3, true); + + // //////////////////////////////////////////////////////////////////////// + // ARIA tree (tree -> treeitem -> group -> treeitem) + testGroupAttrs("tree2_ti1", 1, 2, 1); + testGroupAttrs("tree2_ti1a", 1, 2, 2); + testGroupAttrs("tree2_ti1b", 2, 2, 2); + testGroupAttrs("tree2_ti2", 2, 2, 1); + testGroupAttrs("tree2_ti2a", 1, 2, 2); + testGroupAttrs("tree2_ti2b", 2, 2, 2); + testGroupParentAttrs("tree_2", 2, true); + + // //////////////////////////////////////////////////////////////////////// + // ARIA tree (tree -> treeitem, group -> treeitem) + testGroupAttrs("tree3_ti1", 1, 2, 1); + testGroupAttrs("tree3_ti1a", 1, 2, 2); + testGroupAttrs("tree3_ti1b", 2, 2, 2); + testGroupAttrs("tree3_ti2", 2, 2, 1); + testGroupAttrs("tree3_ti2a", 1, 2, 2); + testGroupAttrs("tree3_ti2b", 2, 2, 2); + testGroupParentAttrs("tree_3", 2, true); + + // //////////////////////////////////////////////////////////////////////// + // ARIA grid + testGroupAttrs("grid_row1", 1, 2); + testAbsentAttrs("grid_cell1", {"posinset": "", "setsize": ""}); + testAbsentAttrs("grid_cell2", {"posinset": "", "setsize": ""}); + + testGroupAttrs("grid_row2", 2, 2); + testAbsentAttrs("grid_cell3", {"posinset": "", "setsize": ""}); + testAbsentAttrs("grid_cell4", {"posinset": "", "setsize": ""}); + testGroupParentAttrs("grid", 2, false); + + // //////////////////////////////////////////////////////////////////////// + // ARIA treegrid + testGroupAttrs("treegrid_row1", 1, 2, 1); + testAbsentAttrs("treegrid_cell1", {"posinset": "", "setsize": ""}); + testAbsentAttrs("treegrid_cell2", {"posinset": "", "setsize": ""}); + + testGroupAttrs("treegrid_row2", 1, 1, 2); + testAbsentAttrs("treegrid_cell3", {"posinset": "", "setsize": ""}); + testAbsentAttrs("treegrid_cell4", {"posinset": "", "setsize": ""}); + + testGroupAttrs("treegrid_row3", 2, 2, 1); + testAbsentAttrs("treegrid_cell5", {"posinset": "", "setsize": ""}); + testAbsentAttrs("treegrid_cell6", {"posinset": "", "setsize": ""}); + + testGroupParentAttrs("treegrid", 2, true); + // row child item count provided by parent grid's aria-colcount + testGroupParentAttrs("treegrid_row1", 4, false); + + // //////////////////////////////////////////////////////////////////////// + // HTML headings + testGroupAttrs("h1", 0, 0, 1); + testGroupAttrs("h2", 0, 0, 2); + testGroupAttrs("h3", 0, 0, 3); + testGroupAttrs("h4", 0, 0, 4); + testGroupAttrs("h5", 0, 0, 5); + testGroupAttrs("h6", 0, 0, 6); + testGroupAttrs("ariaHeadingNoLevel", 0, 0, 2); + // No child item counts or "tree" flag for parent of headings + testAbsentAttrs("headings", {"child-item-count": "", "tree": ""}); + + // //////////////////////////////////////////////////////////////////////// + // ARIA combobox + testGroupAttrs("combo1_opt1", 1, 4); + testGroupAttrs("combo1_opt2", 2, 4); + testGroupAttrs("combo1_opt3", 3, 4); + testGroupAttrs("combo1_opt4", 4, 4); + testGroupParentAttrs("combo1", 4, false); + + // //////////////////////////////////////////////////////////////////////// + // ARIA table + testGroupAttrs("table_cell", 3, 4); + testGroupAttrs("table_row", 2, 2); + + // grid child item count provided by aria-rowcount + testGroupParentAttrs("table", 2, false); + // row child item count provided by parent grid's aria-colcount + testGroupParentAttrs("table_row", 4, false); + + // Attributes calculated even when row is wrapped in a div. + testGroupAttrs("wrapped_row_1", 1, 2); + testGroupAttrs("wrapped_row_2", 2, 2); + + // //////////////////////////////////////////////////////////////////////// + // ARIA list constructed by ARIA owns + testGroupAttrs("t1_li1", 1, 3); + testGroupAttrs("t1_li2", 2, 3); + testGroupAttrs("t1_li3", 3, 3); + testGroupParentAttrs("aria-list_4", 3, false); + + // Test group attributes of ARIA comments + testGroupAttrs("comm_single_1", 1, 2, 1); + testGroupAttrs("comm_single_2", 2, 2, 1); + testGroupAttrs("comm_nested_1", 1, 3, 1); + testGroupAttrs("comm_nested_1_1", 1, 2, 2); + testGroupAttrs("comm_nested_1_2", 2, 2, 2); + testGroupAttrs("comm_nested_2", 2, 3, 1); + testGroupAttrs("comm_nested_2_1", 1, 1, 2); + testGroupAttrs("comm_nested_2_1_1", 1, 1, 3); + testGroupAttrs("comm_nested_3", 3, 3, 1); + + // Test that group position information updates after deleting node. + testGroupAttrs("tree4_ti1", 1, 2, 1); + testGroupAttrs("tree4_ti2", 2, 2, 1); + testGroupParentAttrs("tree4", 2, true); + + var tree4element = document.getElementById("tree4_ti1"); + var tree4acc = getAccessible("tree4"); + tree4element.remove(); + waitForEvent(EVENT_REORDER, tree4acc, function() { + testGroupAttrs("tree4_ti2", 1, 1, 1); + testGroupParentAttrs("tree4", 1, true); + SimpleTest.finish(); + }); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=468418" + title="Expose level for nested lists in HTML"> + Mozilla Bug 468418 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=844023" + title="group info might not be properly updated when flat trees mutate"> + Bug 844023 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=864224" + title="Support nested ARIA listitems structured by role='group'"> + Bug 864224 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=907682" + title=" HTML:option group position is not correct when select is collapsed"> + Mozilla Bug 907682 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select> + <option id="opt1-nosize">option1</option> + <option id="opt2-nosize">option2</option> + <option id="opt3-nosize">option3</option> + <option id="opt4-nosize">option4</option> + </select> + + <select size="4"> + <option id="opt1">option1</option> + <option id="opt2">option2</option> + </select> + + <select size="4"> + <optgroup id="select2_optgroup" label="group"> + <option id="select2_opt1">option1</option> + <option id="select2_opt2">option2</option> + </optgroup> + <option id="select2_opt3">option3</option> + <option id="select2_opt4">option4</option> + </select> + + <form> + <input type="radio" id="radio1" name="group1"/> + <input type="radio" id="radio2" name="group1"/> + </form> + + <input type="radio" id="radio3" name="group2"/> + <input type="radio" id="radio4" name="group2"/> + + <ul id="ul"> + <li id="li1">Oranges</li> + <li id="li2">Apples</li> + <li id="li3">Bananas</li> + </ul> + + <ol id="ol"> + <li id="li4">Oranges</li> + <li id="li5">Apples</li> + <li id="li6">Bananas + <ul id="ol_nested"> + <li id="n_li4">Oranges</li> + <li id="n_li5">Apples</li> + <li id="n_li6">Bananas</li> + </ul> + </li> + </ol> + + <span role="list" id="aria-list_1"> + <span role="listitem" id="li7">Oranges</span> + <span role="listitem" id="li8">Apples</span> + <span role="listitem" id="li9">Bananas</span> + </span> + + <span role="list" id="aria-list_2"> + <span role="listitem" id="li10">Oranges</span> + <span role="listitem" id="li11">Apples</span> + <span role="listitem" id="li12">Bananas + <span role="list" id="aria-list_2_1"> + <span role="listitem" id="n_li10">Oranges</span> + <span role="listitem" id="n_li11">Apples</span> + <span role="listitem" id="n_li12">Bananas</span> + </span> + </span> + </span> + + <div role="list" id="aria-list_3"> + <div role="listitem" id="lgt_li1">Item 1 + <div role="group"> + <div role="listitem" id="lgt_li1_nli1">Item 1A</div> + <div role="listitem" id="lgt_li1_nli2">Item 1B</div> + </div> + </div> + <div role="listitem" id="lgt_li2">Item 2 + <div role="group"> + <div role="listitem" id="lgt_li2_nli1">Item 2A</div> + <div role="listitem" id="lgt_li2_nli2">Item 2B</div> + </div> + </div> + </div> + + <ul role="menubar" id="menubar"> + <li role="menuitem" aria-haspopup="true" id="menu_item1">File + <ul role="menu" id="menu"> + <li role="menuitem" id="menu_item1.1">New</li> + <li role="menuitem" id="menu_item1.2">Open…</li> + <li role="separator">-----</li> + <li role="menuitem" id="menu_item1.3">Item</li> + <li role="menuitemradio" id="menu_item1.4">Radio</li> + <li role="menuitemcheckbox" id="menu_item1.5">Checkbox</li> + </ul> + </li> + <li role="menuitem" aria-haspopup="false" id="menu_item2">Help</li> + </ul> + + <ul id="tablist_1" role="tablist"> + <li id="tab_1" role="tab">Crust</li> + <li id="tab_2" role="tab">Veges</li> + <li id="tab_3" role="tab">Carnivore</li> + </ul> + + <ul id="rg1" role="radiogroup"> + <li id="r1" role="radio" aria-checked="false">Thai</li> + <li id="r2" role="radio" aria-checked="false">Subway</li> + <li id="r3" role="radio" aria-checked="false">Jimmy Johns</li> + </ul> + + <table role="tree" id="tree_1"> + <tr role="presentation"> + <td role="treeitem" aria-expanded="true" aria-level="1" + id="ti1">vegetables</td> + </tr> + <tr role="presentation"> + <td role="treeitem" aria-level="2" id="ti2">cucumber</td> + </tr> + <tr role="presentation"> + <td role="treeitem" aria-level="2" id="ti3">carrot</td> + </tr> + <tr role="presentation"> + <td role="treeitem" aria-expanded="false" aria-level="1" + id="ti4">cars</td> + </tr> + <tr role="presentation"> + <td role="treeitem" aria-level="2" id="ti5">mercedes</td> + </tr> + <tr role="presentation"> + <td role="treeitem" aria-level="2" id="ti6">BMW</td> + </tr> + <tr role="presentation"> + <td role="treeitem" aria-level="2" id="ti7">Audi</td> + </tr> + <tr role="presentation"> + <td role="treeitem" aria-level="1" id="ti8">people</td> + </tr> + </table> + + <ul role="tree" id="tree_2"> + <li role="treeitem" id="tree2_ti1">Item 1 + <ul role="group"> + <li role="treeitem" id="tree2_ti1a">Item 1A</li> + <li role="treeitem" id="tree2_ti1b">Item 1B</li> + </ul> + </li> + <li role="treeitem" id="tree2_ti2">Item 2 + <ul role="group"> + <li role="treeitem" id="tree2_ti2a">Item 2A</li> + <li role="treeitem" id="tree2_ti2b">Item 2B</li> + </ul> + </li> + </div> + + <div role="tree" id="tree_3"> + <div role="treeitem" id="tree3_ti1">Item 1</div> + <div role="group"> + <li role="treeitem" id="tree3_ti1a">Item 1A</li> + <li role="treeitem" id="tree3_ti1b">Item 1B</li> + </div> + <div role="treeitem" id="tree3_ti2">Item 2</div> + <div role="group"> + <div role="treeitem" id="tree3_ti2a">Item 2A</div> + <div role="treeitem" id="tree3_ti2b">Item 2B</div> + </div> + </div> + + <!-- IMPORTANT: Need to have no whitespace between elements in this tree. --> + <div role="tree" id="tree4"><div role="treeitem" + id="tree4_ti1">Item 1</div><div role="treeitem" + id="tree4_ti2">Item 2</div></div> + + <table role="grid" id="grid"> + <tr role="row" id="grid_row1"> + <td role="gridcell" id="grid_cell1">cell1</td> + <td role="gridcell" id="grid_cell2">cell2</td> + </tr> + <tr role="row" id="grid_row2"> + <td role="gridcell" id="grid_cell3">cell3</td> + <td role="gridcell" id="grid_cell4">cell4</td> + </tr> + </table> + + <div role="treegrid" id="treegrid" aria-colcount="4"> + <div role="row" aria-level="1" id="treegrid_row1"> + <div role="gridcell" id="treegrid_cell1">cell1</div> + <div role="gridcell" id="treegrid_cell2">cell2</div> + </div> + <div role="row" aria-level="2" id="treegrid_row2"> + <div role="gridcell" id="treegrid_cell3">cell1</div> + <div role="gridcell" id="treegrid_cell4">cell2</div> + </div> + <div role="row" id="treegrid_row3"> + <div role="gridcell" id="treegrid_cell5">cell1</div> + <div role="gridcell" id="treegrid_cell6">cell2</div> + </div> + </div> + + <div id="headings"> + <h1 id="h1">heading1</h1> + <h2 id="h2">heading2</h2> + <h3 id="h3">heading3</h3> + <h4 id="h4">heading4</h4> + <h5 id="h5">heading5</h5> + <h6 id="h6">heading6</h6> + <div id="ariaHeadingNoLevel" role="heading">ariaHeadingNoLevel</div> + </div> + + <ul id="combo1" role="combobox">Password + <li id="combo1_opt1" role="option">Xyzzy</li> + <li id="combo1_opt2" role="option">Plughs</li> + <li id="combo1_opt3" role="option">Shazaam</li> + <li id="combo1_opt4" role="option">JoeSentMe</li> + </ul> + + <form> + <input type="radio" style="display: none;" name="group3"> + <input type="radio" id="radio5" name="group3"> + </form> + + <div role="table" aria-colcount="4" aria-rowcount="2" id="table"> + <div role="row" id="table_row" aria-rowindex="2"> + <div role="cell" id="table_cell" aria-colindex="3">cell</div> + </div> + </div> + + <div role="grid" aria-readonly="true"> + <div tabindex="-1"> + <div role="row" id="wrapped_row_1"> + <div role="gridcell">cell content</div> + </div> + </div> + <div tabindex="-1"> + <div role="row" id="wrapped_row_2"> + <div role="gridcell">cell content</div> + </div> + </div> + </div> + + <div role="list" aria-owns="t1_li1 t1_li2 t1_li3" id="aria-list_4"> + <div role="listitem" id="t1_li2">Apples</div> + <div role="listitem" id="t1_li1">Oranges</div> + </div> + <div role="listitem" id="t1_li3">Bananas</div> + + <!-- ARIA comments, 1 level, group pos and size calculation --> + <article> + <p id="comm_single_1" role="comment">Comment 1</p> + <p id="comm_single_2" role="comment">Comment 2</p> + </article> + + <!-- Nested comments --> + <article> + <div id="comm_nested_1" role="comment"><p>Comment 1 level 1</p> + <div id="comm_nested_1_1" role="comment"><p>Comment 1 level 2</p></div> + <div id="comm_nested_1_2" role="comment"><p>Comment 2 level 2</p></div> + </div> + <div id="comm_nested_2" role="comment"><p>Comment 2 level 1</p> + <div id="comm_nested_2_1" role="comment"><p>Comment 3 level 2</p> + <div id="comm_nested_2_1_1" role="comment"><p>Comment 1 level 3</p></div> + </div> + </div> + <div id="comm_nested_3" role="comment"><p>Comment 3 level 1</p></div> + </article> +</body> +</html> diff --git a/accessible/tests/mochitest/attributes/test_obj_group.xhtml b/accessible/tests/mochitest/attributes/test_obj_group.xhtml new file mode 100644 index 0000000000..0eda4b6f2d --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_obj_group.xhtml @@ -0,0 +1,215 @@ +<?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="Accessibility Group Attributes ('level', 'setsize', 'posinset') Test."> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../attributes.js" /> + + <script type="application/javascript"> + <![CDATA[ + function openMenu(aID) + { + this.menuNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, this.menuNode) + ]; + + this.invoke = function openMenu_invoke() + { + this.menuNode.open = true; + } + + this.finalCheck = function openMenu_finalCheck() + { + testGroupAttrs("menu_item1.1", 1, 1); + testGroupAttrs("menu_item1.2", 1, 3); + testGroupAttrs("menu_item1.4", 2, 3); + testGroupAttrs("menu_item2", 3, 3); + } + + this.getID = function openMenu_getID() + { + return "open menu " + prettyName(aID); + } + } + + function openSubMenu(aID) + { + this.menuNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, this.menuNode) + ]; + + this.invoke = function openSubMenu_invoke() + { + this.menuNode.open = true; + } + + this.finalCheck = function openSubMenu_finalCheck() + { + testGroupAttrs("menu_item2.1", 1, 2, 1); + testGroupAttrs("menu_item2.2", 2, 2, 1); + } + + this.getID = function openSubMenu_getID() + { + return "open submenu " + prettyName(aID); + } + } + + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // xul:listbox (bug 417317) + testGroupAttrs("listitem1", 1, 4); + testGroupAttrs("listitem2", 2, 4); + testGroupAttrs("listitem3", 3, 4); + testGroupAttrs("listitem4", 4, 4); + + ////////////////////////////////////////////////////////////////////////// + // xul:tab + testGroupAttrs("tab1", 1, 2); + testGroupAttrs("tab2", 2, 2); + + ////////////////////////////////////////////////////////////////////////// + // xul:radio + testGroupAttrs("radio1", 1, 2); + testGroupAttrs("radio2", 2, 2); + + ////////////////////////////////////////////////////////////////////////// + // xul:menulist + testGroupAttrs("menulist1.1", 1); + testGroupAttrs("menulist1.2", 2); + testGroupAttrs("menulist1.3", 3); + testGroupAttrs("menulist1.4", 4); + + ////////////////////////////////////////////////////////////////////////// + // ARIA menu (bug 441888) + testGroupAttrs("aria-menuitem", 1, 3); + testGroupAttrs("aria-menuitemcheckbox", 2, 3); + testGroupAttrs("aria-menuitemradio", 3, 3); + testGroupAttrs("aria-menuitem2", 1, 1); + + ////////////////////////////////////////////////////////////////////////// + // xul:menu (bug 443881) + gQueue = new eventQueue(); + gQueue.push(new openMenu("menu_item1")); + gQueue.push(new openSubMenu("menu_item2")); + gQueue.invoke(); // SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=417317" + title="Certain types of LISTITEM accessibles no longer get attributes set like 'x of y', regression from fix for bug 389926"> + Mozilla Bug 417317 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=443881" + title="take into account separators in xul menus when group attributes are calculating"> + Mozilla Bug 443881 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=441888" + title="ARIA checked menu items are not included in the total list of menu items"> + Mozilla Bug 441888 + </a><br/> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <richlistbox> + <richlistitem id="listitem1"/> + <richlistitem id="listitem2"><label value="listitem2"/></richlistitem> + <richlistitem id="listitem3"/> + <richlistitem id="listitem4"><label value="listitem4"/></richlistitem> + </richlistbox> + + <menubar> + <menu label="item1" id="menu_item1"> + <menupopup> + <menuitem label="item1.1" id="menu_item1.1"/> + <menuseparator/> + <menuitem label="item1.2" id="menu_item1.2"/> + <menuitem label="item1.3" hidden="true"/> + <menuitem label="item1.4" id="menu_item1.4"/> + <menu label="item2" id="menu_item2"> + <menupopup> + <menuitem label="item2.1" id="menu_item2.1"/> + <menuitem label="item2.2" id="menu_item2.2"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menubar> + + <tabbox> + <tabs> + <tab id="tab1" label="tab1"/> + <tab id="tab2" label="tab3"/> + </tabs> + <tabpanels> + <tabpanel/> + <tabpanel/> + </tabpanels> + </tabbox> + + <radiogroup> + <radio id="radio1" label="radio1"/> + <radio id="radio2" label="radio2"/> + </radiogroup> + + <menulist id="menulist1" label="Vehicle"> + <menupopup> + <menuitem id="menulist1.1" label="Car"/> + <menuitem id="menulist1.2" label="Taxi"/> + <menuitem id="menulist1.3" label="Bus" selected="true"/> + <menuitem id="menulist1.4" label="Train"/> + </menupopup> + </menulist> + + <vbox> + <description role="menuitem" id="aria-menuitem" + value="conventional menuitem"/> + <description role="menuitemcheckbox" id="aria-menuitemcheckbox" + value="conventional checkbox menuitem"/> + <description role="menuitem" hidden="true"/> + <description role="menuitemradio" id="aria-menuitemradio" + value="conventional radio menuitem"/> + <description role="separator" + value="conventional separator"/> + <description role="menuitem" id="aria-menuitem2" + value="conventional menuitem"/> + </vbox> + + </vbox> + </hbox> +</window> + diff --git a/accessible/tests/mochitest/attributes/test_obj_group_tree.xhtml b/accessible/tests/mochitest/attributes/test_obj_group_tree.xhtml new file mode 100644 index 0000000000..287ee7989e --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_obj_group_tree.xhtml @@ -0,0 +1,84 @@ +<?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="Accessible XUL tree attributes tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../attributes.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + var treeNode = getNode("tree"); + + var tree = getAccessible(treeNode); + var treeitem1 = tree.firstChild.nextSibling; + testGroupAttrs(treeitem1, 1, 4, 1); + + var treeitem2 = treeitem1.nextSibling; + testGroupAttrs(treeitem2, 2, 4, 1); + + var treeitem3 = treeitem2.nextSibling; + testGroupAttrs(treeitem3, 1, 2, 2); + + var treeitem4 = treeitem3.nextSibling; + testGroupAttrs(treeitem4, 2, 2, 2); + + var treeitem5 = treeitem4.nextSibling; + testGroupAttrs(treeitem5, 3, 4, 1); + + var treeitem6 = treeitem5.nextSibling; + testGroupAttrs(treeitem6, 4, 4, 1); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yXULTreeLoadEvent(doTest, "tree", new nsTreeTreeView()); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727" + title="Reorganize implementation of XUL tree accessibility"> + Mozilla Bug 503727 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + </treecols> + <treechildren/> + </tree> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/attributes/test_tag.html b/accessible/tests/mochitest/attributes/test_tag.html new file mode 100644 index 0000000000..b57cc2dca8 --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_tag.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML landmark tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + + function doTest() { + // And some AT may look for this + testAttrs("nav", {"tag": "nav"}, true); + testAttrs("header", {"tag": "header"}, true); + testAttrs("footer", {"tag": "footer"}, true); + testAttrs("article", {"tag": "article"}, true); + testAttrs("aside", {"tag": "aside"}, true); + testAttrs("section", {"tag": "section"}, true); + testAttrs("main", {"tag": "article"}, true); + testAttrs("form", {"tag": "article"}, true); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Provide mappings for html5 <nav> <header> <footer> <article>" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=593368"> + Bug 593368 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502" + title="Map <article> like we do aria role article"> + Bug 613502 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=610650" + title="Change implementation of HTML5 landmark elements to conform"> + Bug 610650 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=614310" + title="Map section to pane (like role=region)"> + Mozilla Bug 614310 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=734982" + title="Map ARIA role FORM"> + Bug 734982 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <nav id="nav">a nav</nav> + <header id="header">a header</header> + <footer id="footer">a footer</footer> + <aside id="aside">by the way I am an aside</aside> + <section id="section">a section</section> + + <article id="article">an article</article> + <article id="main" role="main">a main area</article> + <article id="form" role="form">a form area</article> + +</body> +</html> diff --git a/accessible/tests/mochitest/attributes/test_xml-roles.html b/accessible/tests/mochitest/attributes/test_xml-roles.html new file mode 100644 index 0000000000..ff71f0da3a --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_xml-roles.html @@ -0,0 +1,267 @@ +<!DOCTYPE html> +<html> +<head> + <title>XML roles tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + + function doTest() { + // Some AT may look for this + testAttrs("nav", {"xml-roles": "navigation"}, true); + testAttrs("header", {"xml-roles": "banner"}, true); + testAbsentAttrs("article_header", {"xml-roles": "banner"}); + testAbsentAttrs("main_header", {"xml-roles": "banner"}); + testAbsentAttrs("section_header", {"xml-roles": "banner"}); + testAttrs("footer", {"xml-roles": "contentinfo"}, true); + testAbsentAttrs("article_footer", {"xml-roles": "contentinfo"}); + testAbsentAttrs("main_footer", {"xml-roles": "contentinfo"}); + testAbsentAttrs("section_footer", {"xml-roles": "contentinfo"}); + testAttrs("aside", {"xml-roles": "complementary"}, true); + testAbsentAttrs("section", {"xml-roles": "region"}); + testAttrs("main", {"xml-roles": "main"}, true); // // ARIA override + testAttrs("form", {"xml-roles": "form"}, true); + testAttrs("feed", {"xml-roles": "feed"}, true); + testAttrs("article", {"xml-roles": "article"}, true); + testAttrs("main_element", {"xml-roles": "main"}, true); + testAttrs("figure", {"xml-roles": "figure"}, true); + + testAttrs("search", {"xml-roles": "searchbox"}, true); + + testAttrs("code", {"xml-roles": "code"}, true); + + testAttrs("open-1", {"xml-roles": "open-fence"}, true); + testAttrs("open-2", {"xml-roles": "open-fence"}, true); + testAttrs("open-3", {"xml-roles": "open-fence"}, true); + testAttrs("open-4", {"xml-roles": "open-fence"}, true); + testAttrs("open-5", {"xml-roles": "open-fence"}, true); + testAttrs("open-6", {"xml-roles": "open-fence"}, true); + testAttrs("open-7", {"xml-roles": "open-fence"}, true); + + testAttrs("sep-1", {"xml-roles": "separator"}, true); + testAttrs("sep-2", {"xml-roles": "separator"}, true); + testAttrs("sep-3", {"xml-roles": "separator"}, true); + testAttrs("sep-4", {"xml-roles": "separator"}, true); + testAttrs("sep-5", {"xml-roles": "separator"}, true); + testAttrs("sep-6", {"xml-roles": "separator"}, true); + testAttrs("sep-7", {"xml-roles": "separator"}, true); + + testAttrs("close-1", {"xml-roles": "close-fence"}, true); + testAttrs("close-2", {"xml-roles": "close-fence"}, true); + testAttrs("close-3", {"xml-roles": "close-fence"}, true); + testAttrs("close-4", {"xml-roles": "close-fence"}, true); + testAttrs("close-5", {"xml-roles": "close-fence"}, true); + testAttrs("close-6", {"xml-roles": "close-fence"}, true); + testAttrs("close-7", {"xml-roles": "close-fence"}, true); + + testAttrs("num", {"xml-roles": "numerator"}, true); + testAttrs("den", {"xml-roles": "denominator"}, true); + + testAttrs("sub-1", {"xml-roles": "subscript"}, true); + testAttrs("sub-2", {"xml-roles": "subscript"}, true); + testAttrs("sub-3", {"xml-roles": "subscript"}, true); + testAttrs("sup-1", {"xml-roles": "superscript"}, true); + testAttrs("sup-2", {"xml-roles": "superscript"}, true); + testAttrs("sup-3", {"xml-roles": "superscript"}, true); + testAttrs("sup-4", {"xml-roles": "superscript"}, true); + testAttrs("presub-1", {"xml-roles": "presubscript"}, true); + testAttrs("presub-2", {"xml-roles": "presubscript"}, true); + testAttrs("presup-1", {"xml-roles": "presuperscript"}, true); + + testAttrs("under-1", {"xml-roles": "underscript"}, true); + testAttrs("under-2", {"xml-roles": "underscript"}, true); + testAttrs("over-1", {"xml-roles": "overscript"}, true); + testAttrs("over-2", {"xml-roles": "overscript"}, true); + + testAttrs("root-index-1", {"xml-roles": "root-index"}, true); + + testAttrs("base-1", {"xml-roles": "base"}, true); + testAttrs("base-2", {"xml-roles": "base"}, true); + testAttrs("base-3", {"xml-roles": "base"}, true); + testAttrs("base-4", {"xml-roles": "base"}, true); + testAttrs("base-5", {"xml-roles": "base"}, true); + testAttrs("base-6", {"xml-roles": "base"}, true); + testAttrs("base-7", {"xml-roles": "base"}, true); + testAttrs("base-8", {"xml-roles": "base"}, true); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Provide mappings for html5 <nav> <header> <footer> <article>" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=593368"> + Bug 593368 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502" + title="Map <article> like we do aria role article"> + Bug 613502 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=610650" + title="Change implementation of HTML5 landmark elements to conform"> + Bug 610650 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=614310" + title="Map section to pane (like role=region)"> + Mozilla Bug 614310 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=734982" + title="Map ARIA role FORM"> + Bug 734982 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=761891" + title="HTML5 article element should expose xml-roles:article object attribute"> + Bug 761891 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=849624" + title="modify HTML5 header and footer accessibility API mapping"> + Bug 849624 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518" + title="ARIA 1.1: Support role 'searchbox'"> + Bug 1121518 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1356049" + title="Map ARIA figure role"> + Bug 1356049 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <nav id="nav">a nav</nav> + <header id="header">a header</header> + <footer id="footer">a footer</footer> + <article id="article_with_header_and_footer"> + <header id="article_header">a header within an article</header> + <footer id="article_footer">a footer within an article</footer> + </article> + <main id="main_with_header_and_footer"> + <header id="main_header">a header within a main</header> + <footer id="main_footer">a footer within a main</footer> + </main> + <section id="section_with_header_and_footer"> + <header id="section_header">a header within an section</header> + <footer id="section_footer">a footer within an section</footer> + </section> + <aside id="aside">by the way I am an aside</aside> + <section id="section">a section</section> + <article id="main" role="main">a main area</article> + <article id="form" role="form">a form area</article> + <div id="feed" role="feed">a feed</div> + <article id="article">article</article> + <main id="main_element">another main area</main> + <div id="figure" role="figure">a figure</div> + + <input id="search" type="search"/> + + <div id="code" role="code"></div> + + <!-- open-fence, separator, close-fence --> + <math><mo id="open-1">(</mo><mi>x</mi><mo id="sep-1">,</mo><mi>y</mi><mo id="close-1">)</mo></math> + <math><mrow><mo id="open-2">(</mo><mi>x</mi><mo id="sep-2">,</mo><mi>y</mi><mo id="close-2">)</mo></mrow></math> + <math><mstyle><mo id="open-3">(</mo><mi>x</mi><mo id="sep-3">,</mo><mi>y</mi><mo id="close-3">)</mo></mstyle></math> + <math><msqrt><mo id="open-4">(</mo><mi>x</mi><mo id="sep-4">,</mo><mi>y</mi><mo id="close-4">)</mo></msqrt></math> + <math><menclose><mo id="open-5">(</mo><mi>x</mi><mo id="sep-5">,</mo><mi>y</mi><mo id="close-5">)</mo></menclose></math> + <math><merror><mo id="open-6">(</mo><mi>x</mi><mo id="sep-6">,</mo><mi>y</mi><mo id="close-6">)</mo></merror></math> + <math><mtable><mtr><mtd><mo id="open-7">(</mo><mi>x</mi><mo id="sep-7">,</mo><mi>y</mi><mo id="close-7">)</mo></mtd></mtr></mtable></math> + + <!-- numerator, denominator --> + <math> + <mfrac> + <mi id="num">a</mi> + <mi id="den">b</mi> + </mfrac> + </math> + + <!-- subscript, superscript, presubscript, presuperscript --> + <math> + <msub> + <mi id="base-1">a</mi> + <mi id="sub-1">b</mi> + </msub> + </math> + <math> + <msup> + <mi id="base-2">a</mi> + <mi id="sup-1">b</mi> + </msup> + </math> + <math> + <msubsup> + <mi id="base-3">a</mi> + <mi id="sub-2">b</mi> + <mi id="sup-2">c</mi> + </msubsup> + </math> + <math> + <mmultiscripts> + <mi id="base-4">a</mi> + <mi id="sub-3">b</mi> + <mi id="sup-3">c</mi> + <none/> + <mi id="sup-4">d</mi> + <mprescripts/> + <mi id="presub-1">e</mi> + <none/> + <mi id="presub-2">f</mi> + <mi id="presup-1">g</mi> + </mmultiscripts> + </math> + + <!-- underscript, overscript --> + <math> + <munder> + <mi id="base-5">a</mi> + <mi id="under-1">b</mi> + </munder> + </math> + <math> + <mover> + <mi id="base-6">a</mi> + <mi id="over-1">b</mi> + </mover> + </math> + <math> + <munderover> + <mi id="base-7">a</mi> + <mi id="under-2">b</mi> + <mi id="over-2">c</mi> + </munderover> + </math> + + <!-- root-index --> + <math> + <mroot> + <mi id="base-8">a</mi> + <mi id="root-index-1">b</mi> + </mroot> + </math> + +</body> +</html> diff --git a/accessible/tests/mochitest/autocomplete.js b/accessible/tests/mochitest/autocomplete.js new file mode 100644 index 0000000000..bd5458c51d --- /dev/null +++ b/accessible/tests/mochitest/autocomplete.js @@ -0,0 +1,198 @@ +const nsISupports = Ci.nsISupports; +const nsIAutoCompleteResult = Ci.nsIAutoCompleteResult; +const nsIAutoCompleteSearch = Ci.nsIAutoCompleteSearch; +const nsIFactory = Ci.nsIFactory; +const nsIUUIDGenerator = Ci.nsIUUIDGenerator; +const nsIComponentRegistrar = Ci.nsIComponentRegistrar; + +var gDefaultAutoCompleteSearch = null; + +/** + * Register 'test-a11y-search' AutoCompleteSearch. + * + * @param aValues [in] set of possible results values + * @param aComments [in] set of possible results descriptions + */ +function initAutoComplete(aValues, aComments) { + var allResults = new ResultsHeap(aValues, aComments); + gDefaultAutoCompleteSearch = new AutoCompleteSearch( + "test-a11y-search", + allResults + ); + registerAutoCompleteSearch( + gDefaultAutoCompleteSearch, + "Accessibility Test AutoCompleteSearch" + ); +} + +/** + * Unregister 'test-a11y-search' AutoCompleteSearch. + */ +function shutdownAutoComplete() { + unregisterAutoCompleteSearch(gDefaultAutoCompleteSearch); + gDefaultAutoCompleteSearch.cid = null; + gDefaultAutoCompleteSearch = null; +} + +/** + * Register the given AutoCompleteSearch. + * + * @param aSearch [in] AutoCompleteSearch object + * @param aDescription [in] description of the search object + */ +function registerAutoCompleteSearch(aSearch, aDescription) { + var name = "@mozilla.org/autocomplete/search;1?name=" + aSearch.name; + + var uuidGenerator = + Cc["@mozilla.org/uuid-generator;1"].getService(nsIUUIDGenerator); + var cid = uuidGenerator.generateUUID(); + + var componentManager = Components.manager.QueryInterface( + nsIComponentRegistrar + ); + componentManager.registerFactory(cid, aDescription, name, aSearch); + + // Keep the id on the object so we can unregister later. + aSearch.cid = cid; +} + +/** + * Unregister the given AutoCompleteSearch. + */ +function unregisterAutoCompleteSearch(aSearch) { + var componentManager = Components.manager.QueryInterface( + nsIComponentRegistrar + ); + componentManager.unregisterFactory(aSearch.cid, aSearch); +} + +/** + * A container to keep all possible results of autocomplete search. + */ +function ResultsHeap(aValues, aComments) { + this.values = aValues; + this.comments = aComments; +} + +ResultsHeap.prototype = { + constructor: ResultsHeap, + + /** + * Return AutoCompleteResult for the given search string. + */ + getAutoCompleteResultFor(aSearchString) { + var values = [], + comments = []; + for (var idx = 0; idx < this.values.length; idx++) { + if (this.values[idx].includes(aSearchString)) { + values.push(this.values[idx]); + comments.push(this.comments[idx]); + } + } + return new AutoCompleteResult(values, comments); + }, +}; + +/** + * nsIAutoCompleteSearch implementation. + * + * @param aName [in] the name of autocomplete search + * @param aAllResults [in] ResultsHeap object + */ +function AutoCompleteSearch(aName, aAllResults) { + this.name = aName; + this.allResults = aAllResults; +} + +AutoCompleteSearch.prototype = { + constructor: AutoCompleteSearch, + + // nsIAutoCompleteSearch implementation + startSearch(aSearchString, aSearchParam, aPreviousResult, aListener) { + var result = this.allResults.getAutoCompleteResultFor(aSearchString); + aListener.onSearchResult(this, result); + }, + + stopSearch() {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI([ + "nsIFactory", + "nsIAutoCompleteSearch", + ]), + + // nsIFactory implementation + createInstance(iid) { + return this.QueryInterface(iid); + }, + + // Search name. Used by AutoCompleteController. + name: null, + + // Results heap. + allResults: null, +}; + +/** + * nsIAutoCompleteResult implementation. + */ +function AutoCompleteResult(aValues, aComments) { + this.values = aValues; + this.comments = aComments; + + if (this.values.length) { + this.searchResult = nsIAutoCompleteResult.RESULT_SUCCESS; + } else { + this.searchResult = nsIAutoCompleteResult.NOMATCH; + } +} + +AutoCompleteResult.prototype = { + constructor: AutoCompleteResult, + + searchString: "", + searchResult: null, + + defaultIndex: 0, + + get matchCount() { + return this.values.length; + }, + + getValueAt(aIndex) { + return this.values[aIndex]; + }, + + getLabelAt(aIndex) { + return this.getValueAt(aIndex); + }, + + getCommentAt(aIndex) { + return this.comments[aIndex]; + }, + + getStyleAt(aIndex) { + return null; + }, + + getImageAt(aIndex) { + return ""; + }, + + getFinalCompleteValueAt(aIndex) { + return this.getValueAt(aIndex); + }, + + isRemovableAt(aRowIndex) { + return true; + }, + + removeValueAt(aRowIndex) {}, + + // nsISupports implementation + QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteResult"]), + + // Data + values: null, + comments: null, +}; diff --git a/accessible/tests/mochitest/bounds/a11y.ini b/accessible/tests/mochitest/bounds/a11y.ini new file mode 100644 index 0000000000..e9f8a3d4eb --- /dev/null +++ b/accessible/tests/mochitest/bounds/a11y.ini @@ -0,0 +1,5 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_list.html] diff --git a/accessible/tests/mochitest/bounds/test_list.html b/accessible/tests/mochitest/bounds/test_list.html new file mode 100644 index 0000000000..7e5b75868d --- /dev/null +++ b/accessible/tests/mochitest/bounds/test_list.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<html> +<head> + <title>Accessible boundaries when page is zoomed</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + + <script type="application/javascript"> + function doTest() { + // Inside list + var li = getAccessible("insidelist_item"); + testBounds(li); + + var [xLI, yLI, widthLI, heightLI] = getBounds(li); + var bullet = li.firstChild; + var [x, y, width, height] = getBounds(bullet); + is(x, xLI, + "Bullet x should match to list item x"); + ok(y >= yLI, + "Bullet y= " + y + " should be not less than list item y=" + yLI); + ok(width < widthLI, + "Bullet width should be lesser list item width"); + ok(height <= heightLI, + "Bullet height= " + height + " should be not greater than list item height=" + heightLI); + + // Outside list + li = getAccessible("outsidelist_item"); + var [xLIElm, yLIElm, widthLIElm, heightLIElm] = getBoundsForDOMElm(li); + [xLI, yLI, widthLI, heightLI] = getBounds(li); + + ok(xLI < xLIElm, + "Outside list item x=" + xLI + " should be lesser than list item element x=" + xLIElm); + is(yLI, yLIElm, + "Outside list item y should match to list item element y"); + ok(widthLI > widthLIElm, + "Outside list item width=" + widthLI + " should be greater than list item element width=" + widthLIElm); + ok(heightLI >= Math.trunc(heightLIElm), + "Outside list item height=" + heightLI + " should not be less than list item element height=" + heightLIElm); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=754627" + title="GetBounds on bullet return wrong values"> + Mozilla Bug 754627 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <ul style="list-style-position: inside;"> + <li id="insidelist_item">item</li> + </ul> + + <ul style="list-style-position: outside;"> + <li id="outsidelist_item">item</li> + </ul> + +</body> +</html> diff --git a/accessible/tests/mochitest/browser.js b/accessible/tests/mochitest/browser.js new file mode 100644 index 0000000000..b08214cfa8 --- /dev/null +++ b/accessible/tests/mochitest/browser.js @@ -0,0 +1,156 @@ +/* import-globals-from common.js */ + +var { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +/** + * Load the browser with the given url and then invokes the given function. + */ +function openBrowserWindow(aFunc, aURL, aRect) { + gBrowserContext.testFunc = aFunc; + gBrowserContext.startURL = aURL; + gBrowserContext.browserRect = aRect; + + addLoadEvent(openBrowserWindowIntl); +} + +/** + * Close the browser window. + */ +function closeBrowserWindow() { + gBrowserContext.browserWnd.close(); +} + +/** + * Return the browser window object. + */ +function browserWindow() { + return gBrowserContext.browserWnd; +} + +/** + * Return the document of the browser window. + */ +function browserDocument() { + return browserWindow().document; +} + +/** + * Return tab browser object. + */ +function tabBrowser() { + return browserWindow().gBrowser; +} + +/** + * Return browser element of the current tab. + */ +function currentBrowser() { + return tabBrowser().selectedBrowser; +} + +/** + * Return DOM document of the current tab. + */ +function currentTabDocument() { + return currentBrowser().contentDocument; +} + +/** + * Return window of the current tab. + */ +function currentTabWindow() { + return currentTabDocument().defaultView; +} + +/** + * Return browser element of the tab at the given index. + */ +function browserAt(aIndex) { + return tabBrowser().getBrowserAtIndex(aIndex); +} + +/** + * Return DOM document of the tab at the given index. + */ +function tabDocumentAt(aIndex) { + return browserAt(aIndex).contentDocument; +} + +/** + * Return input element of address bar. + */ +function urlbarInput() { + return browserWindow().document.getElementById("urlbar").inputField; +} + +/** + * Return reload button. + */ +function reloadButton() { + return browserWindow().document.getElementById("urlbar-reload-button"); +} + +// ////////////////////////////////////////////////////////////////////////////// +// private section + +var gBrowserContext = { + browserWnd: null, + testFunc: null, + startURL: "", +}; + +function openBrowserWindowIntl() { + var params = "chrome,all,dialog=no,non-remote"; + var rect = gBrowserContext.browserRect; + if (rect) { + if ("left" in rect) { + params += ",left=" + rect.left; + } + if ("top" in rect) { + params += ",top=" + rect.top; + } + if ("width" in rect) { + params += ",width=" + rect.width; + } + if ("height" in rect) { + params += ",height=" + rect.height; + } + } + + gBrowserContext.browserWnd = + window.browsingContext.topChromeWindow.openDialog( + AppConstants.BROWSER_CHROME_URL, + "_blank", + params, + gBrowserContext.startURL || "data:text/html,<html></html>" + ); + + whenDelayedStartupFinished(browserWindow(), function () { + addA11yLoadEvent(startBrowserTests, browserWindow()); + }); +} + +function startBrowserTests() { + if (gBrowserContext.startURL) { + // Make sure the window is the one loading our URL. + if (currentBrowser().contentWindow.location != gBrowserContext.startURL) { + setTimeout(startBrowserTests, 0); + return; + } + // wait for load + addA11yLoadEvent(gBrowserContext.testFunc, currentBrowser().contentWindow); + } else { + gBrowserContext.testFunc(); + } +} + +function whenDelayedStartupFinished(aWindow, aCallback) { + Services.obs.addObserver(function observer(aSubject, aTopic) { + if (aWindow == aSubject) { + Services.obs.removeObserver(observer, aTopic); + setTimeout(aCallback, 0); + } + }, "browser-delayed-startup-finished"); +} diff --git a/accessible/tests/mochitest/common.js b/accessible/tests/mochitest/common.js new file mode 100644 index 0000000000..1d42dea107 --- /dev/null +++ b/accessible/tests/mochitest/common.js @@ -0,0 +1,1046 @@ +// This file has circular dependencies that may require other files. Rather +// than use import-globals-from, we list the globals individually here to save +// confusing ESLint. +// actions.js +/* globals testActionNames */ +// attributes.js +/* globals testAttrs, testAbsentAttrs, testTextAttrs */ +// relations.js +/* globals testRelation */ +// role.js +/* globals isRole */ +// state.js +/* globals testStates */ + +// ////////////////////////////////////////////////////////////////////////////// +// Interfaces + +const nsIAccessibilityService = Ci.nsIAccessibilityService; + +const nsIAccessibleEvent = Ci.nsIAccessibleEvent; +const nsIAccessibleStateChangeEvent = Ci.nsIAccessibleStateChangeEvent; +const nsIAccessibleCaretMoveEvent = Ci.nsIAccessibleCaretMoveEvent; +const nsIAccessibleScrollingEvent = Ci.nsIAccessibleScrollingEvent; +const nsIAccessibleTextChangeEvent = Ci.nsIAccessibleTextChangeEvent; +const nsIAccessibleTextSelectionChangeEvent = + Ci.nsIAccessibleTextSelectionChangeEvent; +const nsIAccessibleVirtualCursorChangeEvent = + Ci.nsIAccessibleVirtualCursorChangeEvent; +const nsIAccessibleObjectAttributeChangedEvent = + Ci.nsIAccessibleObjectAttributeChangedEvent; +const nsIAccessibleAnnouncementEvent = Ci.nsIAccessibleAnnouncementEvent; + +const nsIAccessibleStates = Ci.nsIAccessibleStates; +const nsIAccessibleRole = Ci.nsIAccessibleRole; +const nsIAccessibleScrollType = Ci.nsIAccessibleScrollType; +const nsIAccessibleCoordinateType = Ci.nsIAccessibleCoordinateType; + +const nsIAccessibleRelation = Ci.nsIAccessibleRelation; +const nsIAccessibleTextRange = Ci.nsIAccessibleTextRange; + +const nsIAccessible = Ci.nsIAccessible; + +const nsIAccessibleDocument = Ci.nsIAccessibleDocument; +const nsIAccessibleApplication = Ci.nsIAccessibleApplication; + +const nsIAccessibleText = Ci.nsIAccessibleText; +const nsIAccessibleEditableText = Ci.nsIAccessibleEditableText; + +const nsIAccessibleHyperLink = Ci.nsIAccessibleHyperLink; +const nsIAccessibleHyperText = Ci.nsIAccessibleHyperText; + +const nsIAccessibleImage = Ci.nsIAccessibleImage; +const nsIAccessiblePivot = Ci.nsIAccessiblePivot; +const nsIAccessibleSelectable = Ci.nsIAccessibleSelectable; +const nsIAccessibleTable = Ci.nsIAccessibleTable; +const nsIAccessibleTableCell = Ci.nsIAccessibleTableCell; +const nsIAccessibleTraversalRule = Ci.nsIAccessibleTraversalRule; +const nsIAccessibleValue = Ci.nsIAccessibleValue; + +const nsIObserverService = Ci.nsIObserverService; + +const nsIDOMWindow = Ci.nsIDOMWindow; + +const nsIPropertyElement = Ci.nsIPropertyElement; + +// ////////////////////////////////////////////////////////////////////////////// +// OS detect + +const MAC = navigator.platform.includes("Mac"); +const LINUX = navigator.platform.includes("Linux"); +const SOLARIS = navigator.platform.includes("SunOS"); +const WIN = navigator.platform.includes("Win"); + +// ////////////////////////////////////////////////////////////////////////////// +// Application detect +// Firefox is assumed by default. + +const SEAMONKEY = navigator.userAgent.match(/ SeaMonkey\//); + +// ////////////////////////////////////////////////////////////////////////////// +// Accessible general + +const STATE_BUSY = nsIAccessibleStates.STATE_BUSY; + +const SCROLL_TYPE_TOP_EDGE = nsIAccessibleScrollType.SCROLL_TYPE_TOP_EDGE; +const SCROLL_TYPE_ANYWHERE = nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE; + +const COORDTYPE_SCREEN_RELATIVE = + nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE; +const COORDTYPE_WINDOW_RELATIVE = + nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE; +const COORDTYPE_PARENT_RELATIVE = + nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE; + +const kEmbedChar = String.fromCharCode(0xfffc); + +const kDiscBulletChar = String.fromCharCode(0x2022); +const kDiscBulletText = kDiscBulletChar + " "; +const kCircleBulletText = String.fromCharCode(0x25e6) + " "; +const kSquareBulletText = String.fromCharCode(0x25aa) + " "; + +const MAX_TRIM_LENGTH = 100; + +/** + * Services to determine if e10s is enabled. + */ + +/** + * nsIAccessibilityService service. + */ +var gAccService = Cc["@mozilla.org/accessibilityService;1"].getService( + nsIAccessibilityService +); + +/** + * Enable/disable logging. + */ +function enableLogging(aModules) { + gAccService.setLogging(aModules); +} +function disableLogging() { + gAccService.setLogging(""); +} +function isLogged(aModule) { + return gAccService.isLogged(aModule); +} + +/** + * Dumps the accessible tree into console. + */ +function dumpTree(aId, aMsg) { + function dumpTreeIntl(acc, indent) { + dump(indent + prettyName(acc) + "\n"); + + var children = acc.children; + for (var i = 0; i < children.length; i++) { + var child = children.queryElementAt(i, nsIAccessible); + dumpTreeIntl(child, indent + " "); + } + } + + function dumpDOMTreeIntl(node, indent) { + dump(indent + prettyName(node) + "\n"); + + var children = node.childNodes; + for (var i = 0; i < children.length; i++) { + var child = children.item(i); + dumpDOMTreeIntl(child, indent + " "); + } + } + + dump(aMsg + "\n"); + var root = getAccessible(aId); + dumpTreeIntl(root, " "); + + dump("DOM tree:\n"); + dumpDOMTreeIntl(getNode(aId), " "); +} + +/** + * Invokes the given function when document is loaded and focused. Preferable + * to mochitests 'addLoadEvent' function -- additionally ensures state of the + * document accessible is not busy. + * + * @param aFunc the function to invoke + */ +function addA11yLoadEvent(aFunc, aWindow) { + function waitForDocLoad() { + window.setTimeout(function () { + var targetDocument = aWindow ? aWindow.document : document; + var accDoc = getAccessible(targetDocument); + var state = {}; + accDoc.getState(state, {}); + if (state.value & STATE_BUSY) { + waitForDocLoad(); + return; + } + + window.setTimeout(aFunc, 0); + }, 0); + } + + if ( + aWindow && + aWindow.document.activeElement && + aWindow.document.activeElement.localName == "browser" + ) { + waitForDocLoad(); + } else { + SimpleTest.waitForFocus(waitForDocLoad, aWindow); + } +} + +/** + * Analogy of SimpleTest.is function used to compare objects. + */ +function isObject(aObj, aExpectedObj, aMsg) { + if (aObj == aExpectedObj) { + ok(true, aMsg); + return; + } + + ok( + false, + aMsg + + " - got '" + + prettyName(aObj) + + "', expected '" + + prettyName(aExpectedObj) + + "'" + ); +} + +/** + * is() function checking the expected value is within the range. + */ +function isWithin(aExpected, aGot, aWithin, aMsg) { + if (Math.abs(aGot - aExpected) <= aWithin) { + ok(true, `${aMsg} - Got ${aGot}`); + } else { + ok( + false, + `${aMsg} - Got ${aGot}, expected ${aExpected} with error of ${aWithin}` + ); + } +} + +// ////////////////////////////////////////////////////////////////////////////// +// Helpers for getting DOM node/accessible + +/** + * Return the DOM node by identifier (may be accessible, DOM node or ID). + */ +function getNode(aAccOrNodeOrID, aDocument) { + if (!aAccOrNodeOrID) { + return null; + } + + if (Node.isInstance(aAccOrNodeOrID)) { + return aAccOrNodeOrID; + } + + if (aAccOrNodeOrID instanceof nsIAccessible) { + return aAccOrNodeOrID.DOMNode; + } + + var node = (aDocument || document).getElementById(aAccOrNodeOrID); + if (!node) { + ok(false, "Can't get DOM element for " + aAccOrNodeOrID); + return null; + } + + return node; +} + +/** + * Constants indicates getAccessible doesn't fail if there is no accessible. + */ +const DONOTFAIL_IF_NO_ACC = 1; + +/** + * Constants indicates getAccessible won't fail if accessible doesn't implement + * the requested interfaces. + */ +const DONOTFAIL_IF_NO_INTERFACE = 2; + +/** + * Return accessible for the given identifier (may be ID attribute or DOM + * element or accessible object) or null. + * + * @param aAccOrElmOrID [in] identifier to get an accessible implementing + * the given interfaces + * @param aInterfaces [in, optional] the interface or an array interfaces + * to query it/them from obtained accessible + * @param aElmObj [out, optional] object to store DOM element which + * accessible is obtained for + * @param aDoNotFailIf [in, optional] no error for special cases (see + * constants above) + */ +function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj, aDoNotFailIf) { + if (!aAccOrElmOrID) { + return null; + } + + var elm = null; + + if (aAccOrElmOrID instanceof nsIAccessible) { + try { + elm = aAccOrElmOrID.DOMNode; + } catch (e) {} + } else if (Node.isInstance(aAccOrElmOrID)) { + elm = aAccOrElmOrID; + } else { + elm = document.getElementById(aAccOrElmOrID); + if (!elm) { + ok(false, "Can't get DOM element for " + aAccOrElmOrID); + return null; + } + } + + if (aElmObj && typeof aElmObj == "object") { + aElmObj.value = elm; + } + + var acc = aAccOrElmOrID instanceof nsIAccessible ? aAccOrElmOrID : null; + if (!acc) { + try { + acc = gAccService.getAccessibleFor(elm); + } catch (e) {} + + if (!acc) { + if (!(aDoNotFailIf & DONOTFAIL_IF_NO_ACC)) { + ok(false, "Can't get accessible for " + prettyName(aAccOrElmOrID)); + } + + return null; + } + } + + if (!aInterfaces) { + return acc; + } + + if (!(aInterfaces instanceof Array)) { + aInterfaces = [aInterfaces]; + } + + for (var index = 0; index < aInterfaces.length; index++) { + if (acc instanceof aInterfaces[index]) { + continue; + } + try { + acc.QueryInterface(aInterfaces[index]); + } catch (e) { + if (!(aDoNotFailIf & DONOTFAIL_IF_NO_INTERFACE)) { + ok( + false, + "Can't query " + aInterfaces[index] + " for " + aAccOrElmOrID + ); + } + + return null; + } + } + + return acc; +} + +/** + * Return true if the given identifier has an accessible, or exposes the wanted + * interfaces. + */ +function isAccessible(aAccOrElmOrID, aInterfaces) { + return !!getAccessible( + aAccOrElmOrID, + aInterfaces, + null, + DONOTFAIL_IF_NO_ACC | DONOTFAIL_IF_NO_INTERFACE + ); +} + +/** + * Return an accessible that contains the DOM node for the given identifier. + */ +function getContainerAccessible(aAccOrElmOrID) { + var node = getNode(aAccOrElmOrID); + if (!node) { + return null; + } + + // eslint-disable-next-line no-empty + while ((node = node.parentNode) && !isAccessible(node)) {} + return node ? getAccessible(node) : null; +} + +/** + * Return root accessible for the given identifier. + */ +function getRootAccessible(aAccOrElmOrID) { + var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document); + return acc ? acc.rootDocument.QueryInterface(nsIAccessible) : null; +} + +/** + * Return tab document accessible the given accessible is contained by. + */ +function getTabDocAccessible(aAccOrElmOrID) { + var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document); + + var docAcc = acc.document.QueryInterface(nsIAccessible); + var containerDocAcc = docAcc.parent.document; + + // Test is running is stand-alone mode. + if (acc.rootDocument == containerDocAcc) { + return docAcc; + } + + // In the case of running all tests together. + return containerDocAcc.QueryInterface(nsIAccessible); +} + +/** + * Return application accessible. + */ +function getApplicationAccessible() { + return gAccService + .getApplicationAccessible() + .QueryInterface(nsIAccessibleApplication); +} + +/** + * A version of accessible tree testing, doesn't fail if tree is not complete. + */ +function testElm(aID, aTreeObj) { + testAccessibleTree(aID, aTreeObj, kSkipTreeFullCheck); +} + +/** + * Flags used for testAccessibleTree + */ +const kSkipTreeFullCheck = 1; + +/** + * Compare expected and actual accessibles trees. + * + * @param aAccOrElmOrID [in] accessible identifier + * @param aAccTree [in] JS object, each field corresponds to property of + * accessible object. Additionally special properties + * are presented: + * children - an array of JS objects representing + * children of accessible + * states - an object having states and extraStates + * fields + * @param aFlags [in, optional] flags, see constants above + */ +// eslint-disable-next-line complexity +function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags) { + var acc = getAccessible(aAccOrElmOrID); + if (!acc) { + return; + } + + var accTree = aAccTree; + + // Support of simplified accessible tree object. + accTree = normalizeAccTreeObj(accTree); + + // Test accessible properties. + for (var prop in accTree) { + var msg = + "Wrong value of property '" + prop + "' for " + prettyName(acc) + "."; + + switch (prop) { + case "actions": { + testActionNames(acc, accTree.actions); + break; + } + + case "attributes": + testAttrs(acc, accTree[prop], true); + break; + + case "absentAttributes": + testAbsentAttrs(acc, accTree[prop]); + break; + + case "interfaces": { + var ifaces = + accTree[prop] instanceof Array ? accTree[prop] : [accTree[prop]]; + for (let i = 0; i < ifaces.length; i++) { + ok( + acc instanceof ifaces[i], + "No " + ifaces[i] + " interface on " + prettyName(acc) + ); + } + break; + } + + case "relations": { + for (var rel in accTree[prop]) { + testRelation(acc, window[rel], accTree[prop][rel]); + } + break; + } + + case "role": + isRole(acc, accTree[prop], msg); + break; + + case "states": + case "extraStates": + case "absentStates": + case "absentExtraStates": { + testStates( + acc, + accTree.states, + accTree.extraStates, + accTree.absentStates, + accTree.absentExtraStates + ); + break; + } + + case "tagName": + is(accTree[prop], acc.DOMNode.tagName, msg); + break; + + case "textAttrs": { + var prevOffset = -1; + for (var offset in accTree[prop]) { + if (prevOffset != -1) { + let attrs = accTree[prop][prevOffset]; + testTextAttrs( + acc, + prevOffset, + attrs, + {}, + prevOffset, + +offset, + true + ); + } + prevOffset = +offset; + } + + if (prevOffset != -1) { + var charCount = getAccessible(acc, [ + nsIAccessibleText, + ]).characterCount; + let attrs = accTree[prop][prevOffset]; + testTextAttrs( + acc, + prevOffset, + attrs, + {}, + prevOffset, + charCount, + true + ); + } + + break; + } + + default: + if (prop.indexOf("todo_") == 0) { + todo(false, msg); + } else if (prop != "children") { + is(acc[prop], accTree[prop], msg); + } + } + } + + // Test children. + if ("children" in accTree && accTree.children instanceof Array) { + var children = acc.children; + var childCount = children.length; + + if (accTree.children.length != childCount) { + for (let i = 0; i < Math.max(accTree.children.length, childCount); i++) { + var accChild = null, + testChild = null; + try { + testChild = accTree.children[i]; + accChild = children.queryElementAt(i, nsIAccessible); + + if (!testChild) { + ok( + false, + prettyName(acc) + + " has an extra child at index " + + i + + " : " + + prettyName(accChild) + ); + continue; + } + + testChild = normalizeAccTreeObj(testChild); + if (accChild.role !== testChild.role) { + ok( + false, + prettyName(accTree) + + " and " + + prettyName(acc) + + " have different children at index " + + i + + " : " + + prettyName(testChild) + + ", " + + prettyName(accChild) + ); + } + info( + "Matching " + + prettyName(accTree) + + " and " + + prettyName(acc) + + " child at index " + + i + + " : " + + prettyName(accChild) + ); + } catch (e) { + ok( + false, + prettyName(accTree) + + " is expected to have a child at index " + + i + + " : " + + prettyName(testChild) + + ", original tested: " + + prettyName(aAccOrElmOrID) + + ", " + + e + ); + } + } + } else { + if (aFlags & kSkipTreeFullCheck) { + for (let i = 0; i < childCount; i++) { + let child = children.queryElementAt(i, nsIAccessible); + testAccessibleTree(child, accTree.children[i], aFlags); + } + return; + } + + // nsIAccessible::firstChild + var expectedFirstChild = + childCount > 0 ? children.queryElementAt(0, nsIAccessible) : null; + var firstChild = null; + try { + firstChild = acc.firstChild; + } catch (e) {} + is( + firstChild, + expectedFirstChild, + "Wrong first child of " + prettyName(acc) + ); + + // nsIAccessible::lastChild + var expectedLastChild = + childCount > 0 + ? children.queryElementAt(childCount - 1, nsIAccessible) + : null; + var lastChild = null; + try { + lastChild = acc.lastChild; + } catch (e) {} + is( + lastChild, + expectedLastChild, + "Wrong last child of " + prettyName(acc) + ); + + for (var i = 0; i < childCount; i++) { + let child = children.queryElementAt(i, nsIAccessible); + + // nsIAccessible::parent + var parent = null; + try { + parent = child.parent; + } catch (e) {} + is(parent, acc, "Wrong parent of " + prettyName(child)); + + // nsIAccessible::indexInParent + var indexInParent = -1; + try { + indexInParent = child.indexInParent; + } catch (e) {} + is(indexInParent, i, "Wrong index in parent of " + prettyName(child)); + + // nsIAccessible::nextSibling + var expectedNextSibling = + i < childCount - 1 + ? children.queryElementAt(i + 1, nsIAccessible) + : null; + var nextSibling = null; + try { + nextSibling = child.nextSibling; + } catch (e) {} + is( + nextSibling, + expectedNextSibling, + "Wrong next sibling of " + prettyName(child) + ); + + // nsIAccessible::previousSibling + var expectedPrevSibling = + i > 0 ? children.queryElementAt(i - 1, nsIAccessible) : null; + var prevSibling = null; + try { + prevSibling = child.previousSibling; + } catch (e) {} + is( + prevSibling, + expectedPrevSibling, + "Wrong previous sibling of " + prettyName(child) + ); + + // Go down through subtree + testAccessibleTree(child, accTree.children[i], aFlags); + } + } + } +} + +/** + * Return true if accessible for the given node is in cache. + */ +function isAccessibleInCache(aNodeOrId) { + var node = getNode(aNodeOrId); + return !!gAccService.getAccessibleFromCache(node); +} + +/** + * Test accessible tree for defunct accessible. + * + * @param aAcc [in] the defunct accessible + * @param aNodeOrId [in] the DOM node identifier for the defunct accessible + */ +function testDefunctAccessible(aAcc, aNodeOrId) { + if (aNodeOrId) { + ok( + !isAccessible(aNodeOrId), + "Accessible for " + aNodeOrId + " wasn't properly shut down!" + ); + } + + var msg = + " doesn't fail for shut down accessible " + prettyName(aNodeOrId) + "!"; + + // firstChild + var success = false; + try { + aAcc.firstChild; + } catch (e) { + success = e.result == Cr.NS_ERROR_FAILURE; + } + ok(success, "firstChild" + msg); + + // lastChild + success = false; + try { + aAcc.lastChild; + } catch (e) { + success = e.result == Cr.NS_ERROR_FAILURE; + } + ok(success, "lastChild" + msg); + + // childCount + success = false; + try { + aAcc.childCount; + } catch (e) { + success = e.result == Cr.NS_ERROR_FAILURE; + } + ok(success, "childCount" + msg); + + // children + success = false; + try { + aAcc.children; + } catch (e) { + success = e.result == Cr.NS_ERROR_FAILURE; + } + ok(success, "children" + msg); + + // nextSibling + success = false; + try { + aAcc.nextSibling; + } catch (e) { + success = e.result == Cr.NS_ERROR_FAILURE; + } + ok(success, "nextSibling" + msg); + + // previousSibling + success = false; + try { + aAcc.previousSibling; + } catch (e) { + success = e.result == Cr.NS_ERROR_FAILURE; + } + ok(success, "previousSibling" + msg); + + // parent + success = false; + try { + aAcc.parent; + } catch (e) { + success = e.result == Cr.NS_ERROR_FAILURE; + } + ok(success, "parent" + msg); +} + +/** + * Convert role to human readable string. + */ +function roleToString(aRole) { + return gAccService.getStringRole(aRole); +} + +/** + * Convert states to human readable string. + */ +function statesToString(aStates, aExtraStates) { + var list = gAccService.getStringStates(aStates, aExtraStates); + + var str = ""; + for (var index = 0; index < list.length - 1; index++) { + str += list.item(index) + ", "; + } + + if (list.length) { + str += list.item(index); + } + + return str; +} + +/** + * Convert event type to human readable string. + */ +function eventTypeToString(aEventType) { + return gAccService.getStringEventType(aEventType); +} + +/** + * Convert relation type to human readable string. + */ +function relationTypeToString(aRelationType) { + return gAccService.getStringRelationType(aRelationType); +} + +function getLoadContext() { + return window.docShell.QueryInterface(Ci.nsILoadContext); +} + +/** + * Return text from clipboard. + */ +function getTextFromClipboard() { + var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( + Ci.nsITransferable + ); + trans.init(getLoadContext()); + if (!trans) { + return ""; + } + + trans.addDataFlavor("text/plain"); + Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard); + + var str = {}; + trans.getTransferData("text/plain", str); + + if (str) { + str = str.value.QueryInterface(Ci.nsISupportsString); + } + if (str) { + return str.data; + } + + return ""; +} + +/** + * Extract DOMNode id from an accessible. If e10s is enabled, DOMNode is not + * present in parent process but, if available, DOMNode id is attached to an + * accessible object. + * @param {nsIAccessible} accessible accessible + * @return {String?} DOMNode id if available + */ +function getAccessibleDOMNodeID(accessible) { + if (accessible instanceof nsIAccessibleDocument) { + // If accessible is a document, trying to find its document body id. + try { + return accessible.DOMNode.body.id; + } catch (e) { + /* This only works if accessible is not a proxy. */ + } + } + try { + return accessible.DOMNode.id; + } catch (e) { + /* This will fail if DOMNode is in different process. */ + } + try { + // When e10s is enabled, accessible will have an "id" property if its + // corresponding DOMNode has an id. If accessible is a document, its "id" + // property corresponds to the "id" of its body element. + return accessible.id; + } catch (e) { + /* This will fail if accessible is not a proxy. */ + } + return null; +} + +/** + * Return pretty name for identifier, it may be ID, DOM node or accessible. + */ +function prettyName(aIdentifier) { + if (aIdentifier instanceof Array) { + let msg = ""; + for (var idx = 0; idx < aIdentifier.length; idx++) { + if (msg != "") { + msg += ", "; + } + + msg += prettyName(aIdentifier[idx]); + } + return msg; + } + + if (aIdentifier instanceof nsIAccessible) { + var acc = getAccessible(aIdentifier); + var domID = getAccessibleDOMNodeID(acc); + let msg = "["; + try { + if (Services.appinfo.browserTabsRemoteAutostart) { + if (domID) { + msg += `DOM node id: ${domID}, `; + } + } else { + msg += `${getNodePrettyName(acc.DOMNode)}, `; + } + msg += "role: " + roleToString(acc.role); + if (acc.name) { + msg += ", name: '" + shortenString(acc.name) + "'"; + } + } catch (e) { + msg += "defunct"; + } + + if (acc) { + msg += ", address: " + getObjAddress(acc); + } + msg += "]"; + + return msg; + } + + if (Node.isInstance(aIdentifier)) { + return "[ " + getNodePrettyName(aIdentifier) + " ]"; + } + + if (aIdentifier && typeof aIdentifier === "object") { + var treeObj = normalizeAccTreeObj(aIdentifier); + if ("role" in treeObj) { + function stringifyTree(aObj) { + var text = roleToString(aObj.role) + ": [ "; + if ("children" in aObj) { + for (var i = 0; i < aObj.children.length; i++) { + var c = normalizeAccTreeObj(aObj.children[i]); + text += stringifyTree(c); + if (i < aObj.children.length - 1) { + text += ", "; + } + } + } + return text + "] "; + } + return `{ ${stringifyTree(treeObj)} }`; + } + return JSON.stringify(aIdentifier); + } + + return " '" + aIdentifier + "' "; +} + +/** + * Shorten a long string if it exceeds MAX_TRIM_LENGTH. + * @param aString the string to shorten. + * @returns the shortened string. + */ +function shortenString(aString, aMaxLength) { + if (aString.length <= MAX_TRIM_LENGTH) { + return aString; + } + + // Trim the string if its length is > MAX_TRIM_LENGTH characters. + var trimOffset = MAX_TRIM_LENGTH / 2; + return ( + aString.substring(0, trimOffset - 1) + + "..." + + aString.substring(aString.length - trimOffset, aString.length) + ); +} + +// ////////////////////////////////////////////////////////////////////////////// +// General Utils +// ////////////////////////////////////////////////////////////////////////////// +/** + * Return main chrome window (crosses chrome boundary) + */ +function getMainChromeWindow(aWindow) { + return aWindow.browsingContext.topChromeWindow; +} + +// ////////////////////////////////////////////////////////////////////////////// +// Private +// ////////////////////////////////////////////////////////////////////////////// + +// ////////////////////////////////////////////////////////////////////////////// +// Accessible general + +function getNodePrettyName(aNode) { + try { + var tag = ""; + if (aNode.nodeType == Node.DOCUMENT_NODE) { + tag = "document"; + } else { + tag = aNode.localName; + if (aNode.nodeType == Node.ELEMENT_NODE && aNode.hasAttribute("id")) { + tag += '@id="' + aNode.getAttribute("id") + '"'; + } + } + + return "'" + tag + " node', address: " + getObjAddress(aNode); + } catch (e) { + return "' no node info '"; + } +} + +function getObjAddress(aObj) { + var exp = /native\s*@\s*(0x[a-f0-9]+)/g; + var match = exp.exec(aObj.toString()); + if (match) { + return match[1]; + } + + return aObj.toString(); +} + +function normalizeAccTreeObj(aObj) { + var key = Object.keys(aObj)[0]; + var roleName = "ROLE_" + key; + if (roleName in nsIAccessibleRole) { + return { + role: nsIAccessibleRole[roleName], + children: aObj[key], + }; + } + return aObj; +} diff --git a/accessible/tests/mochitest/dumbfile.zip b/accessible/tests/mochitest/dumbfile.zip Binary files differnew file mode 100644 index 0000000000..15cb0ecb3e --- /dev/null +++ b/accessible/tests/mochitest/dumbfile.zip diff --git a/accessible/tests/mochitest/elm/a11y.ini b/accessible/tests/mochitest/elm/a11y.ini new file mode 100644 index 0000000000..ceb296670d --- /dev/null +++ b/accessible/tests/mochitest/elm/a11y.ini @@ -0,0 +1,14 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/moz.png + !/dom/media/test/bug461281.ogg + !/dom/security/test/csp/dummy.pdf + +[test_HTMLSpec.html] +[test_figure.html] +[test_listbox.xhtml] +[test_MathMLSpec.html] +[test_nsApplicationAcc.html] +[test_shadowroot.html] +support-files = test_shadowroot_subframe.html diff --git a/accessible/tests/mochitest/elm/test_HTMLSpec.html b/accessible/tests/mochitest/elm/test_HTMLSpec.html new file mode 100644 index 0000000000..a5cc175123 --- /dev/null +++ b/accessible/tests/mochitest/elm/test_HTMLSpec.html @@ -0,0 +1,2024 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML a11y spec tests</title> + <link id="link" rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../actions.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + <script type="application/javascript" + src="../relations.js"></script> + <script type="application/javascript" + src="../name.js"></script> + + <script type="application/javascript"> + const syntheticBrowsingContexts = SpecialPowers.getBoolPref("browser.opaqueResponseBlocking.syntheticBrowsingContext", false); + + async function doTest() { + // //////////////////////////////////////////////////////////////////////// + // HTML:a@href + + var obj = { + role: ROLE_LINK, + states: STATE_LINKED, + actions: "jump", + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText, nsIAccessibleHyperLink ], + children: [ // all kids inherits linked state and action + { + role: ROLE_TEXT_LEAF, + states: STATE_LINKED, + actions: "click ancestor", + }, + ], + }; + testElm("a_href", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:a no @href + + obj = { + todo_role: ROLE_TEXT_CONTAINER, + absentStates: STATE_LINKED, + actions: null, + children: [ + { + role: ROLE_TEXT_LEAF, + absentStates: STATE_LINKED, + actions: null, + }, + ], + }; + testElm("a_nohref", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:abbr contained by HTML:td + + obj = { + role: ROLE_CELL, + attributes: { abbr: "WWW" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + children: [ + { + role: ROLE_TEXT, + children: [ { role: ROLE_TEXT_LEAF } ], + }, + ], + }; + testElm("td_abbr", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:address + + obj = { + role: ROLE_GROUPING, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("address", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:area@href + + obj = { + role: ROLE_LINK, + states: STATE_LINKED, + actions: "jump", + interfaces: [ nsIAccessibleHyperLink ], + children: [], + }; + testElm(getAccessible("imgmap").firstChild, obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:area no @href + + obj = { + todo_role: "ROLE_SHAPE", + absentStates: STATE_LINKED, + children: [], + }; + testElm(getAccessible("imgmap").lastChild, obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:article + obj = { + role: ROLE_ARTICLE, + states: STATE_READONLY, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("article", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:aside + obj = { + role: ROLE_LANDMARK, + attributes: { "xml-roles": "complementary" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("aside", obj); + + // //////////////////////////////////////////////////////////////////////// + obj = { // HTML:audio + role: ROLE_GROUPING, + }; + testElm("audio", obj); + + // //////////////////////////////////////////////////////////////////////// + obj = { // HTML:b contained by paragraph + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { }, + 6: { "font-weight": kBoldFontWeight }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:b text + ], + }; + testElm("b_container", obj); + + // //////////////////////////////////////////////////////////////////////// + obj = { // HTML:bdi contained by paragraph + role: ROLE_PARAGRAPH, + todo_textAttrs: { + 0: { }, + 5: { "writing-mode": "rl" }, + 8: { }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:bdi text + { role: ROLE_TEXT_LEAF }, // plain text + ], + }; + testElm("bdi_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:bdo contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + todo_textAttrs: { + 0: { }, + 6: { "writing-mode": "rl" }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + ], + }; + testElm("bdo_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:blockquote + + obj = { + role: ROLE_BLOCKQUOTE, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + children: [ { role: ROLE_PARAGRAPH } ], + }; + testElm("blockquote", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:br contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + children: [ + { role: ROLE_WHITESPACE }, + { role: ROLE_WHITESPACE } + ] + }; + testElm("br_container", obj); + + // //////////////////////////////////////////////////////////////////////// + obj = { // HTML:button + role: ROLE_PUSHBUTTON, + absentStates: STATE_DEFAULT, + actions: "press", + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("button", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:button@type="submit" (default button) + + obj = { + role: ROLE_PUSHBUTTON, + states: STATE_DEFAULT, + actions: "press", + }; + testElm("button_default", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:canvas + + obj = { + role: ROLE_CANVAS, + }; + testElm("canvas", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:caption under table + + obj = { + role: ROLE_TABLE, + relations: { + RELATION_LABELLED_BY: "caption", + }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText, nsIAccessibleTable ], + children: [ + { + role: ROLE_CAPTION, + relations: { + RELATION_LABEL_FOR: "table", + }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }, + { // td inside thead + role: ROLE_ROW, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + children: [ + { + role: ROLE_COLUMNHEADER, + interfaces: [ nsIAccessibleTableCell, nsIAccessibleText, nsIAccessibleHyperText ], + }, + { role: ROLE_COLUMNHEADER }, + ], + }, + { // td inside tbody + role: ROLE_ROW, + children: [ + { + role: ROLE_ROWHEADER, + interfaces: [ nsIAccessibleTableCell, nsIAccessibleText, nsIAccessibleHyperText ], + }, + { + role: ROLE_CELL, + interfaces: [ nsIAccessibleTableCell, nsIAccessibleText, nsIAccessibleHyperText ], + }, + ], + }, + { // td inside tfoot + role: ROLE_ROW, + }, + ], + }; + testElm("table", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:cite contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { }, + 6: { "font-style": "italic" }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:cite text + ], + }; + testElm("cite_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:code contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { }, + 6: { "font-family": kMonospaceFontFamily }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:code text + ], + }; + testElm("code_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:col and HTML:colgroup under table + + obj = + { TABLE: [ + { ROW: [ + { role: ROLE_CELL }, + { role: ROLE_CELL }, + { role: ROLE_CELL }, + ] }, + ] }; + testElm("colNcolgroup_table", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:data contained by paragraph + + obj = + { PARAGRAPH: [ + { TEXT_LEAF: [] }, // HTML:data text + ] }; + testElm("data_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:datalist associated with input + + todo(false, "datalist and summary tree hierarchy test missed"); + + // //////////////////////////////////////////////////////////////////////// + // HTML:dd, HTML:dl, HTML:dd + + obj = { + role: ROLE_DEFINITION_LIST, + states: STATE_READONLY, + children: [ // dl + { + role: ROLE_TERM, + states: STATE_READONLY, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + children: [ // dt + { role: ROLE_TEXT_LEAF }, + ], + }, + { + role: ROLE_DEFINITION, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + children: [ // dd + { role: ROLE_TEXT_LEAF }, + ], + }, + ], + }; + testElm("dl", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:del contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_CONTENT_DELETION }, + ], + }; + testElm("del_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:details with open state + + obj = { + role: ROLE_DETAILS, + children: [ + { + role: ROLE_SUMMARY, + states: STATE_EXPANDED, + actions: "collapse", + }, + { role: ROLE_PARAGRAPH }, + ], + }; + testElm("details", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:details with closed (default) state + + obj = { + role: ROLE_DETAILS, + children: [ + { + role: ROLE_SUMMARY, + states: STATE_COLLAPSED, + actions: "expand", + }, + ], + }; + testElm("details_closed", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:dfn contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { "font-style": "italic" }, + 12: { }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // HTML:dfn text + { role: ROLE_TEXT_LEAF }, // plain text + ], + }; + testElm("dfn_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:dialog + + // XXX: Remove the pushing of the pref and just run the test once the + // dialog element is enabled by default. + await SpecialPowers.pushPrefEnv({ set: [["dom.dialog_element.enabled", true]] }); + obj = { + role: ROLE_DIALOG, + children: [ + { role: ROLE_TEXT_LEAF }, + ], + }; + testElm("dialog", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:div + + obj = { + role: ROLE_SECTION, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + ], + }; + testElm("div", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:em in a paragraph + + obj = { + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { }, + 6: { "font-style": "italic" }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:em text + ], + }; + testElm("em_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:embed + + if (syntheticBrowsingContexts) { + obj = { + INTERNAL_FRAME: [ { + DOCUMENT: [ { + role: ROLE_GRAPHIC, + interfaces: [ nsIAccessibleImage ], + } ], + } ], + }; + } else { + obj = { + role: ROLE_GRAPHIC, + interfaces: [ nsIAccessibleImage ], + }; + } + testElm("embed_png", obj); + + obj = { + INTERNAL_FRAME: [ { + DOCUMENT: [ { + role: ROLE_PARAGRAPH, + } ], + } ], + }; + testElm("embed_html", obj); + + obj = { + INTERNAL_FRAME: [ { + DOCUMENT: [ { + } ], + } ], + }; + testElm("embed_pdf", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:fieldset and HTML:legend + + obj = { + role: ROLE_GROUPING, + name: "legend", + relations: { + RELATION_LABELLED_BY: "legend", + }, + children: [ + { + role: ROLE_LABEL, + name: "legend", + relations: { + RELATION_LABEL_FOR: "fieldset", + }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }, + { + role: ROLE_ENTRY, + }, + ], + }; + testElm("fieldset", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:figure and HTML:figcaption + + obj = { + role: ROLE_FIGURE, + attributes: { "xml-roles": "figure" }, + relations: { + RELATION_LABELLED_BY: "figcaption", + }, + children: [ + { role: ROLE_GRAPHIC }, + { + role: ROLE_CAPTION, + relations: { + RELATION_LABEL_FOR: "figure", + }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }, + ], + }; + testElm("figure", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:footer + + obj = { + role: ROLE_LANDMARK, + attributes: { "xml-roles": "contentinfo" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("footer", obj); + + obj = { + role: ROLE_SECTION, + absentAttributes: { "xml-roles": "contentinfo" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("footer_in_article", obj); + testElm("footer_in_aside", obj); + testElm("footer_in_main", obj); + testElm("footer_in_nav", obj); + testElm("footer_in_section", obj); + testElm("footer_in_blockquote", obj); + testElm("footer_in_details", obj); + testElm("footer_in_dialog", obj); + testElm("footer_in_fieldset", obj); + testElm("footer_in_figure", obj); + testElm("footer_in_td", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:form + + obj = { + role: ROLE_FORM, + absentAttributes: { "xml-roles": "form" }, + }; + testElm("form", obj); + + // HTML:form with an accessible name + + obj = { + role: ROLE_FORM_LANDMARK, + attributes: { "xml-roles": "form" }, + }; + testElm("named_form", obj); + + // //////////////////////////////////////////////////////////////////////// + // // HTML:frameset, HTML:frame and HTML:iframe + + obj = { + INTERNAL_FRAME: [ { // HTML:iframe + DOCUMENT: [ { + INTERNAL_FRAME: [ { // HTML:frame + DOCUMENT: [ { role: ROLE_TEXT_LEAF} ], + } ], + } ], + } ], + }; + testElm("frameset_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:h1, HTML:h2, HTML:h3, HTML:h4, HTML:h5, HTML:h6 + + function headingWithLevel(i) { + return { + role: ROLE_HEADING, + attributes: { "level": i.toString() }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + } + + for (let level = 1; level <= 6; ++level) { + testElm("h" + level, headingWithLevel(level)); + for (const ancestor of ["section", "article", "aside", "nav"]) { + testElm("h" + level + "_in_" + ancestor, headingWithLevel(level)); + testElm("h" + level + "_in_" + ancestor + "_in_hgroup", headingWithLevel(level)); + } + } + + // //////////////////////////////////////////////////////////////////////// + // HTML:header + + obj = { + role: ROLE_LANDMARK, + attributes: { "xml-roles": "banner" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("header", obj); + + obj = { + role: ROLE_SECTION, + absentAttributes: { "xml-roles": "banner" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("header_in_article", obj); + testElm("header_in_aside", obj); + testElm("header_in_main", obj); + testElm("header_in_nav", obj); + testElm("header_in_section", obj); + testElm("header_in_blockquote", obj); + testElm("header_in_details", obj); + testElm("header_in_dialog", obj); + testElm("header_in_fieldset", obj); + testElm("header_in_figure", obj); + testElm("header_in_td", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:hr + + obj = { + role: ROLE_SEPARATOR, + }; + testElm("hr", obj); + + // //////////////////////////////////////////////////////////////////////// + obj = { // HTML:i contained by paragraph + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { }, + 6: { "font-style": "italic" }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:i text + ], + }; + testElm("i_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:img + + obj = { + role: ROLE_GRAPHIC, + interfaces: [ nsIAccessibleImage ], + }; + testElm("img", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="button" + + obj = { + role: ROLE_PUSHBUTTON, + absentStates: STATE_DEFAULT, + }; + testElm("input_button", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="checkbox" + + obj = { + role: ROLE_CHECKBUTTON, + states: STATE_CHECKABLE, + absentStates: STATE_CHECKED, + actions: "check", + }; + testElm("input_checkbox", obj); + + obj = { + role: ROLE_CHECKBUTTON, + states: STATE_CHECKABLE | STATE_CHECKED, + actions: "uncheck", + }; + testElm("input_checkbox_true", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="file" + + obj = { + GROUPING: [ + { role: ROLE_PUSHBUTTON }, + { role: ROLE_LABEL }, + ], + }; + testElm("input_file", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="image" + + obj = { + role: ROLE_PUSHBUTTON, + absentStates: STATE_DEFAULT, + actions: "press", + }; + testElm("input_image", obj); + testElm("input_image_display", obj); + testElm("input_submit", obj); + + obj = { + role: ROLE_PUSHBUTTON, + actions: "press", + states: STATE_DEFAULT, + }; + testElm("input_image_default", obj); + testElm("input_submit_default", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="number" and etc + + obj = { + role: ROLE_SPINBUTTON, + interfaces: [ nsIAccessibleValue, nsIAccessibleText, nsIAccessibleEditableText ], + children: [ + { role: ROLE_TEXT_LEAF }, + ], + }; + testElm("input_number", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="text" and etc + + obj = { + role: ROLE_ENTRY, + extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE, + actions: "activate", + interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ], + children: [ + { role: ROLE_TEXT_LEAF }, + ], + }; + testElm("input_email", obj); + testElm("input_search", obj); + testElm("input_tel", obj); + testElm("input_text", obj); + testElm("input_url", obj); + + // //////////////////////////////////////////////////////////////////////// + // input @type="text" with placeholder attribute + + // First: Label and placeholder, text is the same, no attribute. + obj = { + role: ROLE_ENTRY, + name: "Your name", + extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE, + actions: "activate", + absentAttributes: { placeholder: "Your name" }, + interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ], + children: [], + }; + testElm("input_placeholder_same", obj); + + // Second: Label and placeholder, text is different, attribute. + obj = { + role: ROLE_ENTRY, + name: "First name:", + extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE, + actions: "activate", + attributes: { placeholder: "Enter your first name" }, + interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ], + children: [], + }; + testElm("input_placeholder_different", obj); + + // Third: placeholder only, text is name, no attribute. + obj = { + role: ROLE_ENTRY, + name: "Date of birth", + extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE, + actions: "activate", + absentAttributes: { placeholder: "Date of birth" }, + interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ], + children: [], + }; + testElm("input_placeholder_only", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="password" + + obj = { + role: ROLE_PASSWORD_TEXT, + states: STATE_PROTECTED, + extraStates: EXT_STATE_EDITABLE, + actions: "activate", + children: [ + { + role: ROLE_TEXT_LEAF, + }, + ], + }; + testElm("input_password", obj); + ok(getAccessible("input_password").firstChild.name != "44", + "text leaf for password shouldn't have its real value as its name!"); + + obj = { + role: ROLE_PASSWORD_TEXT, + states: STATE_PROTECTED | STATE_READONLY, + actions: "activate", + children: [ + { + role: ROLE_TEXT_LEAF, + }, + ], + }; + testElm("input_password_readonly", obj); + ok(getAccessible("input_password_readonly").firstChild.name != "44", + "text leaf for password shouldn't have its real value as its name!"); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="radio" + + obj = { + role: ROLE_RADIOBUTTON, + states: STATE_CHECKABLE, + absentStates: STATE_CHECKED, + actions: "select", + }; + testElm("input_radio", obj); + + obj = { + role: ROLE_RADIOBUTTON, + states: STATE_CHECKABLE | STATE_CHECKED, + actions: "select", + }; + testElm("input_radio_true", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="range" + + obj = { + role: ROLE_SLIDER, + }; + testElm("input_range", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="reset" + + obj = { + role: ROLE_PUSHBUTTON, + actions: "press", + absentStates: STATE_DEFAULT, + }; + testElm("input_reset", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="time" + + obj = { + role: ROLE_TIME_EDITOR, + name: "time label", + attributes: { "text-input-type": "time" }, + children: [ + { role: ROLE_SPINBUTTON }, + { role: ROLE_TEXT_LEAF }, + { role: ROLE_SPINBUTTON }, + { role: ROLE_TEXT_LEAF }, + { role: ROLE_ENTRY }, + ], + }; + testElm("input_time", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="date" + + obj = { + role: ROLE_DATE_EDITOR, + name: "date label", + attributes: { "text-input-type": "date" }, + children: [ + { role: ROLE_SPINBUTTON }, + { role: ROLE_TEXT_LEAF }, + { role: ROLE_SPINBUTTON }, + { role: ROLE_TEXT_LEAF }, + { role: ROLE_SPINBUTTON }, + { role: ROLE_PUSHBUTTON }, + ], + }; + testElm("input_date", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:input@type="datetime-local" + + obj = { + role: ROLE_DATE_EDITOR, + name: "datetime-local label", + attributes: { "text-input-type": "datetime-local" }, + children: [ + { role: ROLE_SPINBUTTON }, // Month + { role: ROLE_TEXT_LEAF }, // "/"" + { role: ROLE_SPINBUTTON }, // Day + { role: ROLE_TEXT_LEAF }, // "/" + { role: ROLE_SPINBUTTON }, // Year + { role: ROLE_TEXT_LEAF }, // " " + { role: ROLE_SPINBUTTON }, // Hours + { role: ROLE_TEXT_LEAF }, // ":" + { role: ROLE_SPINBUTTON }, // Minutes + { role: ROLE_TEXT_LEAF }, // " " + { role: ROLE_ENTRY }, // "AM" or "PM" + { role: ROLE_PUSHBUTTON }, // Calendar + ], + }; + testElm("input_datetime_local", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:ins contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_CONTENT_INSERTION }, + ], + }; + testElm("ins_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:kbd contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { }, + 6: { "font-family": kMonospaceFontFamily }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:kbd text + ], + }; + testElm("kbd_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:label + + obj = { + role: ROLE_LABEL, + todo_relations: { + RELATION_LABEL_FOR: "label_input", + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { + role: ROLE_ENTRY, + relations: { + RELATION_LABELLED_BY: "label", + }, + }, + ], + }; + testElm("label", obj); + + obj = { + role: ROLE_LABEL, + relations: { + RELATION_LABEL_FOR: "label_for_input", + }, + }; + testElm("label_for", obj); + + obj = { + role: ROLE_ENTRY, + relations: { + RELATION_LABELLED_BY: "label_for", + }, + }; + testElm("label_for_input", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:ul, HTML:ol, HTML:li + + obj = { // ul or ol + role: ROLE_LIST, + states: STATE_READONLY, + children: [ + { // li + role: ROLE_LISTITEM, + states: STATE_READONLY, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }, + ], + }; + testElm("ul", obj); + testElm("ol", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:link + + ok(!isAccessible("link"), "link element is not accessible"); + + // //////////////////////////////////////////////////////////////////////// + // HTML:main + + obj = { + role: ROLE_LANDMARK, + attributes: { "xml-roles": "main" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("main", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:map + + ok(!isAccessible("map_imagemap"), + "map element is not accessible if used as an image map"); + + obj = { + role: ROLE_TEXT_CONTAINER, + }; + testElm("map", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:mark contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_MARK, // HTML:mark text + attributes: { "xml-roles": "mark" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + textAttrs: { + 0: { }, + } + } + ], + }; + testElm("mark_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:math + + obj = { + role: ROLE_MATHML_MATH, + }; + testElm("math", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:menu + + obj = { + role: ROLE_LIST, // menu + children: [ + { role: ROLE_LISTITEM, + children: [ // home + { role: ROLE_LISTITEM_MARKER }, + { role: ROLE_TEXT_LEAF } + ] + }, + { + role: ROLE_LISTITEM, + children: [ + { role: ROLE_LISTITEM_MARKER }, + { role: ROLE_TEXT_LEAF }, // about + { + role: ROLE_LIST, // menu + children: [ + { role: ROLE_LISTITEM, + children: [ + { role: ROLE_LISTITEM_MARKER }, + { role: ROLE_TEXT_LEAF } // our story + ] + }, + ] + }, + ] + }, + ] + }; + + testElm("menu", obj); + obj = { + role: ROLE_LIST, + children: [ + { + role: ROLE_LISTITEM, + children: [ + { role: ROLE_LISTITEM_MARKER }, + { + role: ROLE_PUSHBUTTON, + children: [ + { role: ROLE_TEXT_LEAF } + ] + }, + { + role: ROLE_LIST, + children: [ + { + role: ROLE_LISTITEM, + children: [ + { role: ROLE_LISTITEM_MARKER }, + { + role: ROLE_PUSHBUTTON, + children: [ + { role: ROLE_TEXT_LEAF } + ] + } + ] + }, + ] + } + ] + } + ] + }; + testElm("menu1", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:meter + obj = { + role: ROLE_METER + }; + testElm("meter", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:nav + + obj = { + role: ROLE_LANDMARK, + attributes: { "xml-roles": "navigation" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("nav", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:object and HTML:param + + if (syntheticBrowsingContexts) { + obj = { + INTERNAL_FRAME: [ { + DOCUMENT: [ { + role: ROLE_GRAPHIC, + interfaces: [ nsIAccessibleImage ], + } ], + } ], + }; + } else { + obj = { + role: ROLE_GRAPHIC, + interfaces: [ nsIAccessibleImage ], + }; + } + testElm("object_png", obj); + + obj = { + INTERNAL_FRAME: [ { + DOCUMENT: [ { + role: ROLE_PARAGRAPH, + } ], + } ], + }; + testElm("object_html", obj); + + obj = { + INTERNAL_FRAME: [ { + DOCUMENT: [ { + } ], + } ], + }; + testElm("object_pdf", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:select, HTML:optgroup and HTML:option + + obj = { // HMTL:select@size > 1 + role: ROLE_LISTBOX, + states: STATE_FOCUSABLE, + absentStates: STATE_MULTISELECTABLE, + interfaces: [ nsIAccessibleSelectable ], + children: [ + { GROUPING: [ // HTML:optgroup + { role: ROLE_STATICTEXT }, + { role: ROLE_OPTION }, // HTML:option + { role: ROLE_OPTION }, + ] }, + { + role: ROLE_OPTION, + states: STATE_FOCUSABLE, + actions: "select", + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }, + ], + }; + testElm("select_listbox", obj); + + obj = { // HTML:select@multiple + role: ROLE_LISTBOX, + states: STATE_FOCUSABLE | STATE_MULTISELECTABLE, + children: [ + { role: ROLE_OPTION }, + { role: ROLE_OPTION }, + { role: ROLE_OPTION }, + ], + }; + testElm("select_listbox_multiselectable", obj); + + obj = { // HTML:select + role: ROLE_COMBOBOX, + states: STATE_FOCUSABLE, + children: [ + { + role: ROLE_COMBOBOX_LIST, + children: [ + { role: ROLE_COMBOBOX_OPTION }, + { role: ROLE_COMBOBOX_OPTION }, + { role: ROLE_COMBOBOX_OPTION }, + ], + }, + ], + }; + testElm("select_combobox", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:output + + obj = { + role: ROLE_STATUSBAR, + attributes: { "live": "polite" }, + todo_relations: { + RELATION_CONTROLLED_BY: "output_input", + }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("output", obj); + + obj = { + role: ROLE_ENTRY, + relations: { + RELATION_CONTROLLER_FOR: "output", + }, + }; + testElm("output_input", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:pre + + obj = { + role: ROLE_TEXT_CONTAINER, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("pre", obj); + + // ///////////////////////////////////////////////////////////////////////// + // HTML:progress + + obj = { + role: ROLE_PROGRESSBAR, + absentStates: STATE_MIXED, + interfaces: [ nsIAccessibleValue ], + }; + testElm("progress", obj); + + obj = { + role: ROLE_PROGRESSBAR, + states: STATE_MIXED, + }; + testElm("progress_indeterminate", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:q + + obj = { + role: ROLE_TEXT, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + children: [ + { role: ROLE_STATICTEXT }, // left quote + { role: ROLE_TEXT_LEAF }, // quoted text + { role: ROLE_STATICTEXT }, // right quote + ], + }; + testElm("q", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:ruby + + todo(isAccessible("ruby"), "ruby element is not accessible"); + + // //////////////////////////////////////////////////////////////////////// + // HTML:s contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { }, + 6: { "text-line-through-style": "solid" }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:i text + ], + }; + testElm("s_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:samp contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:samp text + ], + }; + testElm("samp_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:section without an accessible name + + obj = { + role: ROLE_SECTION, + absentAttributes: { "xml-roles": "region" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("section", obj); + + // HTML:section with an accessible name + + obj = { + role: ROLE_REGION, + attributes: { "xml-roles": "region" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("named_section", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:small contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { }, + 6: { "font-size": "10pt" }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:small text + ], + }; + testElm("small_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:source + + ok(!isAccessible("source"), "source element is not accessible"); + + // //////////////////////////////////////////////////////////////////////// + // HTML:span + + ok(!isAccessible("span"), "span element is not accessible"); + + // //////////////////////////////////////////////////////////////////////// + // html:span with a title attribute, which should make it accessible. + obj = { + role: ROLE_TEXT, + }; + testElm("span_explicit", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:strong contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:strong text + ], + }; + testElm("strong_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:sub + obj = { + role: ROLE_SUBSCRIPT + }; + testElm("sub", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:sup + obj = { + role: ROLE_SUPERSCRIPT + }; + testElm("sup", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:sub contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { + role: ROLE_SUBSCRIPT, // HTML:sub + children: [ + { role: ROLE_TEXT_LEAF } // HTML:sub text + ] + } + ], + }; + testElm("sub_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:sup contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { + role: ROLE_SUPERSCRIPT, // HTML:sup + children: [ + { role: ROLE_TEXT_LEAF } // HTML:sup text + ] + } + ], + }; + testElm("sup_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:svg + + obj = { + todo_role: ROLE_GRAPHIC, + }; + testElm("svg", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:textarea + + obj = { + role: ROLE_ENTRY, + extraStates: EXT_STATE_MULTI_LINE | EXT_STATE_EDITABLE, + actions: "activate", + interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ], + }; + testElm("textarea", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:time + + obj = { + role: ROLE_TEXT, + attributes: { "xml-roles": "time", "datetime": "2001-05-15 19:00" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("time", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:u contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { }, + 6: { "text-underline-style": "solid" }, + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:u text + ], + }; + testElm("u_container", obj); + + // //////////////////////////////////////////////////////////////////////// + // HTML:var contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:var text + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF }, // HTML:var text + ], + }; + testElm("var_container", obj); + + // //////////////////////////////////////////////////////////////////////// + obj = { // HTML:video + role: ROLE_GROUPING, + }; + testElm("video", obj); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + + </script> +</head> +<body> + + <a target="_blank" + title="Implement figure and figcaption accessibility" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=658272"> + Mozilla Bug 658272 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <a id="a_href" href="www.mozilla.com">mozilla site</a> + <a id="a_nohref">anchor</a> + <table> + <tr> + <td id="td_abbr"><abbr title="World Wide Web">WWW</abbr></td> + </tr> + </table> + <address id="address"> + Mozilla Foundation<br> + 1981 Landings Drive<br> + Building K<br> + Mountain View, CA 94043-0801<br> + USA + </address> + + <map name="atoz_map"> + <area id="area_href" + href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b" + coords="17,0,30,14" alt="b" shape="rect"> + <area id="area_nohref" + coords="0,0,13,14" alt="a" shape="rect"> + </map> + <img id="imgmap" width="447" height="15" + usemap="#atoz_map" + src="../letters.gif"> + + <article id="article">A document</article> + <audio id="audio" controls="true"> + <source id="source" src="../bug461281.ogg" type="video/ogg"> + </audio> + + <aside id="aside"> + <p>Some content related to an <article></p> + </aside> + + <p id="b_container">normal<b>bold</b></p> + <p id="bdi_container">User <bdi>إيان</bdi>: 90 points</p> + <p id="bdo_container"><bdo dir="rtl">This text will go right to left.</bdo></p> + + <blockquote id="blockquote" cite="http://developer.mozilla.org"> + <p>This is a quotation taken from the Mozilla Developer Center.</p> + </blockquote> + + <!-- two BRs, both will be present --> + <p id="br_container"><br><br></p> + + <button id="button">button</button> + <form> + <button id="button_default" type="submit">button</button> + </form> + + <canvas id="canvas"></canvas> + + <table id="table"> + <caption id="caption">caption</caption> + <thead> + <tr> + <th>col1</th><th>col2</th> + </tr> + </thead> + <tbody> + <tr> + <th>col1</th><td>cell2</td> + </tr> + </tbody> + <tfoot> + <tr> + <td>cell5</td><td>cell6</td> + </tr> + </tfoot> + </table> + + <p id="cite_container">normal<cite>cite</cite></p> + <p id="code_container">normal<code>code</code></p> + + <table id="colNcolgroup_table"> + <colgroup> + <col> + <col span="2"> + </colgroup> + <tr> + <td>Lime</td> + <td>Lemon</td> + <td>Orange</td> + </tr> + </table> + + <p id="data_container"><data value="8">Eight</data></p> + + <datalist id="datalist"> + <summary id="summary">details</summary> + <option>Paris</option> + <option>San Francisco</option> + </datalist> + <input id="autocomplete_datalist" list="datalist"> + + <dl id="dl"> + <dt>item1</dt><dd>description</dd> + </dl> + + <p id="del_container">normal<del>Removed</del></p> + + <details id="details" open="open"> + <summary>Information</summary> + <p>If your browser supports this element, it should allow you to expand and collapse these details.</p> + </details> + + <details id="details_closed"> + <summary>Information</summary> + <p>If your browser supports this element, it should allow you to expand and collapse these details.</p> + </details> + + <p id="dfn_container"><dfn id="def-internet">The Internet</dfn> is a global + system of interconnected networks that use the Internet Protocol Suite (TCP/IP) + to serve billions of users worldwide.</p> + + <dialog id="dialog" open="true">This is a dialog</dialog> + + <div id="div">div</div> + + <p id="em_container">normal<em>emphasis</em></p> + + <embed id="embed_png" type="image/png" src="../moz.png" + width="300" height="300"> + </embed> + <embed id="embed_html" type="text/html" src="../longdesc_src.html" + width="300" height="300"> + </embed> + <embed id="embed_pdf" type="application/pdf" src="../dummy.pdf" + width="300" height="300"> + </embed> + + <fieldset id="fieldset"> + <legend id="legend">legend</legend> + <input /> + </fieldset> + + <!-- Depending on whether or not the image is cached, layout may be able to + optimize away spaces between the figure, img and figcaption tags. As + such, we should keep everything on one line to get consistent results. + --> + <figure id="figure"><img src="../moz.png" alt="An awesome picture"><figcaption id="figcaption">Caption for the awesome picture</figcaption></figure> + + <footer id="footer">Some copyright info</footer> + <article> + <footer id="footer_in_article">Some copyright info</footer> + </article> + <aside> + <footer id="footer_in_aside">Some copyright info</footer> + </aside> + <main> + <footer id="footer_in_main">Some copyright info</footer> + </main> + <nav> + <footer id="footer_in_nav">Some copyright info</footer> + </nav> + <section> + <footer id="footer_in_section">Some copyright info</footer> + </section> + <blockquote> + <footer id="footer_in_blockquote">Some copyright info</footer> + </blockquote> + <details open="true"> + <footer id="footer_in_details">Some copyright info</footer> + </details> + <dialog open="true"> + <footer id="footer_in_dialog">Some copyright info</footer> + </dialog> + <fieldset> + <footer id="footer_in_fieldset">Some copyright info</footer> + </fieldset> + <figure> + <footer id="footer_in_figure">Some copyright info</footer> + </figure> + <table><tr><td> + <footer id="footer_in_td">Some copyright info</footer> + </td></tr></table> + + <form id="form"></form> + <form id="named_form" aria-label="New form"></form> + + <iframe id="frameset_container" + src="data:text/html,<html><frameset><frame src='data:text/html,hi'></frame></frameset></html>"> + </iframe> + + <h1 id="h1">heading1</h1> + <h2 id="h2">heading2</h2> + <h3 id="h3">heading3</h3> + <h4 id="h4">heading4</h4> + <h5 id="h5">heading5</h5> + <h6 id="h6">heading6</h6> + + <header id="header">A logo</header> + <article> + <header id="header_in_article">Not logo</header> + <h1 id="h1_in_article">heading1</h1> + <h2 id="h2_in_article">heading2</h2> + <h3 id="h3_in_article">heading3</h3> + <h4 id="h4_in_article">heading4</h4> + <h5 id="h5_in_article">heading5</h5> + <h6 id="h6_in_article">heading6</h6> + <hgroup> + <h1 id="h1_in_article_in_hgroup">heading1</h1> + <h2 id="h2_in_article_in_hgroup">heading2</h2> + <h3 id="h3_in_article_in_hgroup">heading3</h3> + <h4 id="h4_in_article_in_hgroup">heading4</h4> + <h5 id="h5_in_article_in_hgroup">heading5</h5> + <h6 id="h6_in_article_in_hgroup">heading6</h6> + </hgroup> + </article> + <aside> + <header id="header_in_aside">Not logo</header> + <h1 id="h1_in_aside">heading1</h1> + <h2 id="h2_in_aside">heading2</h2> + <h3 id="h3_in_aside">heading3</h3> + <h4 id="h4_in_aside">heading4</h4> + <h5 id="h5_in_aside">heading5</h5> + <h6 id="h6_in_aside">heading6</h6> + <hgroup> + <h1 id="h1_in_aside_in_hgroup">heading1</h1> + <h2 id="h2_in_aside_in_hgroup">heading2</h2> + <h3 id="h3_in_aside_in_hgroup">heading3</h3> + <h4 id="h4_in_aside_in_hgroup">heading4</h4> + <h5 id="h5_in_aside_in_hgroup">heading5</h5> + <h6 id="h6_in_aside_in_hgroup">heading6</h6> + </hgroup> + </aside> + <main> + <header id="header_in_main">Not logo</header> + </main> + <nav> + <header id="header_in_nav">Not logo</header> + <h1 id="h1_in_nav">heading1</h1> + <h2 id="h2_in_nav">heading2</h2> + <h3 id="h3_in_nav">heading3</h3> + <h4 id="h4_in_nav">heading4</h4> + <h5 id="h5_in_nav">heading5</h5> + <h6 id="h6_in_nav">heading6</h6> + <hgroup> + <h1 id="h1_in_nav_in_hgroup">heading1</h1> + <h2 id="h2_in_nav_in_hgroup">heading2</h2> + <h3 id="h3_in_nav_in_hgroup">heading3</h3> + <h4 id="h4_in_nav_in_hgroup">heading4</h4> + <h5 id="h5_in_nav_in_hgroup">heading5</h5> + <h6 id="h6_in_nav_in_hgroup">heading6</h6> + </hgroup> + </nav> + <section> + <header id="header_in_section">Not logo</header> + <h1 id="h1_in_section">heading1</h1> + <h2 id="h2_in_section">heading2</h2> + <h3 id="h3_in_section">heading3</h3> + <h4 id="h4_in_section">heading4</h4> + <h5 id="h5_in_section">heading5</h5> + <h6 id="h6_in_section">heading6</h6> + <hgroup> + <h1 id="h1_in_section_in_hgroup">heading1</h1> + <h2 id="h2_in_section_in_hgroup">heading2</h2> + <h3 id="h3_in_section_in_hgroup">heading3</h3> + <h4 id="h4_in_section_in_hgroup">heading4</h4> + <h5 id="h5_in_section_in_hgroup">heading5</h5> + <h6 id="h6_in_section_in_hgroup">heading6</h6> + </hgroup> + </section> + <blockquote> + <header id="header_in_blockquote">Not logo</header> + </blockquote> + <details open="true"> + <header id="header_in_details">Not logo</header> + </details> + <dialog open="true"> + <header id="header_in_dialog">Not logo</header> + </dialog> + <fieldset> + <header id="header_in_fieldset">Not logo</header> + </fieldset> + <figure> + <header id="header_in_figure">Not logo</header> + </figure> + <table><tr><td> + <header id="header_in_td">Not logo</header> + </td></tr></table> + + <hr id="hr"> + <p id="i_container">normal<i>italic</i></p> + <img id="img" src="../moz.png"> + + <input id="input_button" type="button" value="Button"> + <input id="input_checkbox" type="checkbox"> + <input id="input_checkbox_true" type="checkbox" checked> + <input id="input_file" type="file"> + <input id="input_image" type="image"> + <input id="input_image_display" type="image" style="display: block"> + <form> + <input id="input_image_default" type="image"> + </form> + <input id="input_submit" type="submit"> + <form> + <input id="input_submit_default" type="submit"> + </form> + <input id="input_number" type="number" value="44"> + <input id="input_text" type="text" value="hi"> + <form> + <label for="input_placeholder_same">Your name</label> + <input id="input_placeholder_same" placeholder="Your name"/> + <label for="input_placeholder_different">First name:</label> + <input id="input_placeholder_different" placeholder="Enter your first name"/> + <input id="input_placeholder_only" placeholder="Date of birth"/> + </form> + <input id="input_search" type="search" value="cats"> + <input id="input_email" type="email" value="me@mozilla.com"> + <input id="input_tel" type="tel" value="111.111.1111"> + <input id="input_url" type="url" value="www.mozilla.com"> + <input id="input_password" type="password" value="44"> + <input id="input_password_readonly" type="password" value="44" readonly> + <input id="input_radio" type="radio"> + <input id="input_radio_true" type="radio" checked> + <input id="input_range" type="range"> + <form> + <input id="input_reset" type="reset"> + </form> + <label>time label + <input id="input_time" type="time" value="23:23"> + </label> + <label>date label + <input id="input_date" type="date" value="2017-11-10"> + </label> + <label>datetime-local label + <input id="input_datetime_local" type="datetime-local" value="2017-11-10T23:23"> + </label> + + <p id="ins_container">normal<ins>Inserted</ins></p> + <p id="kbd_container">normal<kbd>cmd</kbd></p> + + <label id="label">label<input id="label_input"></label> + <label id="label_for" for="label_for_input">label</label> + <input id="label_for_input"> + + <ul id="ul"> + <li>item1</li> + </ul> + <ol id="ol"> + <li>item1</li> + </ol> + + <main id="main">main</main> + + <map id="map_imagemap" name="atoz_map"> + <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b" + coords="17,0,30,14" alt="b" shape="rect"> + <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a" + coords="0,0,13,14" alt="a" shape="rect"> + </map> + <img id="imgmap" width="447" height="15" + usemap="#atoz_map" + src="../letters.gif"> + + <map id="map" title="Navigation Bar" name="mapgroup"> + <p> + [<a href="#how">Bypass navigation bar</a>] + [<a href="home.html">Home</a>] + </p> + </map> + + <p id="mark_container">normal<mark>highlighted</mark></p> + + <math id="math"> + <mrow> + <mrow> + <msup> + <mi>a</mi> + <mn>2</mn> + </msup> + <mo>+</mo> + <msup> + <mi>b</mi> + <mn>2</mn> + </msup> + </mrow> + <mo>=</mo> + <msup> + <mi>c</mi> + <mn>2</mn> + </msup> + </mrow> + </math> + + <menu id="menu"> + <li>Home</li> + <li>About + <menu> + <li>Our Story</li> + </menu> + </li> + </menu> + + <menu id="menu1"> + <li> + <button>File</button> + <menu> + <li> + <button type="button" onclick="new()">New...</button> + </li> + </menu> + </li> + </menu> + + <meter id="meter" min="0" max="1000" low="300" high="700" value="200">200 Euro</meter> + + <nav id="nav"> + <ul> + <li><a href="#">Home</a></li> + <li><a href="#">About</a></li> + <li><a href="#">Contact</a></li> + </ul> + </nav> + + <object id="object_png" type="image/png" data="../moz.png" + width="300" height="300"> + </object> + <object id="object_html" type="text/html" data="../longdesc_src.html" + width="300" height="300"> + </object> + <object id="object_pdf" type="application/pdf" data="../dummy.pdf" + width="300" height="300"> + </object> + + <select id="select_listbox" size="4"> + <optgroup label="Colors"> + <option>Red</option> + <option>Blue</option> + </optgroup> + <option>Animal</option> + </select> + + <select id="select_listbox_multiselectable" multiple> + <option>Red</option> + <option>Blue</option> + <option>Green</option> + </select> + + <select id="select_combobox"> + <option>Red</option> + <option>Blue</option> + <option>Green</option> + </select> + + <input id="output_input"> + <output id="output" for="output_input"></output> + + <pre id="pre">pre</pre> + + <progress id="progress" min="0" value="21" max="42"></progress> + <progress id="progress_indeterminate"></progress> + + <q id="q" cite="http://en.wikipedia.org/wiki/Kenny_McCormick#Cultural_impact"> + Oh my God, they killed Kenny! + </q> + + <ruby id="ruby"> + 漢 <rp>(</rp><rt>Kan</rt><rp>)</rp> + 字 <rp>(</rp><rt>ji</rt><rp>)</rp> + </ruby> + + <p id="s_container">normal<s>striked</s></p> + <p id="samp_container">normal<samp>sample</samp></p> + <section id="section">section</section> + <section id="named_section" aria-label="foo">named section</section> + <p id="small_container">normal<small>small</small></p> + <span id="span"></span> + <span id="span_explicit" title="explicit"></span> + <p id="strong_container">normal<strong>strong</strong></p> + <sub id="sub"></sub> + <sup id="sup"></sup> + <p id="sub_container">normal<sub>sub</sub></p> + <p id="sup_container">normal<sup>sup</sup></p> + + <svg id="svg"></svg> + <textarea id="textarea"></textarea> + + <p>The concert took place on <time id="time" datetime="2001-05-15 19:00">May 15</time></p> + <p id="u_container">normal<u>underline</u></p> + <p id="var_container">An equation: <var>x</var> = <var>y</var></p> + + <video id="video" controls="true"> + <source id="source" src="../bug461281.ogg" type="video/ogg"> + </video> + +</video> +</body> +</html> diff --git a/accessible/tests/mochitest/elm/test_MathMLSpec.html b/accessible/tests/mochitest/elm/test_MathMLSpec.html new file mode 100644 index 0000000000..a55c77668a --- /dev/null +++ b/accessible/tests/mochitest/elm/test_MathMLSpec.html @@ -0,0 +1,616 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML a11y spec tests</title> + <link id="link" rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../actions.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + <script type="application/javascript" + src="../relations.js"></script> + <script type="application/javascript" + src="../name.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // math + + let obj = { + role: ROLE_MATHML_MATH, + }; + testElm("math", obj); + + // //////////////////////////////////////////////////////////////////////// + // mi + + obj = { + role: ROLE_MATHML_IDENTIFIER, + }; + testElm("mi", obj); + + // //////////////////////////////////////////////////////////////////////// + // mn + + obj = { + role: ROLE_MATHML_NUMBER, + }; + testElm("mn", obj); + + // //////////////////////////////////////////////////////////////////////// + // mo + + obj = { + role: ROLE_MATHML_OPERATOR, + attributes: { accent: "true", largeop: "true" }, + }; + testElm("mo", obj); + + obj = { + role: ROLE_MATHML_OPERATOR, + attributes: { fence: "true" }, + }; + testElm("mo_fence", obj); + + obj = { + role: ROLE_MATHML_OPERATOR, + attributes: { separator: "true" }, + }; + testElm("mo_separator", obj); + + // //////////////////////////////////////////////////////////////////////// + // mtext + + obj = { + role: ROLE_MATHML_TEXT, + }; + testElm("mtext", obj); + + // //////////////////////////////////////////////////////////////////////// + // ms + + obj = { + role: ROLE_MATHML_STRING_LITERAL, + }; + testElm("ms", obj); + + // //////////////////////////////////////////////////////////////////////// + // mglyph + + obj = { + role: ROLE_MATHML_GLYPH, + }; + testElm("mglyph", obj); + + // //////////////////////////////////////////////////////////////////////// + // mrow + + obj = { + role: ROLE_MATHML_ROW, + }; + testElm("mrow", obj); + + // //////////////////////////////////////////////////////////////////////// + // mfrac + + obj = { + role: ROLE_MATHML_FRACTION, + attributes: { bevelled: "true", linethickness: "thick" }, + }; + testElm("mfrac", obj); + + // //////////////////////////////////////////////////////////////////////// + // msqrt + + obj = { + role: ROLE_MATHML_SQUARE_ROOT, + }; + testElm("msqrt", obj); + + // //////////////////////////////////////////////////////////////////////// + // mroot + + obj = { + role: ROLE_MATHML_ROOT, + relations: { + RELATION_NODE_PARENT_OF: ["mroot_index", "mroot_base"], + }, + children: [ + { + role: ROLE_MATHML_IDENTIFIER, + relations: { RELATION_NODE_CHILD_OF: "mroot" }, + }, + { + role: ROLE_MATHML_NUMBER, + relations: { RELATION_NODE_CHILD_OF: "mroot" }, + }, + ], + }; + testElm("mroot", obj); + + // //////////////////////////////////////////////////////////////////////// + // Deprecated mfenced element (treated as an mrow). + + obj = { + role: ROLE_MATHML_ROW, + }; + testElm("mfenced", obj); + + // //////////////////////////////////////////////////////////////////////// + // menclose + + obj = { + role: ROLE_MATHML_ENCLOSED, + attributes: { notation: "circle" }, + }; + testElm("menclose", obj); + + // //////////////////////////////////////////////////////////////////////// + // mstyle, mpadded, mphantom + + obj = { + role: ROLE_MATHML_STYLE, + }; + testElm("mstyle", obj); + + ok(!isAccessible("mpadded"), "mpadded should not have accessible"); + ok(!isAccessible("mphantom"), "mphantom should not have accessible"); + + // //////////////////////////////////////////////////////////////////////// + // msub + + obj = { + role: ROLE_MATHML_SUB, + }; + testElm("msub", obj); + + // //////////////////////////////////////////////////////////////////////// + // msup + + obj = { + role: ROLE_MATHML_SUP, + }; + testElm("msup", obj); + + // //////////////////////////////////////////////////////////////////////// + // msubsup + + obj = { + role: ROLE_MATHML_SUB_SUP, + }; + testElm("msubsup", obj); + + // //////////////////////////////////////////////////////////////////////// + // munder + + obj = { + role: ROLE_MATHML_UNDER, + attributes: { accentunder: "true", align: "center" }, + }; + testElm("munder", obj); + + // //////////////////////////////////////////////////////////////////////// + // mover + + obj = { + role: ROLE_MATHML_OVER, + attributes: { accent: "true", align: "center" }, + }; + testElm("mover", obj); + + // //////////////////////////////////////////////////////////////////////// + // munderover + + obj = { + role: ROLE_MATHML_UNDER_OVER, + attributes: { accent: "true", accentunder: "true", align: "center" }, + }; + testElm("munderover", obj); + + // //////////////////////////////////////////////////////////////////////// + // mmultiscripts + + obj = { + role: ROLE_MATHML_MULTISCRIPTS, + }; + testElm("mmultiscripts", obj); + + // //////////////////////////////////////////////////////////////////////// + // mtable + + obj = { + role: ROLE_MATHML_TABLE, + attributes: { align: "center", columnlines: "solid", rowlines: "solid" }, + }; + testElm("mtable", obj); + + // //////////////////////////////////////////////////////////////////////// + // mlabeledtr + + obj = { + role: ROLE_MATHML_LABELED_ROW, + }; + testElm("mlabeledtr", obj); + + // //////////////////////////////////////////////////////////////////////// + // mtr + + obj = { + role: ROLE_MATHML_TABLE_ROW, + }; + testElm("mtr", obj); + + // //////////////////////////////////////////////////////////////////////// + // mtd + + obj = { + role: ROLE_MATHML_CELL, + }; + testElm("mtd", obj); + + // //////////////////////////////////////////////////////////////////////// + // maction + + obj = { + role: ROLE_MATHML_ACTION, + attributes: { actiontype: "toggle", selection: "1" }, + }; + testElm("maction", obj); + + // //////////////////////////////////////////////////////////////////////// + // merror + + obj = { + role: ROLE_MATHML_ERROR, + }; + testElm("merror", obj); + + // //////////////////////////////////////////////////////////////////////// + // semantics, annotation, annotation-xml + ok(!isAccessible("semantics"), "semantics should not have accessible"); + ok(!isAccessible("annotation"), "annotation should not have accessible"); + ok(!isAccessible("annotation-xml"), "annotation-xml should not have accessible"); + + // //////////////////////////////////////////////////////////////////////// + // mstack + + obj = { + role: ROLE_MATHML_STACK, + attributes: { align: "center" }, + }; + testElm("mstack", obj); + + // //////////////////////////////////////////////////////////////////////// + // mlongdiv + + obj = { + role: ROLE_MATHML_LONG_DIVISION, + attributes: { longdivstyle: "stackedrightright" }, + }; + testElm("mlongdiv", obj); + + // //////////////////////////////////////////////////////////////////////// + // msgroup + + obj = { + role: ROLE_MATHML_STACK_GROUP, + attributes: { position: "2", shift: "-1" }, + }; + testElm("msgroup", obj); + + // //////////////////////////////////////////////////////////////////////// + // msrow + + obj = { + role: ROLE_MATHML_STACK_ROW, + attributes: { position: "1" }, + }; + testElm("msrow", obj); + + // //////////////////////////////////////////////////////////////////////// + // mscarries + + obj = { + role: ROLE_MATHML_STACK_CARRIES, + attributes: { location: "nw", position: "1" }, + }; + testElm("mscarries", obj); + + // //////////////////////////////////////////////////////////////////////// + // mscarry + + obj = { + role: ROLE_MATHML_STACK_CARRY, + attributes: { crossout: "updiagonalstrike" }, + }; + testElm("mscarry", obj); + + // //////////////////////////////////////////////////////////////////////// + // msline + + obj = { + role: ROLE_MATHML_STACK_LINE, + attributes: { position: "1" }, + }; + testElm("msline", obj); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + + </script> +</head> +<body> + + <a target="_blank" + title="Implement figure and figcaption accessibility" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=658272"> + Mozilla Bug 658272 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <math id="math"> + <mrow id="mrow"> + <mrow> + <msup id="msup"> + <mi id="mi">a</mi> + <mn id="mn">2</mn> + </msup> + <mo id="mo" accent="true" largeop="true">+</mo> + <msqrt id="msqrt"> + <mn>2</mn> + </msqrt> + </mrow> + <mo>=</mo> + <msub id="msub"> + <mi>c</mi> + <mn>2</mn> + </msub> + </mrow> + <mspace id="mspace" width="1em"/> + <mtext id="mtext">Arbitrary text</mtext> + <mspace width="1em"/> + <ms id="ms">InterpretedStringLiteral</ms> + <mi> + <mglyph id="mglyph" src="../letters.gif" alt="letters"/> + </mi> + <mfrac id="mfrac" bevelled="true" linethickness="thick"> + <mi>x</mi> + <mn>2</mn> + </mfrac> + <mroot id="mroot"> + <mi id="mroot_base">x</mi> + <mn id="mroot_index">5</mn> + </mroot> + <mspace width="1em"/> + <mfenced id="mfenced" close="[" open="]" separators="."> + <mrow> + <mi>x</mi> + <mi>y</mi> + </mrow> + </mfenced> + <mrow> + <mo id="mo_fence" fence="true">[</mo> + <mrow> + X + <mo id="mo_separator" separator="true">,</mo> + Y + </mrow> + <mo fence="true"> closing-fence </mo> + </mrow> + <mspace width="1em"/> + <menclose id="menclose" notation="circle"> + <mi>a</mi> + <mo>+</mo> + <mi>b</mi> + </menclose> + <mstyle id="mstyle" dir="rtl" mathcolor="blue"> + <mpadded id="mpadded" height="100px" width="200px"> + <mi>x</mi> + <mphantom id="mphantom"> + <mo>+</mo> + <mi>y</mi> + </mphantom> + </mpadded> + </mstyle> + + <msubsup id="msubsup"> + <mi>b</mi> + <mn>1</mn> + <mn>2</mn> + </msubsup> + <munder id="munder" accentunder="true" align="center"> + <mrow> + <mi> x </mi> + <mo> + </mo> + <mi> y </mi> + <mo> + </mo> + <mi> z </mi> + </mrow> + <mo> ⏟<!--BOTTOM CURLY BRACKET--> </mo> + </munder> + <mspace width="1em"/> + <mover id="mover" accent="true" align="center"> + <mi> x </mi> + <mo> ^<!--CIRCUMFLEX ACCENT--> </mo> + </mover> + <munderover id="munderover" accentunder="true" accent="true" align="center"> + <mo> ∫<!--INTEGRAL--> </mo> + <mn> 0 </mn> + <mi> ∞<!--INFINITY--> </mi> + </munderover> + <mmultiscripts id="mmultiscripts"> + <mi> R </mi> + <mi> i </mi> + <none/> + <none/> + <mi> j </mi> + <mi> k </mi> + <none/> + <mi> l </mi> + <none/> + </mmultiscripts> + + <mtable id="mtable" align="center" columnlines="solid" rowlines="solid"> + <mlabeledtr id="mlabeledtr"> + <mtd> + <mtext> (2.1) </mtext> + </mtd> + <mtd> + <mrow> + <mi>E</mi> + <mo>=</mo> + <mrow> + <mi>m</mi> + <mo>⁢<!--INVISIBLE TIMES--></mo> + <msup> + <mi>c</mi> + <mn>2</mn> + </msup> + </mrow> + </mrow> + </mtd> + </mlabeledtr> + </mtable> + <mrow> + <mo> ( </mo> + <mtable> + <mtr id="mtr"> + <mtd id="mtd"> <mn>1</mn> </mtd> + <mtd> <mn>0</mn> </mtd> + <mtd> <mn>0</mn> </mtd> + </mtr> + <mtr> + <mtd> <mn>0</mn> </mtd> + <mtd> <mn>1</mn> </mtd> + <mtd> <mn>0</mn> </mtd> + </mtr> + <mtr> + <mtd> <mn>0</mn> </mtd> + <mtd> <mn>0</mn> </mtd> + <mtd> <mn>1</mn> </mtd> + </mtr> + </mtable> + <mo> ) </mo> + </mrow> + + <maction id="maction" actiontype="toggle" selection="1"> + <mfrac> + <mn>6</mn> + <mn>8</mn> + </mfrac> + <mfrac> + <mrow> + <mn>3</mn> + <mo>⋅</mo> + <mn>2</mn> + </mrow> + <mrow> + <mn>4</mn> + <mo>⋅</mo> + <mn>2</mn> + </mrow> + </mfrac> + <mfrac> + <mn>3</mn> + <mn>4</mn> + </mfrac> + </maction> + + <merror id="merror"> + <mrow> + <mtext>Division by zero: </mtext> + <mfrac> + <mn>1</mn> + <mn>0</mn> + </mfrac> + </mrow> + </merror> + + <semantics id="semantics"> + <!-- Presentation MathML --> + <mrow> + <msup> + <mi>x</mi> + <mn>2</mn> + </msup> + <mo>+</mo> + <mi>y</mi> + </mrow> + <!-- Content MathML --> + <annotation-xml id="annotation-xml" encoding="MathML-Content"> + <apply> + <plus/> + <apply> + <power/> + <ci>x</ci> + <cn type="integer">2</cn> + </apply> + <ci>y</ci> + </apply> + </annotation-xml> + <!-- annotate TeX --> + <annotation id="annotation" encoding="application/x-tex"> + x^{2} + y + </annotation> + </semantics> + + <mstack id="mstack" align="center"> + <mscarries id="mscarries" location="nw" position="1"> + <none/> + <mscarry id="mscarry" crossout="updiagonalstrike"> + <mn>1</mn> + </mscarry> + <mscarry location="w"> + <mn>1</mn> + </mscarry> + </mscarries> + <mn>523</mn> + <msrow id="msrow" position="1"> + <mo>-</mo> + <none/> + <mn>15</mn> + </msrow> + <msline id="msline" position="1"/> + <mn>508</mn> + </mstack> + <mspace width="1em"/> + <mlongdiv id="mlongdiv" longdivstyle="stackedrightright"> + <mn>5</mn> + <mn>1</mn> + <mn>5</mn> + </mlongdiv> + + <mstack> + <msgroup id="msgroup" position="2" shift="-1"> + <mn>123</mn> + <msrow><mo>×<!--MULTIPLICATION SIGN--></mo><mn>321</mn></msrow> + </msgroup> + <msline/> + <msgroup shift="1"> + <mn>123</mn> + <mn>246</mn> + <mn>369</mn> + </msgroup> + <msline/> + </mstack> + </math> + +</body> +</html> diff --git a/accessible/tests/mochitest/elm/test_figure.html b/accessible/tests/mochitest/elm/test_figure.html new file mode 100644 index 0000000000..82ac961e36 --- /dev/null +++ b/accessible/tests/mochitest/elm/test_figure.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML5 figure/figcaption tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + <script type="application/javascript" + src="../relations.js"></script> + <script type="application/javascript" + src="../name.js"></script> + + <script type="application/javascript"> + + function doTest() { + testRole("figure", ROLE_FIGURE); + testRole("figcaption", ROLE_CAPTION); + + todo(false, "figure name gets extra whitespace in the end!"); + testName("figure", "figure caption"); + testName("figcaption", null); + + testRelation("figure", RELATION_LABELLED_BY, "figcaption"); + testRelation("figcaption", RELATION_LABEL_FOR, "figure"); + + testAttrs("figure", {"xml-roles": "figure"}, true); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Implement figure and figcaption accessibility" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=658272"> + Mozilla Bug 658272 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <figure id="figure"> + <figcaption id="figcaption">figure caption</figcaption> + </figure> + +</body> +</html> diff --git a/accessible/tests/mochitest/elm/test_listbox.xhtml b/accessible/tests/mochitest/elm/test_listbox.xhtml new file mode 100644 index 0000000000..2315959e3a --- /dev/null +++ b/accessible/tests/mochitest/elm/test_listbox.xhtml @@ -0,0 +1,73 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL listbox element test."> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + var id = ""; + var acc = null; + + ////////////////////////////////////////////////////////////////////////// + // Simple listbox. There is no nsIAccessibleTable interface. + + id = "listbox1"; + acc = getAccessible(id); + + // query nsIAccessibleTable + try { + acc.QueryInterface(nsIAccessibleTable); + ok(false, + id + " shouldn't implement nsIAccessibleTable interface."); + } catch(e) { + ok(true, id + " doesn't implement nsIAccessibleTable interface."); + } + + // role + testRole(id, ROLE_LISTBOX); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=418371" + title="implement the rest of methods of nsIAccessibleTable on xul:listbox"> + Mozilla Bug 418371 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <label control="listbox1" value="listbox: "/> + <richlistbox id="listbox1"> + <richlistitem id="item1"><label value="item1"/></richlistitem> + <richlistitem id="item1"><label value="item2"/></richlistitem> + </richlistbox> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/elm/test_nsApplicationAcc.html b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html new file mode 100644 index 0000000000..2e7aabf882 --- /dev/null +++ b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html @@ -0,0 +1,67 @@ +<html> + +<head> + <title>application accessible name</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + var accessible = getApplicationAccessible(); + if (!accessible) { + SimpleTest.finish(); + return; + } + + var brandBundle = + Services.strings.createBundle("chrome://branding/locale/brand.properties"); + + // nsIAccessible::name + var applicationName = ""; + if (LINUX || SOLARIS) { + applicationName = Services.appinfo.name; + } else { + try { + applicationName = brandBundle.GetStringFromName("brandShortName"); + } catch (e) { + } + + if (applicationName == "") + applicationName = "Gecko based application"; + } + is(accessible.name, applicationName, "wrong application accessible name"); + + // nsIAccessibleApplication + is(accessible.appName, Services.appinfo.name, "Wrong application name"); + is(accessible.appVersion, Services.appinfo.version, "Wrong application version"); + is(accessible.platformName, "Gecko", "Wrong platform name"); + is(accessible.platformVersion, Services.appinfo.platformVersion, + "Wrong platform version"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + </head> + <body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=456121" + title="nsApplicationAccessible::GetName does not return a default value when brand.properties does not exist"> + Mozilla Bug 454211 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + </body> +</html> diff --git a/accessible/tests/mochitest/elm/test_shadowroot.html b/accessible/tests/mochitest/elm/test_shadowroot.html new file mode 100644 index 0000000000..bc221090b4 --- /dev/null +++ b/accessible/tests/mochitest/elm/test_shadowroot.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<head> + <title>ShadowRoot tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +</head> +<body> + + <a target="_blank" + title="Ensure accessible objects are created for shadow root" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1026125"> + Mozilla Bug 1026125 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <script> + SimpleTest.waitForExplicitFinish(); + + window.onload = () => { + var iframe = document.createElement("iframe"); + iframe.src = "test_shadowroot_subframe.html"; + document.body.appendChild(iframe); + }; + + </script> + +</body> +</html> diff --git a/accessible/tests/mochitest/elm/test_shadowroot_subframe.html b/accessible/tests/mochitest/elm/test_shadowroot_subframe.html new file mode 100644 index 0000000000..20e2baf681 --- /dev/null +++ b/accessible/tests/mochitest/elm/test_shadowroot_subframe.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>ShadowRoot tests</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="application/javascript" src="../common.js"></script> + <script type="application/javascript" src="../role.js"></script> + + <script type="application/javascript"> + let SimpleTest = window.parent.SimpleTest; + let ok = window.parent.ok; + let is = window.parent.is; + + function doTest() { + testElm("component", { + role: ROLE_GROUPING, + children: [ + { + role: ROLE_PUSHBUTTON, + }, + { + role: ROLE_LINK, + }, + ], + }); + + // Shadow root boundary between table and row + testElm("table", { + role: ROLE_TABLE, + children: [ + { + role: ROLE_ROW, + }, + ], + }); + + SimpleTest.finish(); + } + + addA11yLoadEvent(doTest); + </script> + +</head> +<body> + <div role="group" id="component"></div> + <div id="table" role="table" style="display: table;"></div> + + <script> + var component = document.getElementById("component"); + var shadow = component.attachShadow({mode: "open"}); + + var button = document.createElement("button"); + button.append("Hello"); + + var a = document.createElement("a"); + a.setAttribute("href", "#"); + a.append(" World"); + + shadow.appendChild(button); + shadow.appendChild(a); + + var table = document.getElementById("table"); + shadow = table.attachShadow({mode: "open"}); + shadow.innerHTML = "<div style='display: table-row' role='row'>" + + "<div style='display: table-cell' role='cell'>hi</div>" + + "</div>"; + </script> +</body> diff --git a/accessible/tests/mochitest/events.js b/accessible/tests/mochitest/events.js new file mode 100644 index 0000000000..a6c216e01d --- /dev/null +++ b/accessible/tests/mochitest/events.js @@ -0,0 +1,2660 @@ +/* import-globals-from common.js */ +/* import-globals-from states.js */ +/* import-globals-from text.js */ + +// XXX Bug 1425371 - enable no-redeclare and fix the issues with the tests. +/* eslint-disable no-redeclare */ + +// ////////////////////////////////////////////////////////////////////////////// +// Constants + +const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT; +const EVENT_ANNOUNCEMENT = nsIAccessibleEvent.EVENT_ANNOUNCEMENT; +const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE; +const EVENT_DOCUMENT_LOAD_COMPLETE = + nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE; +const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD; +const EVENT_DOCUMENT_LOAD_STOPPED = + nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_STOPPED; +const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE; +const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS; +const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE; +const EVENT_MENU_START = nsIAccessibleEvent.EVENT_MENU_START; +const EVENT_MENU_END = nsIAccessibleEvent.EVENT_MENU_END; +const EVENT_MENUPOPUP_START = nsIAccessibleEvent.EVENT_MENUPOPUP_START; +const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END; +const EVENT_OBJECT_ATTRIBUTE_CHANGED = + nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED; +const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER; +const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START; +const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION; +const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD; +const EVENT_SELECTION_REMOVE = nsIAccessibleEvent.EVENT_SELECTION_REMOVE; +const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN; +const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW; +const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE; +const EVENT_TEXT_ATTRIBUTE_CHANGED = + nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED; +const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED; +const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED; +const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED; +const EVENT_TEXT_SELECTION_CHANGED = + nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED; +const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE; +const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE; +const EVENT_VIRTUALCURSOR_CHANGED = + nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED; + +const kNotFromUserInput = 0; +const kFromUserInput = 1; + +// ////////////////////////////////////////////////////////////////////////////// +// General + +/** + * Set up this variable to dump events into DOM. + */ +var gA11yEventDumpID = ""; + +/** + * Set up this variable to dump event processing into console. + */ +var gA11yEventDumpToConsole = false; + +/** + * Set up this variable to dump event processing into error console. + */ +var gA11yEventDumpToAppConsole = false; + +/** + * Semicolon separated set of logging features. + */ +var gA11yEventDumpFeature = ""; + +/** + * Function to detect HTML elements when given a node. + */ +function isHTMLElement(aNode) { + return ( + aNode.nodeType == aNode.ELEMENT_NODE && + aNode.namespaceURI == "http://www.w3.org/1999/xhtml" + ); +} + +function isXULElement(aNode) { + return ( + aNode.nodeType == aNode.ELEMENT_NODE && + aNode.namespaceURI == + "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + ); +} + +/** + * Executes the function when requested event is handled. + * + * @param aEventType [in] event type + * @param aTarget [in] event target + * @param aFunc [in] function to call when event is handled + * @param aContext [in, optional] object in which context the function is + * called + * @param aArg1 [in, optional] argument passed into the function + * @param aArg2 [in, optional] argument passed into the function + */ +function waitForEvent( + aEventType, + aTargetOrFunc, + aFunc, + aContext, + aArg1, + aArg2 +) { + var handler = { + handleEvent: function handleEvent(aEvent) { + var target = aTargetOrFunc; + if (typeof aTargetOrFunc == "function") { + target = aTargetOrFunc.call(); + } + + if (target) { + if (target instanceof nsIAccessible && target != aEvent.accessible) { + return; + } + + if (Node.isInstance(target) && target != aEvent.DOMNode) { + return; + } + } + + unregisterA11yEventListener(aEventType, this); + + window.setTimeout(function () { + aFunc.call(aContext, aArg1, aArg2); + }, 0); + }, + }; + + registerA11yEventListener(aEventType, handler); +} + +/** + * Generate mouse move over image map what creates image map accessible (async). + * See waitForImageMap() function. + */ +function waveOverImageMap(aImageMapID) { + var imageMapNode = getNode(aImageMapID); + synthesizeMouse( + imageMapNode, + 10, + 10, + { type: "mousemove" }, + imageMapNode.ownerGlobal + ); +} + +/** + * Call the given function when the tree of the given image map is built. + */ +function waitForImageMap(aImageMapID, aTestFunc) { + waveOverImageMap(aImageMapID); + + var imageMapAcc = getAccessible(aImageMapID); + if (imageMapAcc.firstChild) { + aTestFunc(); + return; + } + + waitForEvent(EVENT_REORDER, imageMapAcc, aTestFunc); +} + +/** + * Register accessibility event listener. + * + * @param aEventType the accessible event type (see nsIAccessibleEvent for + * available constants). + * @param aEventHandler event listener object, when accessible event of the + * given type is handled then 'handleEvent' method of + * this object is invoked with nsIAccessibleEvent object + * as the first argument. + */ +function registerA11yEventListener(aEventType, aEventHandler) { + listenA11yEvents(true); + addA11yEventListener(aEventType, aEventHandler); +} + +/** + * Unregister accessibility event listener. Must be called for every registered + * event listener (see registerA11yEventListener() function) when the listener + * is not needed. + */ +function unregisterA11yEventListener(aEventType, aEventHandler) { + removeA11yEventListener(aEventType, aEventHandler); + listenA11yEvents(false); +} + +// ////////////////////////////////////////////////////////////////////////////// +// Event queue + +/** + * Return value of invoke method of invoker object. Indicates invoker was unable + * to prepare action. + */ +const INVOKER_ACTION_FAILED = 1; + +/** + * Return value of eventQueue.onFinish. Indicates eventQueue should not finish + * tests. + */ +const DO_NOT_FINISH_TEST = 1; + +/** + * Creates event queue for the given event type. The queue consists of invoker + * objects, each of them generates the event of the event type. When queue is + * started then every invoker object is asked to generate event after timeout. + * When event is caught then current invoker object is asked to check whether + * event was handled correctly. + * + * Invoker interface is: + * + * var invoker = { + * // Generates accessible event or event sequence. If returns + * // INVOKER_ACTION_FAILED constant then stop tests. + * invoke: function(){}, + * + * // [optional] Invoker's check of handled event for correctness. + * check: function(aEvent){}, + * + * // [optional] Invoker's check before the next invoker is proceeded. + * finalCheck: function(aEvent){}, + * + * // [optional] Is called when event of any registered type is handled. + * debugCheck: function(aEvent){}, + * + * // [ignored if 'eventSeq' is defined] DOM node event is generated for + * // (used in the case when invoker expects single event). + * DOMNode getter: function() {}, + * + * // [optional] if true then event sequences are ignored (no failure if + * // sequences are empty). Use you need to invoke an action, do some check + * // after timeout and proceed a next invoker. + * noEventsOnAction getter: function() {}, + * + * // Array of checker objects defining expected events on invoker's action. + * // + * // Checker object interface: + * // + * // var checker = { + * // * DOM or a11y event type. * + * // type getter: function() {}, + * // + * // * DOM node or accessible. * + * // target getter: function() {}, + * // + * // * DOM event phase (false - bubbling). * + * // phase getter: function() {}, + * // + * // * Callback, called to match handled event. * + * // match : function(aEvent) {}, + * // + * // * Callback, called when event is handled + * // check: function(aEvent) {}, + * // + * // * Checker ID * + * // getID: function() {}, + * // + * // * Event that don't have predefined order relative other events. * + * // async getter: function() {}, + * // + * // * Event that is not expected. * + * // unexpected getter: function() {}, + * // + * // * No other event of the same type is not allowed. * + * // unique getter: function() {} + * // }; + * eventSeq getter() {}, + * + * // Array of checker objects defining unexpected events on invoker's + * // action. + * unexpectedEventSeq getter() {}, + * + * // The ID of invoker. + * getID: function(){} // returns invoker ID + * }; + * + * // Used to add a possible scenario of expected/unexpected events on + * // invoker's action. + * defineScenario(aInvokerObj, aEventSeq, aUnexpectedEventSeq) + * + * + * @param aEventType [in, optional] the default event type (isn't used if + * invoker defines eventSeq property). + */ +function eventQueue(aEventType) { + // public + + /** + * Add invoker object into queue. + */ + this.push = function eventQueue_push(aEventInvoker) { + this.mInvokers.push(aEventInvoker); + }; + + /** + * Start the queue processing. + */ + this.invoke = function eventQueue_invoke() { + listenA11yEvents(true); + + // XXX: Intermittent test_events_caretmove.html fails withouth timeout, + // see bug 474952. + this.processNextInvokerInTimeout(true); + }; + + /** + * This function is called when all events in the queue were handled. + * Override it if you need to be notified of this. + */ + this.onFinish = function eventQueue_finish() {}; + + // private + + /** + * Process next invoker. + */ + // eslint-disable-next-line complexity + this.processNextInvoker = function eventQueue_processNextInvoker() { + // Some scenario was matched, we wait on next invoker processing. + if (this.mNextInvokerStatus == kInvokerCanceled) { + this.setInvokerStatus( + kInvokerNotScheduled, + "scenario was matched, wait for next invoker activation" + ); + return; + } + + this.setInvokerStatus( + kInvokerNotScheduled, + "the next invoker is processed now" + ); + + // Finish processing of the current invoker if any. + var testFailed = false; + + var invoker = this.getInvoker(); + if (invoker) { + if ("finalCheck" in invoker) { + invoker.finalCheck(); + } + + if (this.mScenarios && this.mScenarios.length) { + var matchIdx = -1; + for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) { + var eventSeq = this.mScenarios[scnIdx]; + if (!this.areExpectedEventsLeft(eventSeq)) { + for (var idx = 0; idx < eventSeq.length; idx++) { + var checker = eventSeq[idx]; + if ( + (checker.unexpected && checker.wasCaught) || + (!checker.unexpected && checker.wasCaught != 1) + ) { + break; + } + } + + // Ok, we have matched scenario. Report it was completed ok. In + // case of empty scenario guess it was matched but if later we + // find out that non empty scenario was matched then it will be + // a final match. + if (idx == eventSeq.length) { + if ( + matchIdx != -1 && + !!eventSeq.length && + this.mScenarios[matchIdx].length + ) { + ok( + false, + "We have a matched scenario at index " + + matchIdx + + " already." + ); + } + + if (matchIdx == -1 || eventSeq.length) { + matchIdx = scnIdx; + } + + // Report everything is ok. + for (var idx = 0; idx < eventSeq.length; idx++) { + var checker = eventSeq[idx]; + + var typeStr = eventQueue.getEventTypeAsString(checker); + var msg = + "Test with ID = '" + this.getEventID(checker) + "' succeed. "; + + if (checker.unexpected) { + ok(true, msg + `There's no unexpected '${typeStr}' event.`); + } else if (checker.todo) { + todo(false, `Todo event '${typeStr}' was caught`); + } else { + ok(true, `${msg} Event '${typeStr}' was handled.`); + } + } + } + } + } + + // We don't have completely matched scenario. Report each failure/success + // for every scenario. + if (matchIdx == -1) { + testFailed = true; + for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) { + var eventSeq = this.mScenarios[scnIdx]; + for (var idx = 0; idx < eventSeq.length; idx++) { + var checker = eventSeq[idx]; + + var typeStr = eventQueue.getEventTypeAsString(checker); + var msg = + "Scenario #" + + scnIdx + + " of test with ID = '" + + this.getEventID(checker) + + "' failed. "; + + if (checker.wasCaught > 1) { + ok(false, msg + "Dupe " + typeStr + " event."); + } + + if (checker.unexpected) { + if (checker.wasCaught) { + ok(false, msg + "There's unexpected " + typeStr + " event."); + } + } else if (!checker.wasCaught) { + var rf = checker.todo ? todo : ok; + rf(false, `${msg} '${typeStr} event is missed.`); + } + } + } + } + } + } + + this.clearEventHandler(); + + // Check if need to stop the test. + if (testFailed || this.mIndex == this.mInvokers.length - 1) { + listenA11yEvents(false); + + var res = this.onFinish(); + if (res != DO_NOT_FINISH_TEST) { + SimpleTest.executeSoon(SimpleTest.finish); + } + + return; + } + + // Start processing of next invoker. + invoker = this.getNextInvoker(); + + // Set up event listeners. Process a next invoker if no events were added. + if (!this.setEventHandler(invoker)) { + this.processNextInvoker(); + return; + } + + if (gLogger.isEnabled()) { + gLogger.logToConsole("Event queue: \n invoke: " + invoker.getID()); + gLogger.logToDOM("EQ: invoke: " + invoker.getID(), true); + } + + var infoText = "Invoke the '" + invoker.getID() + "' test { "; + var scnCount = this.mScenarios ? this.mScenarios.length : 0; + for (var scnIdx = 0; scnIdx < scnCount; scnIdx++) { + infoText += "scenario #" + scnIdx + ": "; + var eventSeq = this.mScenarios[scnIdx]; + for (var idx = 0; idx < eventSeq.length; idx++) { + infoText += eventSeq[idx].unexpected + ? "un" + : "" + + "expected '" + + eventQueue.getEventTypeAsString(eventSeq[idx]) + + "' event; "; + } + } + infoText += " }"; + info(infoText); + + if (invoker.invoke() == INVOKER_ACTION_FAILED) { + // Invoker failed to prepare action, fail and finish tests. + this.processNextInvoker(); + return; + } + + if (this.hasUnexpectedEventsScenario()) { + this.processNextInvokerInTimeout(true); + } + }; + + this.processNextInvokerInTimeout = + function eventQueue_processNextInvokerInTimeout(aUncondProcess) { + this.setInvokerStatus(kInvokerPending, "Process next invoker in timeout"); + + // No need to wait extra timeout when a) we know we don't need to do that + // and b) there's no any single unexpected event. + if (!aUncondProcess && this.areAllEventsExpected()) { + // We need delay to avoid events coalesce from different invokers. + var queue = this; + SimpleTest.executeSoon(function () { + queue.processNextInvoker(); + }); + return; + } + + // Check in timeout invoker didn't fire registered events. + window.setTimeout( + function (aQueue) { + aQueue.processNextInvoker(); + }, + 300, + this + ); + }; + + /** + * Handle events for the current invoker. + */ + // eslint-disable-next-line complexity + this.handleEvent = function eventQueue_handleEvent(aEvent) { + var invoker = this.getInvoker(); + if (!invoker) { + // skip events before test was started + return; + } + + if (!this.mScenarios) { + // Bad invoker object, error will be reported before processing of next + // invoker in the queue. + this.processNextInvoker(); + return; + } + + if ("debugCheck" in invoker) { + invoker.debugCheck(aEvent); + } + + for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) { + var eventSeq = this.mScenarios[scnIdx]; + for (var idx = 0; idx < eventSeq.length; idx++) { + var checker = eventSeq[idx]; + + // Search through handled expected events to report error if one of them + // is handled for a second time. + if ( + !checker.unexpected && + checker.wasCaught > 0 && + eventQueue.isSameEvent(checker, aEvent) + ) { + checker.wasCaught++; + continue; + } + + // Search through unexpected events, any match results in error report + // after this invoker processing (in case of matched scenario only). + if (checker.unexpected && eventQueue.compareEvents(checker, aEvent)) { + checker.wasCaught++; + continue; + } + + // Report an error if we handled not expected event of unique type + // (i.e. event types are matched, targets differs). + if ( + !checker.unexpected && + checker.unique && + eventQueue.compareEventTypes(checker, aEvent) + ) { + var isExpected = false; + for (var jdx = 0; jdx < eventSeq.length; jdx++) { + isExpected = eventQueue.compareEvents(eventSeq[jdx], aEvent); + if (isExpected) { + break; + } + } + + if (!isExpected) { + ok( + false, + "Unique type " + + eventQueue.getEventTypeAsString(checker) + + " event was handled." + ); + } + } + } + } + + var hasMatchedCheckers = false; + for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) { + var eventSeq = this.mScenarios[scnIdx]; + + // Check if handled event matches expected sync event. + var nextChecker = this.getNextExpectedEvent(eventSeq); + if (nextChecker) { + if (eventQueue.compareEvents(nextChecker, aEvent)) { + this.processMatchedChecker(aEvent, nextChecker, scnIdx, eventSeq.idx); + hasMatchedCheckers = true; + continue; + } + } + + // Check if handled event matches any expected async events. + var haveUnmatchedAsync = false; + for (idx = 0; idx < eventSeq.length; idx++) { + if (eventSeq[idx] instanceof orderChecker && haveUnmatchedAsync) { + break; + } + + if (!eventSeq[idx].wasCaught) { + haveUnmatchedAsync = true; + } + + if (!eventSeq[idx].unexpected && eventSeq[idx].async) { + if (eventQueue.compareEvents(eventSeq[idx], aEvent)) { + this.processMatchedChecker(aEvent, eventSeq[idx], scnIdx, idx); + hasMatchedCheckers = true; + break; + } + } + } + } + + if (hasMatchedCheckers) { + var invoker = this.getInvoker(); + if ("check" in invoker) { + invoker.check(aEvent); + } + } + + for (idx = 0; idx < eventSeq.length; idx++) { + if (!eventSeq[idx].wasCaught) { + if (eventSeq[idx] instanceof orderChecker) { + eventSeq[idx].wasCaught++; + } else { + break; + } + } + } + + // If we don't have more events to wait then schedule next invoker. + if (this.hasMatchedScenario()) { + if (this.mNextInvokerStatus == kInvokerNotScheduled) { + this.processNextInvokerInTimeout(); + } else if (this.mNextInvokerStatus == kInvokerCanceled) { + this.setInvokerStatus( + kInvokerPending, + "Full match. Void the cancelation of next invoker processing" + ); + } + return; + } + + // If we have scheduled a next invoker then cancel in case of match. + if (this.mNextInvokerStatus == kInvokerPending && hasMatchedCheckers) { + this.setInvokerStatus( + kInvokerCanceled, + "Cancel the scheduled invoker in case of match" + ); + } + }; + + // Helpers + this.processMatchedChecker = function eventQueue_function( + aEvent, + aMatchedChecker, + aScenarioIdx, + aEventIdx + ) { + aMatchedChecker.wasCaught++; + + if ("check" in aMatchedChecker) { + aMatchedChecker.check(aEvent); + } + + eventQueue.logEvent( + aEvent, + aMatchedChecker, + aScenarioIdx, + aEventIdx, + this.areExpectedEventsLeft(), + this.mNextInvokerStatus + ); + }; + + this.getNextExpectedEvent = function eventQueue_getNextExpectedEvent( + aEventSeq + ) { + if (!("idx" in aEventSeq)) { + aEventSeq.idx = 0; + } + + while ( + aEventSeq.idx < aEventSeq.length && + (aEventSeq[aEventSeq.idx].unexpected || + aEventSeq[aEventSeq.idx].todo || + aEventSeq[aEventSeq.idx].async || + aEventSeq[aEventSeq.idx] instanceof orderChecker || + aEventSeq[aEventSeq.idx].wasCaught > 0) + ) { + aEventSeq.idx++; + } + + return aEventSeq.idx != aEventSeq.length ? aEventSeq[aEventSeq.idx] : null; + }; + + this.areExpectedEventsLeft = function eventQueue_areExpectedEventsLeft( + aScenario + ) { + function scenarioHasUnhandledExpectedEvent(aEventSeq) { + // Check if we have unhandled async (can be anywhere in the sequance) or + // sync expcected events yet. + for (var idx = 0; idx < aEventSeq.length; idx++) { + if ( + !aEventSeq[idx].unexpected && + !aEventSeq[idx].todo && + !aEventSeq[idx].wasCaught && + !(aEventSeq[idx] instanceof orderChecker) + ) { + return true; + } + } + + return false; + } + + if (aScenario) { + return scenarioHasUnhandledExpectedEvent(aScenario); + } + + for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) { + var eventSeq = this.mScenarios[scnIdx]; + if (scenarioHasUnhandledExpectedEvent(eventSeq)) { + return true; + } + } + return false; + }; + + this.areAllEventsExpected = function eventQueue_areAllEventsExpected() { + for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) { + var eventSeq = this.mScenarios[scnIdx]; + for (var idx = 0; idx < eventSeq.length; idx++) { + if (eventSeq[idx].unexpected || eventSeq[idx].todo) { + return false; + } + } + } + + return true; + }; + + this.isUnexpectedEventScenario = + function eventQueue_isUnexpectedEventsScenario(aScenario) { + for (var idx = 0; idx < aScenario.length; idx++) { + if (!aScenario[idx].unexpected && !aScenario[idx].todo) { + break; + } + } + + return idx == aScenario.length; + }; + + this.hasUnexpectedEventsScenario = + function eventQueue_hasUnexpectedEventsScenario() { + if (this.getInvoker().noEventsOnAction) { + return true; + } + + for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) { + if (this.isUnexpectedEventScenario(this.mScenarios[scnIdx])) { + return true; + } + } + + return false; + }; + + this.hasMatchedScenario = function eventQueue_hasMatchedScenario() { + for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) { + var scn = this.mScenarios[scnIdx]; + if ( + !this.isUnexpectedEventScenario(scn) && + !this.areExpectedEventsLeft(scn) + ) { + return true; + } + } + return false; + }; + + this.getInvoker = function eventQueue_getInvoker() { + return this.mInvokers[this.mIndex]; + }; + + this.getNextInvoker = function eventQueue_getNextInvoker() { + return this.mInvokers[++this.mIndex]; + }; + + this.setEventHandler = function eventQueue_setEventHandler(aInvoker) { + if (!("scenarios" in aInvoker) || !aInvoker.scenarios.length) { + var eventSeq = aInvoker.eventSeq; + var unexpectedEventSeq = aInvoker.unexpectedEventSeq; + if (!eventSeq && !unexpectedEventSeq && this.mDefEventType) { + eventSeq = [new invokerChecker(this.mDefEventType, aInvoker.DOMNode)]; + } + + if (eventSeq || unexpectedEventSeq) { + defineScenario(aInvoker, eventSeq, unexpectedEventSeq); + } + } + + if (aInvoker.noEventsOnAction) { + return true; + } + + this.mScenarios = aInvoker.scenarios; + if (!this.mScenarios || !this.mScenarios.length) { + ok(false, "Broken invoker '" + aInvoker.getID() + "'"); + return false; + } + + // Register event listeners. + for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) { + var eventSeq = this.mScenarios[scnIdx]; + + if (gLogger.isEnabled()) { + var msg = + "scenario #" + + scnIdx + + ", registered events number: " + + eventSeq.length; + gLogger.logToConsole(msg); + gLogger.logToDOM(msg, true); + } + + // Do not warn about empty event sequances when more than one scenario + // was registered. + if (this.mScenarios.length == 1 && !eventSeq.length) { + ok( + false, + "Broken scenario #" + + scnIdx + + " of invoker '" + + aInvoker.getID() + + "'. No registered events" + ); + return false; + } + + for (var idx = 0; idx < eventSeq.length; idx++) { + eventSeq[idx].wasCaught = 0; + } + + for (var idx = 0; idx < eventSeq.length; idx++) { + if (gLogger.isEnabled()) { + var msg = "registered"; + if (eventSeq[idx].unexpected) { + msg += " unexpected"; + } + if (eventSeq[idx].async) { + msg += " async"; + } + + msg += + ": event type: " + + eventQueue.getEventTypeAsString(eventSeq[idx]) + + ", target: " + + eventQueue.getEventTargetDescr(eventSeq[idx], true); + + gLogger.logToConsole(msg); + gLogger.logToDOM(msg, true); + } + + var eventType = eventSeq[idx].type; + if (typeof eventType == "string") { + // DOM event + var target = eventQueue.getEventTarget(eventSeq[idx]); + if (!target) { + ok(false, "no target for DOM event!"); + return false; + } + var phase = eventQueue.getEventPhase(eventSeq[idx]); + target.addEventListener(eventType, this, phase); + } else { + // A11y event + addA11yEventListener(eventType, this); + } + } + } + + return true; + }; + + this.clearEventHandler = function eventQueue_clearEventHandler() { + if (!this.mScenarios) { + return; + } + + for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) { + var eventSeq = this.mScenarios[scnIdx]; + for (var idx = 0; idx < eventSeq.length; idx++) { + var eventType = eventSeq[idx].type; + if (typeof eventType == "string") { + // DOM event + var target = eventQueue.getEventTarget(eventSeq[idx]); + var phase = eventQueue.getEventPhase(eventSeq[idx]); + target.removeEventListener(eventType, this, phase); + } else { + // A11y event + removeA11yEventListener(eventType, this); + } + } + } + this.mScenarios = null; + }; + + this.getEventID = function eventQueue_getEventID(aChecker) { + if ("getID" in aChecker) { + return aChecker.getID(); + } + + var invoker = this.getInvoker(); + return invoker.getID(); + }; + + this.setInvokerStatus = function eventQueue_setInvokerStatus( + aStatus, + aLogMsg + ) { + this.mNextInvokerStatus = aStatus; + + // Uncomment it to debug invoker processing logic. + // gLogger.log(eventQueue.invokerStatusToMsg(aStatus, aLogMsg)); + }; + + this.mDefEventType = aEventType; + + this.mInvokers = []; + this.mIndex = -1; + this.mScenarios = null; + + this.mNextInvokerStatus = kInvokerNotScheduled; +} + +// ////////////////////////////////////////////////////////////////////////////// +// eventQueue static members and constants + +const kInvokerNotScheduled = 0; +const kInvokerPending = 1; +const kInvokerCanceled = 2; + +eventQueue.getEventTypeAsString = function eventQueue_getEventTypeAsString( + aEventOrChecker +) { + if (Event.isInstance(aEventOrChecker)) { + return aEventOrChecker.type; + } + + if (aEventOrChecker instanceof nsIAccessibleEvent) { + return eventTypeToString(aEventOrChecker.eventType); + } + + return typeof aEventOrChecker.type == "string" + ? aEventOrChecker.type + : eventTypeToString(aEventOrChecker.type); +}; + +eventQueue.getEventTargetDescr = function eventQueue_getEventTargetDescr( + aEventOrChecker, + aDontForceTarget +) { + if (Event.isInstance(aEventOrChecker)) { + return prettyName(aEventOrChecker.originalTarget); + } + + // XXXbz this block doesn't seem to be reachable... + if (Event.isInstance(aEventOrChecker)) { + return prettyName(aEventOrChecker.accessible); + } + + var descr = aEventOrChecker.targetDescr; + if (descr) { + return descr; + } + + if (aDontForceTarget) { + return "no target description"; + } + + var target = "target" in aEventOrChecker ? aEventOrChecker.target : null; + return prettyName(target); +}; + +eventQueue.getEventPhase = function eventQueue_getEventPhase(aChecker) { + return "phase" in aChecker ? aChecker.phase : true; +}; + +eventQueue.getEventTarget = function eventQueue_getEventTarget(aChecker) { + if ("eventTarget" in aChecker) { + switch (aChecker.eventTarget) { + case "element": + return aChecker.target; + case "document": + default: + return aChecker.target.ownerDocument; + } + } + return aChecker.target.ownerDocument; +}; + +eventQueue.compareEventTypes = function eventQueue_compareEventTypes( + aChecker, + aEvent +) { + var eventType = Event.isInstance(aEvent) ? aEvent.type : aEvent.eventType; + return aChecker.type == eventType; +}; + +eventQueue.compareEvents = function eventQueue_compareEvents(aChecker, aEvent) { + if (!eventQueue.compareEventTypes(aChecker, aEvent)) { + return false; + } + + // If checker provides "match" function then allow the checker to decide + // whether event is matched. + if ("match" in aChecker) { + return aChecker.match(aEvent); + } + + var target1 = aChecker.target; + if (target1 instanceof nsIAccessible) { + var target2 = Event.isInstance(aEvent) + ? getAccessible(aEvent.target) + : aEvent.accessible; + + return target1 == target2; + } + + // If original target isn't suitable then extend interface to support target + // (original target is used in test_elm_media.html). + var target2 = Event.isInstance(aEvent) + ? aEvent.originalTarget + : aEvent.DOMNode; + return target1 == target2; +}; + +eventQueue.isSameEvent = function eventQueue_isSameEvent(aChecker, aEvent) { + // We don't have stored info about handled event other than its type and + // target, thus we should filter text change and state change events since + // they may occur on the same element because of complex changes. + return ( + this.compareEvents(aChecker, aEvent) && + !(aEvent instanceof nsIAccessibleTextChangeEvent) && + !(aEvent instanceof nsIAccessibleStateChangeEvent) + ); +}; + +eventQueue.invokerStatusToMsg = function eventQueue_invokerStatusToMsg( + aInvokerStatus, + aMsg +) { + var msg = "invoker status: "; + switch (aInvokerStatus) { + case kInvokerNotScheduled: + msg += "not scheduled"; + break; + case kInvokerPending: + msg += "pending"; + break; + case kInvokerCanceled: + msg += "canceled"; + break; + } + + if (aMsg) { + msg += " (" + aMsg + ")"; + } + + return msg; +}; + +eventQueue.logEvent = function eventQueue_logEvent( + aOrigEvent, + aMatchedChecker, + aScenarioIdx, + aEventIdx, + aAreExpectedEventsLeft, + aInvokerStatus +) { + // Dump DOM event information. Skip a11y event since it is dumped by + // gA11yEventObserver. + if (Event.isInstance(aOrigEvent)) { + var info = "Event type: " + eventQueue.getEventTypeAsString(aOrigEvent); + info += ". Target: " + eventQueue.getEventTargetDescr(aOrigEvent); + gLogger.logToDOM(info); + } + + var infoMsg = + "unhandled expected events: " + + aAreExpectedEventsLeft + + ", " + + eventQueue.invokerStatusToMsg(aInvokerStatus); + + var currType = eventQueue.getEventTypeAsString(aMatchedChecker); + var currTargetDescr = eventQueue.getEventTargetDescr(aMatchedChecker); + var consoleMsg = + "*****\nScenario " + + aScenarioIdx + + ", event " + + aEventIdx + + " matched: " + + currType + + "\n" + + infoMsg + + "\n*****"; + gLogger.logToConsole(consoleMsg); + + var emphText = "matched "; + var msg = + "EQ event, type: " + + currType + + ", target: " + + currTargetDescr + + ", " + + infoMsg; + gLogger.logToDOM(msg, true, emphText); +}; + +// ////////////////////////////////////////////////////////////////////////////// +// Action sequence + +/** + * Deal with action sequence. Used when you need to execute couple of actions + * each after other one. + */ +function sequence() { + /** + * Append new sequence item. + * + * @param aProcessor [in] object implementing interface + * { + * // execute item action + * process: function() {}, + * // callback, is called when item was processed + * onProcessed: function() {} + * }; + * @param aEventType [in] event type of expected event on item action + * @param aTarget [in] event target of expected event on item action + * @param aItemID [in] identifier of item + */ + this.append = function sequence_append( + aProcessor, + aEventType, + aTarget, + aItemID + ) { + var item = new sequenceItem(aProcessor, aEventType, aTarget, aItemID); + this.items.push(item); + }; + + /** + * Process next sequence item. + */ + this.processNext = function sequence_processNext() { + this.idx++; + if (this.idx >= this.items.length) { + ok(false, "End of sequence: nothing to process!"); + SimpleTest.finish(); + return; + } + + this.items[this.idx].startProcess(); + }; + + this.items = []; + this.idx = -1; +} + +// ////////////////////////////////////////////////////////////////////////////// +// Event queue invokers + +/** + * Defines a scenario of expected/unexpected events. Each invoker can have + * one or more scenarios of events. Only one scenario must be completed. + */ +function defineScenario(aInvoker, aEventSeq, aUnexpectedEventSeq) { + if (!("scenarios" in aInvoker)) { + aInvoker.scenarios = []; + } + + // Create unified event sequence concatenating expected and unexpected + // events. + if (!aEventSeq) { + aEventSeq = []; + } + + for (var idx = 0; idx < aEventSeq.length; idx++) { + aEventSeq[idx].unexpected |= false; + aEventSeq[idx].async |= false; + } + + if (aUnexpectedEventSeq) { + for (var idx = 0; idx < aUnexpectedEventSeq.length; idx++) { + aUnexpectedEventSeq[idx].unexpected = true; + aUnexpectedEventSeq[idx].async = false; + } + + aEventSeq = aEventSeq.concat(aUnexpectedEventSeq); + } + + aInvoker.scenarios.push(aEventSeq); +} + +/** + * Invokers defined below take a checker object (or array of checker objects). + * An invoker listens for default event type registered in event queue object + * until its checker is provided. + * + * Note, checker object or array of checker objects is optional. + */ + +/** + * Click invoker. + */ +function synthClick(aNodeOrID, aCheckerOrEventSeq, aArgs) { + this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq); + + this.invoke = function synthClick_invoke() { + var targetNode = this.DOMNode; + if (targetNode.nodeType == targetNode.DOCUMENT_NODE) { + targetNode = this.DOMNode.body + ? this.DOMNode.body + : this.DOMNode.documentElement; + } + + // Scroll the node into view, otherwise synth click may fail. + if (isHTMLElement(targetNode)) { + targetNode.scrollIntoView(true); + } else if (isXULElement(targetNode)) { + var targetAcc = getAccessible(targetNode); + targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE); + } + + var x = 1, + y = 1; + if (aArgs && "where" in aArgs) { + if (aArgs.where == "right") { + if (isHTMLElement(targetNode)) { + x = targetNode.offsetWidth - 1; + } else if (isXULElement(targetNode)) { + x = targetNode.getBoundingClientRect().width - 1; + } + } else if (aArgs.where == "center") { + if (isHTMLElement(targetNode)) { + x = targetNode.offsetWidth / 2; + y = targetNode.offsetHeight / 2; + } else if (isXULElement(targetNode)) { + x = targetNode.getBoundingClientRect().width / 2; + y = targetNode.getBoundingClientRect().height / 2; + } + } + } + synthesizeMouse(targetNode, x, y, aArgs ? aArgs : {}); + }; + + this.finalCheck = function synthClick_finalCheck() { + // Scroll top window back. + window.top.scrollTo(0, 0); + }; + + this.getID = function synthClick_getID() { + return prettyName(aNodeOrID) + " click"; + }; +} + +/** + * Scrolls the node into view. + */ +function scrollIntoView(aNodeOrID, aCheckerOrEventSeq) { + this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq); + + this.invoke = function scrollIntoView_invoke() { + var targetNode = this.DOMNode; + if (isHTMLElement(targetNode)) { + targetNode.scrollIntoView(true); + } else if (isXULElement(targetNode)) { + var targetAcc = getAccessible(targetNode); + targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE); + } + }; + + this.getID = function scrollIntoView_getID() { + return prettyName(aNodeOrID) + " scrollIntoView"; + }; +} + +/** + * Mouse move invoker. + */ +function synthMouseMove(aID, aCheckerOrEventSeq) { + this.__proto__ = new synthAction(aID, aCheckerOrEventSeq); + + this.invoke = function synthMouseMove_invoke() { + synthesizeMouse(this.DOMNode, 5, 5, { type: "mousemove" }); + synthesizeMouse(this.DOMNode, 6, 6, { type: "mousemove" }); + }; + + this.getID = function synthMouseMove_getID() { + return prettyName(aID) + " mouse move"; + }; +} + +/** + * General key press invoker. + */ +function synthKey(aNodeOrID, aKey, aArgs, aCheckerOrEventSeq) { + this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq); + + this.invoke = function synthKey_invoke() { + synthesizeKey(this.mKey, this.mArgs, this.mWindow); + }; + + this.getID = function synthKey_getID() { + var key = this.mKey; + switch (this.mKey) { + case "VK_TAB": + key = "tab"; + break; + case "VK_DOWN": + key = "down"; + break; + case "VK_UP": + key = "up"; + break; + case "VK_LEFT": + key = "left"; + break; + case "VK_RIGHT": + key = "right"; + break; + case "VK_HOME": + key = "home"; + break; + case "VK_END": + key = "end"; + break; + case "VK_ESCAPE": + key = "escape"; + break; + case "VK_RETURN": + key = "enter"; + break; + } + if (aArgs) { + if (aArgs.shiftKey) { + key += " shift"; + } + if (aArgs.ctrlKey) { + key += " ctrl"; + } + if (aArgs.altKey) { + key += " alt"; + } + } + return prettyName(aNodeOrID) + " '" + key + " ' key"; + }; + + this.mKey = aKey; + this.mArgs = aArgs ? aArgs : {}; + this.mWindow = aArgs ? aArgs.window : null; +} + +/** + * Tab key invoker. + */ +function synthTab(aNodeOrID, aCheckerOrEventSeq, aWindow) { + this.__proto__ = new synthKey( + aNodeOrID, + "VK_TAB", + { shiftKey: false, window: aWindow }, + aCheckerOrEventSeq + ); +} + +/** + * Shift tab key invoker. + */ +function synthShiftTab(aNodeOrID, aCheckerOrEventSeq) { + this.__proto__ = new synthKey( + aNodeOrID, + "VK_TAB", + { shiftKey: true }, + aCheckerOrEventSeq + ); +} + +/** + * Escape key invoker. + */ +function synthEscapeKey(aNodeOrID, aCheckerOrEventSeq) { + this.__proto__ = new synthKey( + aNodeOrID, + "VK_ESCAPE", + null, + aCheckerOrEventSeq + ); +} + +/** + * Down arrow key invoker. + */ +function synthDownKey(aNodeOrID, aCheckerOrEventSeq, aArgs) { + this.__proto__ = new synthKey( + aNodeOrID, + "VK_DOWN", + aArgs, + aCheckerOrEventSeq + ); +} + +/** + * Up arrow key invoker. + */ +function synthUpKey(aNodeOrID, aCheckerOrEventSeq, aArgs) { + this.__proto__ = new synthKey(aNodeOrID, "VK_UP", aArgs, aCheckerOrEventSeq); +} + +/** + * Left arrow key invoker. + */ +function synthLeftKey(aNodeOrID, aCheckerOrEventSeq, aArgs) { + this.__proto__ = new synthKey( + aNodeOrID, + "VK_LEFT", + aArgs, + aCheckerOrEventSeq + ); +} + +/** + * Right arrow key invoker. + */ +function synthRightKey(aNodeOrID, aCheckerOrEventSeq, aArgs) { + this.__proto__ = new synthKey( + aNodeOrID, + "VK_RIGHT", + aArgs, + aCheckerOrEventSeq + ); +} + +/** + * Home key invoker. + */ +function synthHomeKey(aNodeOrID, aCheckerOrEventSeq) { + this.__proto__ = new synthKey(aNodeOrID, "VK_HOME", null, aCheckerOrEventSeq); +} + +/** + * End key invoker. + */ +function synthEndKey(aNodeOrID, aCheckerOrEventSeq) { + this.__proto__ = new synthKey(aNodeOrID, "VK_END", null, aCheckerOrEventSeq); +} + +/** + * Enter key invoker + */ +function synthEnterKey(aID, aCheckerOrEventSeq) { + this.__proto__ = new synthKey(aID, "VK_RETURN", null, aCheckerOrEventSeq); +} + +/** + * Synth alt + down arrow to open combobox. + */ +function synthOpenComboboxKey(aID, aCheckerOrEventSeq) { + this.__proto__ = new synthDownKey(aID, aCheckerOrEventSeq, { altKey: true }); + + this.getID = function synthOpenComboboxKey_getID() { + return "open combobox (alt + down arrow) " + prettyName(aID); + }; +} + +/** + * Focus invoker. + */ +function synthFocus(aNodeOrID, aCheckerOrEventSeq) { + var checkerOfEventSeq = aCheckerOrEventSeq + ? aCheckerOrEventSeq + : new focusChecker(aNodeOrID); + this.__proto__ = new synthAction(aNodeOrID, checkerOfEventSeq); + + this.invoke = function synthFocus_invoke() { + if (this.DOMNode.editor) { + this.DOMNode.selectionStart = this.DOMNode.selectionEnd = + this.DOMNode.value.length; + } + this.DOMNode.focus(); + }; + + this.getID = function synthFocus_getID() { + return prettyName(aNodeOrID) + " focus"; + }; +} + +/** + * Focus invoker. Focus the HTML body of content document of iframe. + */ +function synthFocusOnFrame(aNodeOrID, aCheckerOrEventSeq) { + var frameDoc = getNode(aNodeOrID).contentDocument; + var checkerOrEventSeq = aCheckerOrEventSeq + ? aCheckerOrEventSeq + : new focusChecker(frameDoc); + this.__proto__ = new synthAction(frameDoc, checkerOrEventSeq); + + this.invoke = function synthFocus_invoke() { + this.DOMNode.body.focus(); + }; + + this.getID = function synthFocus_getID() { + return prettyName(aNodeOrID) + " frame document focus"; + }; +} + +/** + * Change the current item when the widget doesn't have a focus. + */ +function changeCurrentItem(aID, aItemID) { + this.eventSeq = [new nofocusChecker()]; + + this.invoke = function changeCurrentItem_invoke() { + var controlNode = getNode(aID); + var itemNode = getNode(aItemID); + + // HTML + if (controlNode.localName == "input") { + if (controlNode.checked) { + this.reportError(); + } + + controlNode.checked = true; + return; + } + + if (controlNode.localName == "select") { + if (controlNode.selectedIndex == itemNode.index) { + this.reportError(); + } + + controlNode.selectedIndex = itemNode.index; + return; + } + + // XUL + if (controlNode.localName == "tree") { + if (controlNode.currentIndex == aItemID) { + this.reportError(); + } + + controlNode.currentIndex = aItemID; + return; + } + + if (controlNode.localName == "menulist") { + if (controlNode.selectedItem == itemNode) { + this.reportError(); + } + + controlNode.selectedItem = itemNode; + return; + } + + if (controlNode.currentItem == itemNode) { + ok( + false, + "Error in test: proposed current item is already current" + + prettyName(aID) + ); + } + + controlNode.currentItem = itemNode; + }; + + this.getID = function changeCurrentItem_getID() { + return "current item change for " + prettyName(aID); + }; + + this.reportError = function changeCurrentItem_reportError() { + ok( + false, + "Error in test: proposed current item '" + + aItemID + + "' is already current" + ); + }; +} + +/** + * Toggle top menu invoker. + */ +function toggleTopMenu(aID, aCheckerOrEventSeq) { + this.__proto__ = new synthKey(aID, "VK_ALT", null, aCheckerOrEventSeq); + + this.getID = function toggleTopMenu_getID() { + return "toggle top menu on " + prettyName(aID); + }; +} + +/** + * Context menu invoker. + */ +function synthContextMenu(aID, aCheckerOrEventSeq) { + this.__proto__ = new synthClick(aID, aCheckerOrEventSeq, { + button: 0, + type: "contextmenu", + }); + + this.getID = function synthContextMenu_getID() { + return "context menu on " + prettyName(aID); + }; +} + +/** + * Open combobox, autocomplete and etc popup, check expandable states. + */ +function openCombobox(aComboboxID) { + this.eventSeq = [ + new stateChangeChecker(STATE_EXPANDED, false, true, aComboboxID), + ]; + + this.invoke = function openCombobox_invoke() { + getNode(aComboboxID).focus(); + synthesizeKey("VK_DOWN", { altKey: true }); + }; + + this.getID = function openCombobox_getID() { + return "open combobox " + prettyName(aComboboxID); + }; +} + +/** + * Close combobox, autocomplete and etc popup, check expandable states. + */ +function closeCombobox(aComboboxID) { + this.eventSeq = [ + new stateChangeChecker(STATE_EXPANDED, false, false, aComboboxID), + ]; + + this.invoke = function closeCombobox_invoke() { + synthesizeKey("KEY_Escape"); + }; + + this.getID = function closeCombobox_getID() { + return "close combobox " + prettyName(aComboboxID); + }; +} + +/** + * Select all invoker. + */ +function synthSelectAll(aNodeOrID, aCheckerOrEventSeq) { + this.__proto__ = new synthAction(aNodeOrID, aCheckerOrEventSeq); + + this.invoke = function synthSelectAll_invoke() { + if (ChromeUtils.getClassName(this.DOMNode) === "HTMLInputElement") { + this.DOMNode.select(); + } else { + window.getSelection().selectAllChildren(this.DOMNode); + } + }; + + this.getID = function synthSelectAll_getID() { + return aNodeOrID + " selectall"; + }; +} + +/** + * Move the caret to the end of line. + */ +function moveToLineEnd(aID, aCaretOffset) { + if (MAC) { + this.__proto__ = new synthKey( + aID, + "VK_RIGHT", + { metaKey: true }, + new caretMoveChecker(aCaretOffset, true, aID) + ); + } else { + this.__proto__ = new synthEndKey( + aID, + new caretMoveChecker(aCaretOffset, true, aID) + ); + } + + this.getID = function moveToLineEnd_getID() { + return "move to line end in " + prettyName(aID); + }; +} + +/** + * Move the caret to the end of previous line if any. + */ +function moveToPrevLineEnd(aID, aCaretOffset) { + this.__proto__ = new synthAction( + aID, + new caretMoveChecker(aCaretOffset, true, aID) + ); + + this.invoke = function moveToPrevLineEnd_invoke() { + synthesizeKey("KEY_ArrowUp"); + + if (MAC) { + synthesizeKey("Key_ArrowRight", { metaKey: true }); + } else { + synthesizeKey("KEY_End"); + } + }; + + this.getID = function moveToPrevLineEnd_getID() { + return "move to previous line end in " + prettyName(aID); + }; +} + +/** + * Move the caret to begining of the line. + */ +function moveToLineStart(aID, aCaretOffset) { + if (MAC) { + this.__proto__ = new synthKey( + aID, + "VK_LEFT", + { metaKey: true }, + new caretMoveChecker(aCaretOffset, true, aID) + ); + } else { + this.__proto__ = new synthHomeKey( + aID, + new caretMoveChecker(aCaretOffset, true, aID) + ); + } + + this.getID = function moveToLineEnd_getID() { + return "move to line start in " + prettyName(aID); + }; +} + +/** + * Move the caret to begining of the text. + */ +function moveToTextStart(aID) { + if (MAC) { + this.__proto__ = new synthKey( + aID, + "VK_UP", + { metaKey: true }, + new caretMoveChecker(0, true, aID) + ); + } else { + this.__proto__ = new synthKey( + aID, + "VK_HOME", + { ctrlKey: true }, + new caretMoveChecker(0, true, aID) + ); + } + + this.getID = function moveToTextStart_getID() { + return "move to text start in " + prettyName(aID); + }; +} + +/** + * Move the caret in text accessible. + */ +function moveCaretToDOMPoint( + aID, + aDOMPointNodeID, + aDOMPointOffset, + aExpectedOffset, + aFocusTargetID, + aCheckFunc +) { + this.target = getAccessible(aID, [nsIAccessibleText]); + this.DOMPointNode = getNode(aDOMPointNodeID); + this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null; + this.focusNode = this.focus ? this.focus.DOMNode : null; + + this.invoke = function moveCaretToDOMPoint_invoke() { + if (this.focusNode) { + this.focusNode.focus(); + } + + var selection = this.DOMPointNode.ownerGlobal.getSelection(); + var selRange = selection.getRangeAt(0); + selRange.setStart(this.DOMPointNode, aDOMPointOffset); + selRange.collapse(true); + + selection.removeRange(selRange); + selection.addRange(selRange); + }; + + this.getID = function moveCaretToDOMPoint_getID() { + return ( + "Set caret on " + + prettyName(aID) + + " at point: " + + prettyName(aDOMPointNodeID) + + " node with offset " + + aDOMPointOffset + ); + }; + + this.finalCheck = function moveCaretToDOMPoint_finalCheck() { + if (aCheckFunc) { + aCheckFunc.call(); + } + }; + + this.eventSeq = [new caretMoveChecker(aExpectedOffset, true, this.target)]; + + if (this.focus) { + this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus)); + } +} + +/** + * Set caret offset in text accessible. + */ +function setCaretOffset(aID, aOffset, aFocusTargetID) { + this.target = getAccessible(aID, [nsIAccessibleText]); + this.offset = aOffset == -1 ? this.target.characterCount : aOffset; + this.focus = aFocusTargetID ? getAccessible(aFocusTargetID) : null; + + this.invoke = function setCaretOffset_invoke() { + this.target.caretOffset = this.offset; + }; + + this.getID = function setCaretOffset_getID() { + return "Set caretOffset on " + prettyName(aID) + " at " + this.offset; + }; + + this.eventSeq = [new caretMoveChecker(this.offset, true, this.target)]; + + if (this.focus) { + this.eventSeq.push(new asyncInvokerChecker(EVENT_FOCUS, this.focus)); + } +} + +// ////////////////////////////////////////////////////////////////////////////// +// Event queue checkers + +/** + * Common invoker checker (see eventSeq of eventQueue). + */ +function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync) { + this.type = aEventType; + this.async = aIsAsync; + + this.__defineGetter__("target", invokerChecker_targetGetter); + this.__defineSetter__("target", invokerChecker_targetSetter); + + // implementation details + function invokerChecker_targetGetter() { + if (typeof this.mTarget == "function") { + return this.mTarget.call(null, this.mTargetFuncArg); + } + if (typeof this.mTarget == "string") { + return getNode(this.mTarget); + } + + return this.mTarget; + } + + function invokerChecker_targetSetter(aValue) { + this.mTarget = aValue; + return this.mTarget; + } + + this.__defineGetter__("targetDescr", invokerChecker_targetDescrGetter); + + function invokerChecker_targetDescrGetter() { + if (typeof this.mTarget == "function") { + return this.mTarget.name + ", arg: " + this.mTargetFuncArg; + } + + return prettyName(this.mTarget); + } + + this.mTarget = aTargetOrFunc; + this.mTargetFuncArg = aTargetFuncArg; +} + +/** + * event checker that forces preceeding async events to happen before this + * checker. + */ +function orderChecker() { + // XXX it doesn't actually work to inherit from invokerChecker, but maybe we + // should fix that? + // this.__proto__ = new invokerChecker(null, null, null, false); +} + +/** + * Generic invoker checker for todo events. + */ +function todo_invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) { + this.__proto__ = new invokerChecker( + aEventType, + aTargetOrFunc, + aTargetFuncArg, + true + ); + this.todo = true; +} + +/** + * Generic invoker checker for unexpected events. + */ +function unexpectedInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) { + this.__proto__ = new invokerChecker( + aEventType, + aTargetOrFunc, + aTargetFuncArg, + true + ); + + this.unexpected = true; +} + +/** + * Common invoker checker for async events. + */ +function asyncInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg) { + this.__proto__ = new invokerChecker( + aEventType, + aTargetOrFunc, + aTargetFuncArg, + true + ); +} + +function focusChecker(aTargetOrFunc, aTargetFuncArg) { + this.__proto__ = new invokerChecker( + EVENT_FOCUS, + aTargetOrFunc, + aTargetFuncArg, + false + ); + + this.unique = true; // focus event must be unique for invoker action + + this.check = function focusChecker_check(aEvent) { + testStates(aEvent.accessible, STATE_FOCUSED); + }; +} + +function nofocusChecker(aID) { + this.__proto__ = new focusChecker(aID); + this.unexpected = true; +} + +/** + * Text inserted/removed events checker. + * @param aFromUser [in, optional] kNotFromUserInput or kFromUserInput + */ +function textChangeChecker( + aID, + aStart, + aEnd, + aTextOrFunc, + aIsInserted, + aFromUser, + aAsync +) { + this.target = getNode(aID); + this.type = aIsInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED; + this.startOffset = aStart; + this.endOffset = aEnd; + this.textOrFunc = aTextOrFunc; + this.async = aAsync; + + this.match = function stextChangeChecker_match(aEvent) { + if ( + !(aEvent instanceof nsIAccessibleTextChangeEvent) || + aEvent.accessible !== getAccessible(this.target) + ) { + return false; + } + + let tcEvent = aEvent.QueryInterface(nsIAccessibleTextChangeEvent); + let modifiedText = + typeof this.textOrFunc === "function" + ? this.textOrFunc() + : this.textOrFunc; + return modifiedText === tcEvent.modifiedText; + }; + + this.check = function textChangeChecker_check(aEvent) { + aEvent.QueryInterface(nsIAccessibleTextChangeEvent); + + var modifiedText = + typeof this.textOrFunc == "function" + ? this.textOrFunc() + : this.textOrFunc; + var modifiedTextLen = + this.endOffset == -1 ? modifiedText.length : aEnd - aStart; + + is( + aEvent.start, + this.startOffset, + "Wrong start offset for " + prettyName(aID) + ); + is(aEvent.length, modifiedTextLen, "Wrong length for " + prettyName(aID)); + var changeInfo = aIsInserted ? "inserted" : "removed"; + is( + aEvent.isInserted, + aIsInserted, + "Text was " + changeInfo + " for " + prettyName(aID) + ); + is( + aEvent.modifiedText, + modifiedText, + "Wrong " + changeInfo + " text for " + prettyName(aID) + ); + if (typeof aFromUser != "undefined") { + is( + aEvent.isFromUserInput, + aFromUser, + "wrong value of isFromUserInput() for " + prettyName(aID) + ); + } + }; +} + +/** + * Caret move events checker. + */ +function caretMoveChecker( + aCaretOffset, + aIsSelectionCollapsed, + aTargetOrFunc, + aTargetFuncArg, + aIsAsync +) { + this.__proto__ = new invokerChecker( + EVENT_TEXT_CARET_MOVED, + aTargetOrFunc, + aTargetFuncArg, + aIsAsync + ); + + this.check = function caretMoveChecker_check(aEvent) { + let evt = aEvent.QueryInterface(nsIAccessibleCaretMoveEvent); + is( + evt.caretOffset, + aCaretOffset, + "Wrong caret offset for " + prettyName(aEvent.accessible) + ); + is( + evt.isSelectionCollapsed, + aIsSelectionCollapsed, + "wrong collapsed value for " + prettyName(aEvent.accessible) + ); + }; +} + +function asyncCaretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg) { + this.__proto__ = new caretMoveChecker( + aCaretOffset, + true, // Caret is collapsed + aTargetOrFunc, + aTargetFuncArg, + true + ); +} + +/** + * Text selection change checker. + */ +function textSelectionChecker( + aID, + aStartOffset, + aEndOffset, + aRangeStartContainer, + aRangeStartOffset, + aRangeEndContainer, + aRangeEndOffset +) { + this.__proto__ = new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID); + + this.check = function textSelectionChecker_check(aEvent) { + if (aStartOffset == aEndOffset) { + ok(true, "Collapsed selection triggered text selection change event."); + } else { + testTextGetSelection(aID, aStartOffset, aEndOffset, 0); + + // Test selection test range + let selectionRanges = aEvent.QueryInterface( + nsIAccessibleTextSelectionChangeEvent + ).selectionRanges; + let range = selectionRanges.queryElementAt(0, nsIAccessibleTextRange); + is( + range.startContainer, + getAccessible(aRangeStartContainer), + "correct range start container" + ); + is(range.startOffset, aRangeStartOffset, "correct range start offset"); + is(range.endOffset, aRangeEndOffset, "correct range end offset"); + is( + range.endContainer, + getAccessible(aRangeEndContainer), + "correct range end container" + ); + } + }; +} + +/** + * Object attribute changed checker + */ +function objAttrChangedChecker(aID, aAttr) { + this.__proto__ = new invokerChecker(EVENT_OBJECT_ATTRIBUTE_CHANGED, aID); + + this.check = function objAttrChangedChecker_check(aEvent) { + var event = null; + try { + var event = aEvent.QueryInterface( + nsIAccessibleObjectAttributeChangedEvent + ); + } catch (e) { + ok(false, "Object attribute changed event was expected"); + } + + if (!event) { + return; + } + + is( + event.changedAttribute, + aAttr, + "Wrong attribute name of the object attribute changed event." + ); + }; + + this.match = function objAttrChangedChecker_match(aEvent) { + if (aEvent instanceof nsIAccessibleObjectAttributeChangedEvent) { + var scEvent = aEvent.QueryInterface( + nsIAccessibleObjectAttributeChangedEvent + ); + return ( + aEvent.accessible == getAccessible(this.target) && + scEvent.changedAttribute == aAttr + ); + } + return false; + }; +} + +/** + * State change checker. + */ +function stateChangeChecker( + aState, + aIsExtraState, + aIsEnabled, + aTargetOrFunc, + aTargetFuncArg, + aIsAsync, + aSkipCurrentStateCheck +) { + this.__proto__ = new invokerChecker( + EVENT_STATE_CHANGE, + aTargetOrFunc, + aTargetFuncArg, + aIsAsync + ); + + this.check = function stateChangeChecker_check(aEvent) { + var event = null; + try { + var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent); + } catch (e) { + ok(false, "State change event was expected"); + } + + if (!event) { + return; + } + + is( + event.isExtraState, + aIsExtraState, + "Wrong extra state bit of the statechange event." + ); + isState( + event.state, + aState, + aIsExtraState, + "Wrong state of the statechange event." + ); + is(event.isEnabled, aIsEnabled, "Wrong state of statechange event state"); + + if (aSkipCurrentStateCheck) { + todo(false, "State checking was skipped!"); + return; + } + + var state = aIsEnabled ? (aIsExtraState ? 0 : aState) : 0; + var extraState = aIsEnabled ? (aIsExtraState ? aState : 0) : 0; + var unxpdState = aIsEnabled ? 0 : aIsExtraState ? 0 : aState; + var unxpdExtraState = aIsEnabled ? 0 : aIsExtraState ? aState : 0; + testStates( + event.accessible, + state, + extraState, + unxpdState, + unxpdExtraState + ); + }; + + this.match = function stateChangeChecker_match(aEvent) { + if (aEvent instanceof nsIAccessibleStateChangeEvent) { + var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent); + return ( + aEvent.accessible == getAccessible(this.target) && + scEvent.state == aState + ); + } + return false; + }; +} + +function asyncStateChangeChecker( + aState, + aIsExtraState, + aIsEnabled, + aTargetOrFunc, + aTargetFuncArg +) { + this.__proto__ = new stateChangeChecker( + aState, + aIsExtraState, + aIsEnabled, + aTargetOrFunc, + aTargetFuncArg, + true + ); +} + +/** + * Expanded state change checker. + */ +function expandedStateChecker(aIsEnabled, aTargetOrFunc, aTargetFuncArg) { + this.__proto__ = new invokerChecker( + EVENT_STATE_CHANGE, + aTargetOrFunc, + aTargetFuncArg + ); + + this.check = function expandedStateChecker_check(aEvent) { + var event = null; + try { + var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent); + } catch (e) { + ok(false, "State change event was expected"); + } + + if (!event) { + return; + } + + is(event.state, STATE_EXPANDED, "Wrong state of the statechange event."); + is( + event.isExtraState, + false, + "Wrong extra state bit of the statechange event." + ); + is(event.isEnabled, aIsEnabled, "Wrong state of statechange event state"); + + testStates(event.accessible, aIsEnabled ? STATE_EXPANDED : STATE_COLLAPSED); + }; +} + +// ////////////////////////////////////////////////////////////////////////////// +// Event sequances (array of predefined checkers) + +/** + * Event seq for single selection change. + */ +function selChangeSeq(aUnselectedID, aSelectedID) { + if (!aUnselectedID) { + return [ + new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID), + new invokerChecker(EVENT_SELECTION, aSelectedID), + ]; + } + + // Return two possible scenarios: depending on widget type when selection is + // moved the the order of items that get selected and unselected may vary. + return [ + [ + new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID), + new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID), + new invokerChecker(EVENT_SELECTION, aSelectedID), + ], + [ + new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID), + new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID), + new invokerChecker(EVENT_SELECTION, aSelectedID), + ], + ]; +} + +/** + * Event seq for item removed form the selection. + */ +function selRemoveSeq(aUnselectedID) { + return [ + new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID), + new invokerChecker(EVENT_SELECTION_REMOVE, aUnselectedID), + ]; +} + +/** + * Event seq for item added to the selection. + */ +function selAddSeq(aSelectedID) { + return [ + new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID), + new invokerChecker(EVENT_SELECTION_ADD, aSelectedID), + ]; +} + +// ////////////////////////////////////////////////////////////////////////////// +// Private implementation details. +// ////////////////////////////////////////////////////////////////////////////// + +// ////////////////////////////////////////////////////////////////////////////// +// General + +var gA11yEventListeners = {}; +var gA11yEventApplicantsCount = 0; + +var gA11yEventObserver = { + // eslint-disable-next-line complexity + observe: function observe(aSubject, aTopic, aData) { + if (aTopic != "accessible-event") { + return; + } + + var event; + try { + event = aSubject.QueryInterface(nsIAccessibleEvent); + } catch (ex) { + // After a test is aborted (i.e. timed out by the harness), this exception is soon triggered. + // Remove the leftover observer, otherwise it "leaks" to all the following tests. + Services.obs.removeObserver(this, "accessible-event"); + // Forward the exception, with added explanation. + throw new Error( + "[accessible/events.js, gA11yEventObserver.observe] This is expected " + + `if a previous test has been aborted... Initial exception was: [ ${ex} ]` + ); + } + var listenersArray = gA11yEventListeners[event.eventType]; + + var eventFromDumpArea = false; + if (gLogger.isEnabled()) { + // debug stuff + eventFromDumpArea = true; + + var target = event.DOMNode; + var dumpElm = gA11yEventDumpID + ? document.getElementById(gA11yEventDumpID) + : null; + + if (dumpElm) { + var parent = target; + while (parent && parent != dumpElm) { + parent = parent.parentNode; + } + } + + if (!dumpElm || parent != dumpElm) { + var type = eventTypeToString(event.eventType); + var info = "Event type: " + type; + + if (event instanceof nsIAccessibleStateChangeEvent) { + var stateStr = statesToString( + event.isExtraState ? 0 : event.state, + event.isExtraState ? event.state : 0 + ); + info += ", state: " + stateStr + ", is enabled: " + event.isEnabled; + } else if (event instanceof nsIAccessibleTextChangeEvent) { + info += + ", start: " + + event.start + + ", length: " + + event.length + + ", " + + (event.isInserted ? "inserted" : "removed") + + " text: " + + event.modifiedText; + } + + info += ". Target: " + prettyName(event.accessible); + + if (listenersArray) { + info += ". Listeners count: " + listenersArray.length; + } + + if (gLogger.hasFeature("parentchain:" + type)) { + info += "\nParent chain:\n"; + var acc = event.accessible; + while (acc) { + info += " " + prettyName(acc) + "\n"; + acc = acc.parent; + } + } + + eventFromDumpArea = false; + gLogger.log(info); + } + } + + // Do not notify listeners if event is result of event log changes. + if (!listenersArray || eventFromDumpArea) { + return; + } + + for (var index = 0; index < listenersArray.length; index++) { + listenersArray[index].handleEvent(event); + } + }, +}; + +function listenA11yEvents(aStartToListen) { + if (aStartToListen) { + // Add observer when adding the first applicant only. + if (!gA11yEventApplicantsCount++) { + Services.obs.addObserver(gA11yEventObserver, "accessible-event"); + } + } else { + // Remove observer when there are no more applicants only. + // '< 0' case should not happen, but just in case: removeObserver() will throw. + // eslint-disable-next-line no-lonely-if + if (--gA11yEventApplicantsCount <= 0) { + Services.obs.removeObserver(gA11yEventObserver, "accessible-event"); + } + } +} + +function addA11yEventListener(aEventType, aEventHandler) { + if (!(aEventType in gA11yEventListeners)) { + gA11yEventListeners[aEventType] = []; + } + + var listenersArray = gA11yEventListeners[aEventType]; + var index = listenersArray.indexOf(aEventHandler); + if (index == -1) { + listenersArray.push(aEventHandler); + } +} + +function removeA11yEventListener(aEventType, aEventHandler) { + var listenersArray = gA11yEventListeners[aEventType]; + if (!listenersArray) { + return false; + } + + var index = listenersArray.indexOf(aEventHandler); + if (index == -1) { + return false; + } + + listenersArray.splice(index, 1); + + if (!listenersArray.length) { + gA11yEventListeners[aEventType] = null; + delete gA11yEventListeners[aEventType]; + } + + return true; +} + +/** + * Used to dump debug information. + */ +var gLogger = { + /** + * Return true if dump is enabled. + */ + isEnabled: function debugOutput_isEnabled() { + return ( + gA11yEventDumpID || gA11yEventDumpToConsole || gA11yEventDumpToAppConsole + ); + }, + + /** + * Dump information into DOM and console if applicable. + */ + log: function logger_log(aMsg) { + this.logToConsole(aMsg); + this.logToAppConsole(aMsg); + this.logToDOM(aMsg); + }, + + /** + * Log message to DOM. + * + * @param aMsg [in] the primary message + * @param aHasIndent [in, optional] if specified the message has an indent + * @param aPreEmphText [in, optional] the text is colored and appended prior + * primary message + */ + logToDOM: function logger_logToDOM(aMsg, aHasIndent, aPreEmphText) { + if (gA11yEventDumpID == "") { + return; + } + + var dumpElm = document.getElementById(gA11yEventDumpID); + if (!dumpElm) { + ok( + false, + "No dump element '" + gA11yEventDumpID + "' within the document!" + ); + return; + } + + var containerTagName = + ChromeUtils.getClassName(document) == "HTMLDocument" + ? "div" + : "description"; + + var container = document.createElement(containerTagName); + if (aHasIndent) { + container.setAttribute("style", "padding-left: 10px;"); + } + + if (aPreEmphText) { + var inlineTagName = + ChromeUtils.getClassName(document) == "HTMLDocument" + ? "span" + : "description"; + var emphElm = document.createElement(inlineTagName); + emphElm.setAttribute("style", "color: blue;"); + emphElm.textContent = aPreEmphText; + + container.appendChild(emphElm); + } + + var textNode = document.createTextNode(aMsg); + container.appendChild(textNode); + + dumpElm.appendChild(container); + }, + + /** + * Log message to console. + */ + logToConsole: function logger_logToConsole(aMsg) { + if (gA11yEventDumpToConsole) { + dump("\n" + aMsg + "\n"); + } + }, + + /** + * Log message to error console. + */ + logToAppConsole: function logger_logToAppConsole(aMsg) { + if (gA11yEventDumpToAppConsole) { + Services.console.logStringMessage("events: " + aMsg); + } + }, + + /** + * Return true if logging feature is enabled. + */ + hasFeature: function logger_hasFeature(aFeature) { + var startIdx = gA11yEventDumpFeature.indexOf(aFeature); + if (startIdx == -1) { + return false; + } + + var endIdx = startIdx + aFeature.length; + return ( + endIdx == gA11yEventDumpFeature.length || + gA11yEventDumpFeature[endIdx] == ";" + ); + }, +}; + +// ////////////////////////////////////////////////////////////////////////////// +// Sequence + +/** + * Base class of sequence item. + */ +function sequenceItem(aProcessor, aEventType, aTarget, aItemID) { + // private + + this.startProcess = function sequenceItem_startProcess() { + this.queue.invoke(); + }; + + this.queue = new eventQueue(); + this.queue.onFinish = function () { + aProcessor.onProcessed(); + return DO_NOT_FINISH_TEST; + }; + + var invoker = { + invoke: function invoker_invoke() { + return aProcessor.process(); + }, + getID: function invoker_getID() { + return aItemID; + }, + eventSeq: [new invokerChecker(aEventType, aTarget)], + }; + + this.queue.push(invoker); +} + +// ////////////////////////////////////////////////////////////////////////////// +// Event queue invokers + +/** + * Invoker base class for prepare an action. + */ +function synthAction(aNodeOrID, aEventsObj) { + this.DOMNode = getNode(aNodeOrID); + + if (aEventsObj) { + var scenarios = null; + if (aEventsObj instanceof Array) { + if (aEventsObj[0] instanceof Array) { + scenarios = aEventsObj; + } + // scenarios + else { + scenarios = [aEventsObj]; + } // event sequance + } else { + scenarios = [[aEventsObj]]; // a single checker object + } + + for (var i = 0; i < scenarios.length; i++) { + defineScenario(this, scenarios[i]); + } + } + + this.getID = function synthAction_getID() { + return prettyName(aNodeOrID) + " action"; + }; +} diff --git a/accessible/tests/mochitest/events/a11y.ini b/accessible/tests/mochitest/events/a11y.ini new file mode 100644 index 0000000000..40ef776175 --- /dev/null +++ b/accessible/tests/mochitest/events/a11y.ini @@ -0,0 +1,68 @@ +[DEFAULT] +support-files = + focus.html + scroll.html + slow_image.sjs + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + !/image/test/mochitest/animated-gif-finalframe.gif + !/image/test/mochitest/animated-gif.gif + +[test_announcement.html] +[test_aria_alert.html] +[test_aria_menu.html] +[test_aria_objattr.html] +[test_aria_owns.html] +[test_aria_statechange.html] +[test_attrs.html] +[test_attrchange.html] +[test_bug1322593.html] +[test_bug1322593-2.html] +[test_caretmove.html] +[test_coalescence.html] +[test_contextmenu.html] +[test_descrchange.html] +[test_dragndrop.html] +[test_flush.html] +[test_focusable_statechange.html] +[test_focus_aria_activedescendant.html] +[test_focus_autocomplete.html] +[test_focus_autocomplete.xhtml] +# Disabled on Linux and Windows due to frequent failures - bug 695019, bug 890795 +skip-if = os == 'win' || os == 'linux' +[test_focus_canvas.html] +[test_focus_contextmenu.xhtml] +[test_focus_controls.html] +[test_focus_doc.html] +[test_focus_general.html] +[test_focus_general.xhtml] +[test_focus_listcontrols.xhtml] +[test_focus_menu.xhtml] +[test_focus_name.html] +[test_focus_removal.html] +[test_focus_selects.html] +[test_focus_tabbox.xhtml] +skip-if = true +[test_focus_tree.xhtml] +[test_fromUserInput.html] +[test_label.xhtml] +[test_menu.xhtml] +[test_mutation.html] +[test_namechange.xhtml] +[test_namechange.html] +[test_scroll.xhtml] +[test_scroll_caret.xhtml] +[test_selection.html] +skip-if = os == 'mac' +[test_selection.xhtml] +skip-if = os == 'mac' +[test_selection_aria.html] +[test_statechange.html] +[test_statechange.xhtml] +[test_text.html] +[test_text_alg.html] +[test_textattrchange.html] +[test_textselchange.html] +[test_tree.xhtml] +[test_valuechange.html] +skip-if = os == 'mac' diff --git a/accessible/tests/mochitest/events/docload/a11y.ini b/accessible/tests/mochitest/events/docload/a11y.ini new file mode 100644 index 0000000000..6e014d511c --- /dev/null +++ b/accessible/tests/mochitest/events/docload/a11y.ini @@ -0,0 +1,13 @@ +[DEFAULT] +support-files = + docload_wnd.html + !/accessible/tests/mochitest/*.js + +[test_docload_aria.html] +[test_docload_busy.html] +[test_docload_embedded.html] +[test_docload_iframe.html] +[test_docload_root.html] +skip-if = os == 'mac' # bug 1456997 +[test_docload_shutdown.html] +skip-if = os == 'mac' # bug 1456997 diff --git a/accessible/tests/mochitest/events/docload/docload_wnd.html b/accessible/tests/mochitest/events/docload/docload_wnd.html new file mode 100644 index 0000000000..93df1e86d4 --- /dev/null +++ b/accessible/tests/mochitest/events/docload/docload_wnd.html @@ -0,0 +1,37 @@ +<html> +<head> + <title>Accessible events testing for document</title> + <script> + const STATE_BUSY = Ci.nsIAccessibleStates.STATE_BUSY; + + var gService = null; + function waitForDocLoad() { + if (!gService) { + gService = Cc["@mozilla.org/accessibilityService;1"]. + getService(Ci.nsIAccessibilityService); + } + + var accDoc = gService.getAccessibleFor(document); + + var state = {}; + accDoc.getState(state, {}); + if (state.value & STATE_BUSY) { + window.setTimeout(waitForDocLoad, 0); + return; + } + + hideIFrame(); + } + + function hideIFrame() { + var iframe = document.getElementById("iframe"); + gService.getAccessibleFor(iframe.contentDocument); + iframe.style.display = "none"; + } + </script> +</head> + +<body onload="waitForDocLoad();"> + <iframe id="iframe"></iframe> +</body> +</html> diff --git a/accessible/tests/mochitest/events/docload/test_docload_aria.html b/accessible/tests/mochitest/events/docload/test_docload_aria.html new file mode 100644 index 0000000000..c5fc099918 --- /dev/null +++ b/accessible/tests/mochitest/events/docload/test_docload_aria.html @@ -0,0 +1,75 @@ +<html> + +<head> + <title>Accessible events testing for ARIA document</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../../common.js"></script> + <script type="application/javascript" + src="../../role.js"></script> + <script type="application/javascript" + src="../../states.js"></script> + <script type="application/javascript" + src="../../events.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function showARIADialog(aID) { + this.dialogNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, this.dialogNode), + ]; + + this.invoke = function showARIADialog_invoke() { + this.dialogNode.style.display = "block"; + }; + + this.getID = function showARIADialog_getID() { + return "show ARIA dialog"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new showARIADialog("dialog")); + gQueue.push(new showARIADialog("document")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=759833" + title="ARIA documents should fire document loading events"> + Mozilla Bug 759833 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div role="dialog" id="dialog" style="display: none;">It's a dialog</div> + <div role="document" id="document" style="display: none;">It's a document</div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/docload/test_docload_busy.html b/accessible/tests/mochitest/events/docload/test_docload_busy.html new file mode 100644 index 0000000000..37caf306bb --- /dev/null +++ b/accessible/tests/mochitest/events/docload/test_docload_busy.html @@ -0,0 +1,83 @@ +<html> + +<head> + <title>Accessible events testing for document</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../../common.js"></script> + <script type="application/javascript" + src="../../role.js"></script> + <script type="application/javascript" + src="../../states.js"></script> + <script type="application/javascript" + src="../../events.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function makeIFrameVisible(aID) { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.DOMNode.parentNode), + { + type: EVENT_STATE_CHANGE, + get target() { + return getAccessible("iframe").firstChild; + }, + match(aEvent) { + // The document shouldn't have busy state (the DOM document was + // loaded before its accessible was created). Do this test lately to + // make sure the content of document accessible was created + // initially, prior to this the document accessible keeps busy + // state. The initial creation happens asynchronously after document + // creation, there are no events we could use to catch it. + let { state, isEnabled } = aEvent.QueryInterface(nsIAccessibleStateChangeEvent); + return state & STATE_BUSY && !isEnabled; + }, + }, + ]; + + this.invoke = () => (this.DOMNode.style.visibility = "visible"); + + this.getID = () => + "The accessible for DOM document loaded before it's shown shouldn't have busy state."; + } + + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + function doTests() { + const gQueue = new eventQueue(); + gQueue.push(new makeIFrameVisible("iframe")); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=658185" + title="The DOM document loaded before it's shown shouldn't have busy state"> + Mozilla Bug 658185 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="testContainer"><iframe id="iframe" src="about:mozilla" style="visibility: hidden;"></iframe></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/docload/test_docload_embedded.html b/accessible/tests/mochitest/events/docload/test_docload_embedded.html new file mode 100644 index 0000000000..18873dc904 --- /dev/null +++ b/accessible/tests/mochitest/events/docload/test_docload_embedded.html @@ -0,0 +1,85 @@ +<html> + +<head> + <title>Accessible events testing for document</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../../common.js"></script> + <script type="application/javascript" + src="../../role.js"></script> + <script type="application/javascript" + src="../../events.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function changeIframeSrc(aIdentifier, aURL, aTitle) { + this.DOMNode = getNode(aIdentifier); + + function getIframeDoc() { + return getAccessible(getNode(aIdentifier).contentDocument); + } + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getAccessible(this.DOMNode)), + new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getIframeDoc), + ]; + + this.invoke = () => (this.DOMNode.src = aURL); + + this.finalCheck = () => + testAccessibleTree(this.DOMNode, { + role: ROLE_INTERNAL_FRAME, + children: [ + { + role: ROLE_DOCUMENT, + name: aTitle, + }, + ], + }); + + this.getID = () => `change iframe src on ${aURL}`; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + function doTests() { + const gQueue = new eventQueue(); + gQueue.push(new changeIframeSrc("iframe", "about:license", "Licenses")); + gQueue.push(new changeIframeSrc("iframe", "about:buildconfig", "Build Configuration")); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=420845" + title="Fire event_reorder on any embedded frames/iframes whos document has just loaded"> + Mozilla Bug 420845 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=754165" + title="Fire document load events on iframes too"> + Mozilla Bug 754165 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="testContainer"><iframe id="iframe"></iframe></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/docload/test_docload_iframe.html b/accessible/tests/mochitest/events/docload/test_docload_iframe.html new file mode 100644 index 0000000000..d410ebb7e2 --- /dev/null +++ b/accessible/tests/mochitest/events/docload/test_docload_iframe.html @@ -0,0 +1,99 @@ +<html> + +<head> + <title>Accessible events testing for document</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../../common.js"></script> + <script type="application/javascript" + src="../../role.js"></script> + <script type="application/javascript" + src="../../states.js"></script> + <script type="application/javascript" + src="../../events.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + const kHide = 1; + const kShow = 2; + const kRemove = 3; + + function morphIFrame(aIdentifier, aAction) { + this.DOMNode = getNode(aIdentifier); + this.IFrameContainerDOMNode = this.DOMNode.parentNode; + + this.eventSeq = [ + new invokerChecker(aAction === kShow ? EVENT_SHOW : EVENT_HIDE, this.DOMNode), + new invokerChecker(EVENT_REORDER, this.IFrameContainerDOMNode), + ]; + + this.invoke = () => { + if (aAction === kRemove) { + this.IFrameContainerDOMNode.removeChild(this.DOMNode); + } else { + this.DOMNode.style.display = aAction === kHide ? "none" : "block"; + } + }; + + this.finalCheck = () => + testAccessibleTree(this.IFrameContainerDOMNode, { + role: ROLE_SECTION, + children: (aAction == kHide || aAction == kRemove) ? [ ] : + [ + { + role: ROLE_INTERNAL_FRAME, + children: [ + { role: ROLE_DOCUMENT }, + ], + }, + ], + }); + + this.getID = () => { + if (aAction === kRemove) { + return "remove iframe"; + } + + return `change display style of iframe to ${aAction === kHide ? "none" : "block"}`; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + function doTests() { + const gQueue = new eventQueue(EVENT_REORDER); + gQueue.push(new morphIFrame("iframe", kHide)); + gQueue.push(new morphIFrame("iframe", kShow)); + gQueue.push(new morphIFrame("iframe", kRemove)); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=566103" + title="Reorganize accessible document handling"> + Mozilla Bug 566103 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="testContainer"><iframe id="iframe"></iframe></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/docload/test_docload_root.html b/accessible/tests/mochitest/events/docload/test_docload_root.html new file mode 100644 index 0000000000..91ce3a10ee --- /dev/null +++ b/accessible/tests/mochitest/events/docload/test_docload_root.html @@ -0,0 +1,125 @@ +<html> + +<head> + <title>Accessible events testing for document</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../../common.js"></script> + <script type="application/javascript" + src="../../role.js"></script> + <script type="application/javascript" + src="../../events.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + let gDialog; + let gDialogDoc; + let gRootAcc; + + function openDialogWnd(aURL) { + // Get application root accessible. + let docAcc = getAccessible(document); + while (docAcc) { + gRootAcc = docAcc; + try { + docAcc = docAcc.parent; + } catch (e) { + ok(false, `Can't get parent for ${prettyName(docAcc)}`); + throw e; + } + } + + this.eventSeq = [ + new asyncInvokerChecker(EVENT_REORDER, gRootAcc), + // We use a function here to get the target because gDialog isn't set + // yet, but it will be when the function is called. + new invokerChecker(EVENT_FOCUS, () => gDialog.document) + ]; + + this.invoke = () => (gDialog = window.browsingContext.topChromeWindow.openDialog(aURL)); + + this.finalCheck = () => { + const accTree = { + role: ROLE_APP_ROOT, + children: [ + { + role: ROLE_CHROME_WINDOW, + }, + { + role: ROLE_CHROME_WINDOW, + }, + ], + }; + + testAccessibleTree(gRootAcc, accTree); + + gDialogDoc = gDialog.document; + ok(isAccessibleInCache(gDialogDoc), + `The document accessible for '${aURL}' is not in cache!`); + }; + + this.getID = () => `open dialog '${aURL}'`; + } + + function closeDialogWnd() { + this.eventSeq = [ new invokerChecker(EVENT_FOCUS, getAccessible(document)) ]; + + this.invoke = () => { + gDialog.close(); + window.focus(); + }; + + this.finalCheck = () => { + ok(!isAccessibleInCache(gDialogDoc), + `The document accessible for dialog is in cache still!`); + + gDialog = gDialogDoc = gRootAcc = null; + }; + + this.getID = () => "close dialog"; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + function doTests() { + // Front end stuff sometimes likes to stuff things in the hidden window(s) + // in which case we should repress all accessibles for those. + + // Try to create an accessible for the hidden window's document. + let doc = Services.appShell.hiddenDOMWindow.document; + let hiddenDocAcc = gAccService.getAccessibleFor(doc); + ok(!hiddenDocAcc, "hiddenDOMWindow should not have an accessible."); + + const gQueue = new eventQueue(); + gQueue.push(new openDialogWnd("about:about")); + gQueue.push(new closeDialogWnd()); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=506206" + title="Fire event_reorder application root accessible"> + Mozilla Bug 506206 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/events/docload/test_docload_shutdown.html b/accessible/tests/mochitest/events/docload/test_docload_shutdown.html new file mode 100644 index 0000000000..a111d9e43b --- /dev/null +++ b/accessible/tests/mochitest/events/docload/test_docload_shutdown.html @@ -0,0 +1,142 @@ +<html> + +<head> + <title>Accessible events testing for document</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../../common.js"></script> + <script type="application/javascript" + src="../../role.js"></script> + <script type="application/javascript" + src="../../events.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + let gDialog; + let gDialogDoc; + let gRootAcc; + let gIframeDoc; + + function openWndShutdownDoc(aURL) { + // Get application root accessible. + let docAcc = getAccessible(document); + while (docAcc) { + gRootAcc = docAcc; + try { + docAcc = docAcc.parent; + } catch (e) { + ok(false, `Can't get parent for ${prettyName(docAcc)}`); + throw e; + } + } + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, gRootAcc), + { + type: EVENT_HIDE, + get target() { + gDialogDoc = gDialog.document; + const iframe = gDialogDoc.getElementById("iframe"); + gIframeDoc = iframe.contentDocument; + return iframe; + }, + get targetDescr() { + return "inner iframe of docload_wnd.html document"; + }, + }, + ]; + + + this.invoke = () => gDialog = window.browsingContext.topChromeWindow.openDialog(aURL); + + this.finalCheck = () => { + const accTree = { + role: ROLE_APP_ROOT, + children: [ + { + role: ROLE_CHROME_WINDOW, + }, + { + role: ROLE_CHROME_WINDOW, + }, + ], + }; + + testAccessibleTree(gRootAcc, accTree); + // After timeout after event hide for iframe was handled the document + // accessible for iframe's document should no longer be in cache. + ok(!isAccessibleInCache(gIframeDoc), + "The document accessible for iframe is in cache still after iframe hide!"); + ok(isAccessibleInCache(gDialogDoc), + `The document accessible for '${aURL}' is not in cache!`); + }; + + this.getID = () => `open dialog '${aURL}'`; + } + + function closeWndShutdownDoc() { + this.eventSeq = [ new invokerChecker(EVENT_FOCUS, getAccessible(document)) ]; + + this.invoke = () => { + gDialog.close(); + window.focus(); + }; + + this.finalCheck = () => { + ok(!isAccessibleInCache(gDialogDoc), + "The document accessible for dialog is in cache still!"); + // After the window is closed all alive subdocument accessibles should + // be shut down. + ok(!isAccessibleInCache(gIframeDoc), + "The document accessible for iframe is in cache still!"); + + gDialog = gDialogDoc = gRootAcc = gIframeDoc = null; + }; + + this.getID = () => "close dialog"; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + function doTests() { + // Front end stuff sometimes likes to stuff things in the hidden window(s) + // in which case we should repress all accessibles for those. + + // Try to create an accessible for the hidden window's document. + let doc = Services.appShell.hiddenDOMWindow.document; + let hiddenDocAcc = gAccService.getAccessibleFor(doc); + ok(!hiddenDocAcc, "hiddenDOMWindow should not have an accessible."); + + const gQueue = new eventQueue(); + gQueue.push(new openWndShutdownDoc("../../events/docload/docload_wnd.html")); + gQueue.push(new closeWndShutdownDoc()); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=571459" + title="Shutdown document accessible when presshell goes away"> + Mozilla Bug 571459 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/events/focus.html b/accessible/tests/mochitest/events/focus.html new file mode 100644 index 0000000000..ab055df82c --- /dev/null +++ b/accessible/tests/mochitest/events/focus.html @@ -0,0 +1,10 @@ +<html> + +<head> + <title>editable document</title> +</head> + +<body contentEditable="true"> + editable document +</body> +</html> diff --git a/accessible/tests/mochitest/events/scroll.html b/accessible/tests/mochitest/events/scroll.html new file mode 100644 index 0000000000..562e0a3825 --- /dev/null +++ b/accessible/tests/mochitest/events/scroll.html @@ -0,0 +1,181 @@ +<html> + +<head> + <title>nsIAccessible actions testing for anchors</title> +</head> + +<body> + <p> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + </p> + <a name="link1">link1</a> + + <p style="color: blue"> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + </p> + + <h1 id="heading_1">heading 1</h1> + <p style="color: blue"> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + text text text text text text text text text text text text text text <br> + </p> +</body> +<html> diff --git a/accessible/tests/mochitest/events/slow_image.sjs b/accessible/tests/mochitest/events/slow_image.sjs new file mode 100644 index 0000000000..f322568be6 --- /dev/null +++ b/accessible/tests/mochitest/events/slow_image.sjs @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// small red image +const IMG_BYTES = atob( + "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" + + "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" +); + +// stolen from file_blocked_script.sjs +function setGlobalState(data, key) { + let x = { + data, + QueryInterface: ChromeUtils.generateQI([]), + }; + x.wrappedJSObject = x; + setObjectState(key, x); +} + +function getGlobalState(key) { + var data; + getObjectState(key, function (x) { + data = x && x.wrappedJSObject.data; + }); + return data; +} + +function handleRequest(request, response) { + if (request.queryString == "complete") { + // Unblock the previous request. + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "application/json", false); + response.write("true"); // the payload doesn't matter. + + let blockedResponse = getGlobalState("a11y-image"); + if (blockedResponse) { + blockedResponse.setStatusLine(request.httpVersion, 200, "OK"); + blockedResponse.setHeader("Cache-Control", "no-cache", false); + blockedResponse.setHeader("Content-Type", "image/png", false); + blockedResponse.write(IMG_BYTES); + blockedResponse.finish(); + + setGlobalState(undefined, "a11y-image"); + } + } else { + // Getting the image + response.processAsync(); + // Store the response in the global state + setGlobalState(response, "a11y-image"); + } +} diff --git a/accessible/tests/mochitest/events/test_announcement.html b/accessible/tests/mochitest/events/test_announcement.html new file mode 100644 index 0000000000..eb303e4aa9 --- /dev/null +++ b/accessible/tests/mochitest/events/test_announcement.html @@ -0,0 +1,61 @@ +<html> + +<head> + <title>Announcement event and method testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + async function doTests() { + let acc = getAccessible("display"); + + let onAnnounce = waitForEvent(EVENT_ANNOUNCEMENT, acc); + acc.announce("please", nsIAccessibleAnnouncementEvent.POLITE); + let evt = await onAnnounce; + evt.QueryInterface(nsIAccessibleAnnouncementEvent); + is(evt.announcement, "please", "announcement matches."); + is(evt.priority, nsIAccessibleAnnouncementEvent.POLITE, "priority matches"); + + onAnnounce = waitForEvent(EVENT_ANNOUNCEMENT, acc); + acc.announce("do it", nsIAccessibleAnnouncementEvent.ASSERTIVE); + evt = await onAnnounce; + evt.QueryInterface(nsIAccessibleAnnouncementEvent); + is(evt.announcement, "do it", "announcement matches."); + is(evt.priority, nsIAccessibleAnnouncementEvent.ASSERTIVE, + "priority matches"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1525980" + title="Introduce announcement event and method"> + Mozilla Bug 1525980 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_aria_alert.html b/accessible/tests/mochitest/events/test_aria_alert.html new file mode 100644 index 0000000000..48f4197b50 --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_alert.html @@ -0,0 +1,84 @@ +<html> + +<head> + <title>ARIA alert event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function showAlert(aID) { + this.DOMNode = document.createElement("div"); + + this.invoke = function showAlert_invoke() { + this.DOMNode.setAttribute("role", "alert"); + this.DOMNode.setAttribute("id", aID); + var text = document.createTextNode("alert"); + this.DOMNode.appendChild(text); + document.body.appendChild(this.DOMNode); + }; + + this.getID = function showAlert_getID() { + return "Show ARIA alert " + aID; + }; + } + + function changeAlert(aID) { + this.__defineGetter__("DOMNode", function() { return getNode(aID); }); + + this.invoke = function changeAlert_invoke() { + this.DOMNode.textContent = "new alert"; + }; + + this.getID = function showAlert_getID() { + return "Change ARIA alert " + aID; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + // gA11yEventDumpToConsole = true; // debuging + // enableLogging("tree,events,verbose"); + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(nsIAccessibleEvent.EVENT_ALERT); + + gQueue.push(new showAlert("alert")); + gQueue.push(new changeAlert("alert")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=591199" + title="mochitest for bug 334386: fire alert event when ARIA alert is shown or new its children are inserted"> + Mozilla Bug 591199 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_aria_menu.html b/accessible/tests/mochitest/events/test_aria_menu.html new file mode 100644 index 0000000000..b240090cb9 --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_menu.html @@ -0,0 +1,267 @@ +<html> + +<head> + <title>ARIA menu events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + const kViaDisplayStyle = 0; + const kViaVisibilityStyle = 1; + + function focusMenu(aMenuBarID, aMenuID, aActiveMenuBarID) { + this.eventSeq = []; + + if (aActiveMenuBarID) { + this.eventSeq.push(new invokerChecker(EVENT_MENU_END, + getNode(aActiveMenuBarID))); + } + + this.eventSeq.push(new invokerChecker(EVENT_MENU_START, getNode(aMenuBarID))); + this.eventSeq.push(new invokerChecker(EVENT_FOCUS, getNode(aMenuID))); + + this.invoke = function focusMenu_invoke() { + getNode(aMenuID).focus(); + }; + + this.getID = function focusMenu_getID() { + return "focus menu '" + aMenuID + "'"; + }; + } + + function showMenu(aMenuID, aParentMenuID, aHow) { + this.menuNode = getNode(aMenuID); + + // Because of aria-owns processing we may have menupopup start fired before + // related show event. + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.menuNode), + new invokerChecker(EVENT_REORDER, getNode(aParentMenuID)), + new invokerChecker(EVENT_MENUPOPUP_START, this.menuNode), + ]; + + this.invoke = function showMenu_invoke() { + if (aHow == kViaDisplayStyle) + this.menuNode.style.display = "block"; + else + this.menuNode.style.visibility = "visible"; + }; + + this.getID = function showMenu_getID() { + return "Show ARIA menu '" + aMenuID + "' by " + + (aHow == kViaDisplayStyle ? "display" : "visibility") + + " style tricks"; + }; + } + + function closeMenu(aMenuID, aParentMenuID, aHow) { + this.menuNode = getNode(aMenuID); + this.menu = null; + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getMenu, this), + new invokerChecker(EVENT_MENUPOPUP_END, getMenu, this), + new invokerChecker(EVENT_REORDER, getNode(aParentMenuID)), + ]; + + this.invoke = function closeMenu_invoke() { + // Store menu accessible reference while menu is still open. + this.menu = getAccessible(this.menuNode); + + // Hide menu. + if (aHow == kViaDisplayStyle) + this.menuNode.style.display = "none"; + else + this.menuNode.style.visibility = "hidden"; + }; + + this.getID = function closeMenu_getID() { + return "Close ARIA menu " + aMenuID + " by " + + (aHow == kViaDisplayStyle ? "display" : "visibility") + + " style tricks"; + }; + + function getMenu(aThisObj) { + return aThisObj.menu; + } + } + + function focusInsideMenu(aMenuID, aMenuBarID) { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getNode(aMenuID)), + ]; + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_MENU_END, getNode(aMenuBarID)), + ]; + + this.invoke = function focusInsideMenu_invoke() { + getNode(aMenuID).focus(); + }; + + this.getID = function focusInsideMenu_getID() { + return "focus menu '" + aMenuID + "'"; + }; + } + + function blurMenu(aMenuBarID) { + var eventSeq = [ + new invokerChecker(EVENT_MENU_END, getNode(aMenuBarID)), + new invokerChecker(EVENT_FOCUS, getNode("outsidemenu")), + ]; + + this.__proto__ = new synthClick("outsidemenu", eventSeq); + + this.getID = function blurMenu_getID() { + return "blur menu"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + // gA11yEventDumpToConsole = true; // debuging + // enableLogging("tree,events,verbose"); + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new focusMenu("menubar2", "menu-help")); + gQueue.push(new focusMenu("menubar", "menu-file", "menubar2")); + gQueue.push(new showMenu("menupopup-file", "menu-file", kViaDisplayStyle)); + gQueue.push(new closeMenu("menupopup-file", "menu-file", kViaDisplayStyle)); + gQueue.push(new showMenu("menupopup-edit", "menu-edit", kViaVisibilityStyle)); + gQueue.push(new closeMenu("menupopup-edit", "menu-edit", kViaVisibilityStyle)); + gQueue.push(new focusInsideMenu("menu-edit", "menubar")); + gQueue.push(new blurMenu("menubar")); + + gQueue.push(new focusMenu("menubar3", "mb3-mi-outside")); + gQueue.push(new showMenu("mb4-menu", document, kViaDisplayStyle)); + gQueue.push(new focusMenu("menubar4", "mb4-item1")); + gQueue.push(new focusMenu("menubar5", "mb5-mi")); + + gQueue.push(new synthFocus("mi6")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=606207" + title="Dojo dropdown buttons are broken"> + Bug 606207 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=614829" + title="Menupopup end event isn't fired for ARIA menus"> + Bug 614829 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=615189" + title="Clean up FireAccessibleFocusEvent"> + Bug 615189 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" + title="Rework accessible focus handling"> + Bug 673958 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=933322" + title="menustart/end events are missing when aria-owns makes a menu hierarchy"> + Bug 933322 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=934460" + title="menustart/end events may be missed when top level menuitem is focused"> + Bug 934460 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=970005" + title="infinite long loop in a11y:FocusManager::ProcessFocusEvent"> + Bug 970005 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="menubar" role="menubar"> + <div id="menu-file" role="menuitem" tabindex="0"> + File + <div id="menupopup-file" role="menu" style="display: none;"> + <div id="menuitem-newtab" role="menuitem" tabindex="0">New Tab</div> + <div id="menuitem-newwindow" role="menuitem" tabindex="0">New Window</div> + </div> + </div> + <div id="menu-edit" role="menuitem" tabindex="0"> + Edit + <div id="menupopup-edit" role="menu" style="visibility: hidden;"> + <div id="menuitem-undo" role="menuitem" tabindex="0">Undo</div> + <div id="menuitem-redo" role="menuitem" tabindex="0">Redo</div> + </div> + </div> + </div> + <div id="menubar2" role="menubar"> + <div id="menu-help" role="menuitem" tabindex="0"> + Help + <div id="menupopup-help" role="menu" style="display: none;"> + <div id="menuitem-about" role="menuitem" tabindex="0">About</div> + </div> + </div> + </div> + <div tabindex="0" id="outsidemenu">outsidemenu</div> + + <!-- aria-owns relations --> + <div id="menubar3" role="menubar" aria-owns="mb3-mi-outside"></div> + <div id="mb3-mi-outside" role="menuitem" tabindex="0">Outside</div> + + <div id="menubar4" role="menubar"> + <div id="mb4_topitem" role="menuitem" aria-haspopup="true" + aria-owns="mb4-menu">Item</div> + </div> + <div id="mb4-menu" role="menu" style="display:none;"> + <div role="menuitem" id="mb4-item1" tabindex="0">Item 1.1</div> + <div role="menuitem" tabindex="0">Item 1.2</div> + </div> + + <!-- focus top-level menu item having haspopup --> + <div id="menubar5" role="menubar"> + <div role="menuitem" aria-haspopup="true" id="mb5-mi" tabindex="0"> + Item + <div role="menu" style="display:none;"> + <div role="menuitem" tabindex="0">Item 1.1</div> + <div role="menuitem" tabindex="0">Item 1.2</div> + </div> + </div> + </div> + + <!-- other aria-owns relations --> + <div id="mi6" tabindex="0" role="menuitem">aria-owned item</div> + <div aria-owns="mi6">Obla</div> + + <div id="eventdump"></div> + +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_aria_objattr.html b/accessible/tests/mochitest/events/test_aria_objattr.html new file mode 100644 index 0000000000..709089ca02 --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_objattr.html @@ -0,0 +1,68 @@ +<html> + +<head> + <title>Accessible ARIA object attribute changes</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + /** + * Do tests. + */ + var gQueue = null; + function updateAttribute(aID, aAttr, aValue) { + this.node = getNode(aID); + this.accessible = getAccessible(this.node); + + this.eventSeq = [ + new objAttrChangedChecker(aID, aAttr), + ]; + + this.invoke = function updateAttribute_invoke() { + this.node.setAttribute(aAttr, aValue); + }; + + this.getID = function updateAttribute_getID() { + return aAttr + " for " + aID + " " + aValue; + }; + } + + // gA11yEventDumpToConsole = true; + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new updateAttribute("sortable", "aria-sort", "ascending")); + + // For experimental ARIA extensions + gQueue.push(new updateAttribute("custom", "aria-blah", "true")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="sortable" role="columnheader" aria-sort="none">aria-sort</div> + + <div id="custom" role="custom" aria-blah="false">Fat free cheese</div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_aria_owns.html b/accessible/tests/mochitest/events/test_aria_owns.html new file mode 100644 index 0000000000..3c638ad838 --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_owns.html @@ -0,0 +1,122 @@ +<html> + +<head> + <title>Aria-owns targets shouldn't be on invalidation list so shouldn't have + show/hide events</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Do tests. + + // gA11yEventDumpToConsole = true; // debug stuff + // enableLogging("tree,eventTree,verbose"); + + /** + * Aria-owns target shouldn't have a show event. + * Markup: + * <div id="t1_fc" aria-owns="t1_owns"></div> + * <span id="t1_owns"></div> + */ + function testAriaOwns() { + this.parent = getNode("t1"); + this.fc = document.createElement("div"); + this.fc.setAttribute("id", "t1_fc"); + this.owns = document.createElement("span"); + this.owns.setAttribute("id", "t1_owns"); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.fc), + new unexpectedInvokerChecker(EVENT_SHOW, this.owns), + ]; + + this.invoke = function testAriaOwns_invoke() { + getNode("t1").appendChild(this.fc); + getNode("t1").appendChild(this.owns); + getNode("t1_fc").setAttribute("aria-owns", "t1_owns"); + }; + + this.getID = function testAriaOwns_getID() { + return "Aria-owns target shouldn't have show event"; + }; + } + + /** + * Target of both aria-owns and other aria attribute like aria-labelledby + * shouldn't have a show event. + * Markup: + * <div id="t2_fc" aria-owns="t1_owns"></div> + * <div id="t2_sc" aria-labelledby="t2_owns"></div> + * <span id="t2_owns"></div> + */ + function testAriaOwnsAndLabelledBy() { + this.parent = getNode("t2"); + this.fc = document.createElement("div"); + this.fc.setAttribute("id", "t2_fc"); + this.sc = document.createElement("div"); + this.sc.setAttribute("id", "t2_sc"); + this.owns = document.createElement("span"); + this.owns.setAttribute("id", "t2_owns"); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.fc), + new invokerChecker(EVENT_SHOW, this.sc), + new unexpectedInvokerChecker(EVENT_SHOW, this.owns), + ]; + + this.invoke = function testAriaOwns_invoke() { + getNode("t2").appendChild(this.fc); + getNode("t2").appendChild(this.sc); + getNode("t2").appendChild(this.owns); + getNode("t2_fc").setAttribute("aria-owns", "t2_owns"); + getNode("t2_sc").setAttribute("aria-labelledby", "t2_owns"); + }; + + this.getID = function testAriaOwns_getID() { + return "Aria-owns and aria-labelledby target shouldn't have show event"; + }; + } + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(); + gQueue.push(new testAriaOwns()); + gQueue.push(new testAriaOwnsAndLabelledBy()); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1296420" + title="Aria-owns targets shouldn't be on invalidation list so shouldn't + have show/hide events"> + Mozilla Bug 1296420 + </a><br> + + <div id="testContainer"> + <div id="t1"></div> + + <div id="t2"></div> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_aria_statechange.html b/accessible/tests/mochitest/events/test_aria_statechange.html new file mode 100644 index 0000000000..7796d88ec4 --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_statechange.html @@ -0,0 +1,231 @@ +<html> + +<head> + <title>ARIA state change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + let PromEvents = {}; + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js", + PromEvents); + + /** + * Do tests. + */ + var gQueue = null; + + // gA11yEventDumpID = "eventdump"; // debugging + // gA11yEventDumpToConsole = true; // debugging + + function expandNode(aID, aIsExpanded) { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new expandedStateChecker(aIsExpanded, this.DOMNode), + ]; + + this.invoke = function expandNode_invoke() { + this.DOMNode.setAttribute("aria-expanded", + (aIsExpanded ? "true" : "false")); + }; + + this.getID = function expandNode_getID() { + return prettyName(aID) + " aria-expanded changed to '" + aIsExpanded + "'"; + }; + } + + function busyify(aID, aIsBusy) { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new stateChangeChecker(STATE_BUSY, kOrdinalState, aIsBusy, this.DOMNode), + ]; + + this.invoke = function busyify_invoke() { + this.DOMNode.setAttribute("aria-busy", (aIsBusy ? "true" : "false")); + }; + + this.getID = function busyify_getID() { + return prettyName(aID) + " aria-busy changed to '" + aIsBusy + "'"; + }; + } + + function makeCurrent(aID, aIsCurrent, aValue) { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new stateChangeChecker(EXT_STATE_CURRENT, true, aIsCurrent, this.DOMNode), + ]; + + this.invoke = function makeCurrent_invoke() { + this.DOMNode.setAttribute("aria-current", aValue); + }; + + this.getID = function makeCurrent_getID() { + return prettyName(aID) + " aria-current changed to " + aValue; + }; + } + + async function testToggleAttribute(aID, aAttribute, aIncludeMixed) { + let toggleState = aAttribute == "aria-pressed" ? STATE_PRESSED : STATE_CHECKED; + + // bug 472142. Role changes here if aria-pressed is added, + // accessible should be recreated? + let stateChange = PromEvents.waitForStateChange(aID, toggleState, true); + getNode(aID).setAttribute(aAttribute, "true"); + await stateChange; + + stateChange = PromEvents.waitForStateChange(aID, toggleState, false); + getNode(aID).setAttribute(aAttribute, "false"); + await stateChange; + + if (aIncludeMixed) { + stateChange = PromEvents.waitForStateChange(aID, STATE_MIXED, true); + getNode(aID).setAttribute(aAttribute, "mixed"); + await stateChange; + + stateChange = PromEvents.waitForStateChange(aID, STATE_MIXED, false); + getNode(aID).setAttribute(aAttribute, ""); + await stateChange; + } + + stateChange = PromEvents.waitForStateChange(aID, toggleState, true); + getNode(aID).setAttribute(aAttribute, "true"); + await stateChange; + + if (aIncludeMixed) { + stateChange = Promise.all([ + PromEvents.waitForStateChange(aID, STATE_MIXED, true), + PromEvents.waitForStateChange(aID, toggleState, false)]); + getNode(aID).setAttribute(aAttribute, "mixed"); + await stateChange; + + stateChange = Promise.all([ + PromEvents.waitForStateChange(aID, STATE_MIXED, false), + PromEvents.waitForStateChange(aID, toggleState, true)]); + getNode(aID).setAttribute(aAttribute, "true"); + await stateChange; + } + + // bug 472142. Role changes here too if aria-pressed is removed, + // accessible should be recreated? + stateChange = PromEvents.waitForStateChange(aID, toggleState, false); + getNode(aID).removeAttribute(aAttribute); + await stateChange; + } + + async function doTests() { + gQueue = new eventQueue(); + + let queueFinished = new Promise(resolve => { + gQueue.onFinish = function() { + resolve(); + return DO_NOT_FINISH_TEST; + }; + }); + + gQueue.push(new expandNode("section", true)); + gQueue.push(new expandNode("section", false)); + gQueue.push(new expandNode("div", true)); + gQueue.push(new expandNode("div", false)); + + gQueue.push(new busyify("aria_doc", true)); + gQueue.push(new busyify("aria_doc", false)); + + gQueue.push(new makeCurrent("current_page_1", false, "false")); + gQueue.push(new makeCurrent("current_page_2", true, "page")); + gQueue.push(new makeCurrent("current_page_2", false, "false")); + gQueue.push(new makeCurrent("current_page_3", true, "true")); + gQueue.push(new makeCurrent("current_page_3", false, "")); + + gQueue.invoke(); + await queueFinished; + // Tests beyond this point use await rather than eventQueue. + + await testToggleAttribute("pressable", "aria-pressed", true); + await testToggleAttribute("pressable_native", "aria-pressed", true); + await testToggleAttribute("checkable", "aria-checked", true); + await testToggleAttribute("checkableBool", "aria-checked", false); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=551684" + title="No statechange event for aria-expanded on native HTML elements, is fired on ARIA widgets"> + Mozilla Bug 551684 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=648133" + title="fire state change event for aria-busy"> + Mozilla Bug 648133 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=467143" + title="mixed state change event is fired for focused accessible only"> + Mozilla Bug 467143 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958" + title="Pressed state is not exposed on a button element with aria-pressed attribute"> + Mozilla Bug 989958 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563" + title="Support ARIA 1.1 switch role"> + Mozilla Bug 1136563 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1355921" + title="Elements with a defined, non-false value for aria-current should expose ATK_STATE_ACTIVE"> + Mozilla Bug 1355921 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <!-- aria-expanded --> + <div id="section" role="section" aria-expanded="false">expandable section</div> + <div id="div" aria-expanded="false">expandable native div</div> + + <!-- aria-busy --> + <div id="aria_doc" role="document" tabindex="0">A document</div> + + <!-- aria-pressed --> + <div id="pressable" role="button"></div> + <button id="pressable_native"></button> + + <!-- aria-checked --> + <div id="checkable" role="checkbox"></div> + <div id="checkableBool" role="switch"></div> + + <!-- aria-current --> + <div id="current_page_1" role="link" aria-current="page">1</div> + <div id="current_page_2" role="link" aria-current="false">2</div> + <div id="current_page_3" role="link">3</div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_attrchange.html b/accessible/tests/mochitest/events/test_attrchange.html new file mode 100644 index 0000000000..edd9195ddd --- /dev/null +++ b/accessible/tests/mochitest/events/test_attrchange.html @@ -0,0 +1,107 @@ +<html> + +<head> + <title>Accessible attr change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + async function testGotAttrChange(elem, name, value) { + const waitFor = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, elem); + if (value) { + document.getElementById(elem).setAttribute(name, value); + } else { + document.getElementById(elem).removeAttribute(name); + } + await waitFor; + } + + async function doTests() { + info("Removing summary attr"); + // after summary is removed, we should have a layout table + await testGotAttrChange( + "sampleTable", + "summary", + null + ); + + info("Setting abbr attr"); + // after abbr is set we should have a data table again + await testGotAttrChange( + "cellOne", + "abbr", + "hello world" + ); + + info("Removing abbr attr"); + // after abbr is removed we should have a layout table again + await testGotAttrChange( + "cellOne", + "abbr", + null + ); + + info("Setting scope attr"); + // after scope is set we should have a data table again + await testGotAttrChange( + "cellOne", + "scope", + "col" + ); + + info("Removing scope attr"); + // remove scope should give layout + await testGotAttrChange( + "cellOne", + "scope", + null + ); + + info("Setting headers attr"); + // add headers attr should give data + await testGotAttrChange( + "cellThree", + "headers", + "cellOne" + ); + + info("Removing headers attr"); + // remove headers attr should give layout + await testGotAttrChange( + "cellThree", + "headers", + null + ); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> +<body> + <table id="sampleTable" summary="example summary"> + <tr> + <td id="cellOne">cell1</td> + <td>cell2</td> + </tr> + <tr> + <td id="cellThree">cell3</td> + <td>cell4</td> + </tr> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_attrs.html b/accessible/tests/mochitest/events/test_attrs.html new file mode 100644 index 0000000000..c09bd9cf1e --- /dev/null +++ b/accessible/tests/mochitest/events/test_attrs.html @@ -0,0 +1,85 @@ +<html> + +<head> + <title>Event object attributes tests</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + + /** + * Test "event-from-input" object attribute. + */ + function eventFromInputChecker(aEventType, aID, aValue, aNoTargetID) { + this.type = aEventType; + this.target = getAccessible(aID); + + this.noTarget = getNode(aNoTargetID); + + this.check = function checker_check(aEvent) { + testAttrs(aEvent.accessible, { "event-from-input": aValue }, true); + + var accessible = getAccessible(this.noTarget); + testAbsentAttrs(accessible, { "event-from-input": "" }); + }; + } + + /** + * Do tests. + */ + var gQueue = null; + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; // debug stuff + + function doTests() { + gQueue = new eventQueue(); + + var id = "textbox", noTargetId = "textarea"; + let checker = + new eventFromInputChecker(EVENT_FOCUS, id, "false", noTargetId); + gQueue.push(new synthFocus(id, checker)); + + if (!MAC) { // Mac failure is bug 541093 + checker = + new eventFromInputChecker(EVENT_TEXT_CARET_MOVED, id, "true", noTargetId); + gQueue.push(new synthHomeKey(id, checker)); + } + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=540285" + title="Event object attributes testing"> + Mozilla Bug 540285 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="textbox" value="hello"> + <textarea id="textarea"></textarea> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_bug1322593-2.html b/accessible/tests/mochitest/events/test_bug1322593-2.html new file mode 100644 index 0000000000..05bd31ffa6 --- /dev/null +++ b/accessible/tests/mochitest/events/test_bug1322593-2.html @@ -0,0 +1,77 @@ +<html> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function changeMultipleElements() { + this.node1 = getNode("span1"); + this.node2 = getNode("span2"); + + this.eventSeq = [ + new textChangeChecker("container", 0, 5, "hello", false, undefined, true), + new textChangeChecker("container", 6, 11, "world", false, undefined, true), + new orderChecker(), + new textChangeChecker("container", 0, 1, "a", true, undefined, true), + new textChangeChecker("container", 7, 8, "b", true, undefined, true), + ]; + + this.invoke = function changeMultipleElements_invoke() { + this.node1.textContent = "a"; + this.node2.textContent = "b"; + }; + + this.getID = function changeMultipleElements_invoke_getID() { + return "Change the text content of multiple sibling divs"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests +// gA11yEventDumpToConsole = true; // debugging + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new changeMultipleElements()); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322593" + title="missing text change events when multiple elements updated at once"> + Mozilla Bug 1322593 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="container"> + <span id="span1">hello</span> + <span>your</span> + <span id="span2">world</span> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_bug1322593.html b/accessible/tests/mochitest/events/test_bug1322593.html new file mode 100644 index 0000000000..968e808106 --- /dev/null +++ b/accessible/tests/mochitest/events/test_bug1322593.html @@ -0,0 +1,74 @@ +<html> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function changeMultipleElements() { + this.node1 = getNode("div1"); + this.node2 = getNode("div2"); + + this.eventSeq = [ + new textChangeChecker("div1", 0, 5, "hello", false, undefined, true), + new textChangeChecker("div2", 0, 5, "world", false, undefined, true), + new orderChecker(), + new textChangeChecker("div1", 0, 1, "a", true, undefined, true), + new textChangeChecker("div2", 0, 1, "b", true, undefined, true), + ]; + + this.invoke = function changeMultipleElements_invoke() { + this.node1.textContent = "a"; + this.node2.textContent = "b"; + }; + + this.getID = function changeMultipleElements_invoke_getID() { + return "Change the text content of multiple sibling divs"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests +// gA11yEventDumpToConsole = true; // debugging + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new changeMultipleElements()); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322593" + title="missing text change events when multiple elements updated at once"> + Mozilla Bug 1322593 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="div1">hello</div> + <div id="div2">world</div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_caretmove.html b/accessible/tests/mochitest/events/test_caretmove.html new file mode 100644 index 0000000000..d1091ac7f1 --- /dev/null +++ b/accessible/tests/mochitest/events/test_caretmove.html @@ -0,0 +1,142 @@ +<html> + +<head> + <title>Accessible caret move events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + /** + * Click checker. + */ + function clickChecker(aCaretOffset, aIsSelectionCollapsed, aID, aExtraNodeOrID, aExtraCaretOffset) { + this.__proto__ = new caretMoveChecker(aCaretOffset, aIsSelectionCollapsed, aID); + + this.extraNode = getNode(aExtraNodeOrID); + + this.check = function clickChecker_check(aEvent) { + this.__proto__.check(aEvent); + + if (this.extraNode) { + var acc = getAccessible(this.extraNode, [nsIAccessibleText]); + is(acc.caretOffset, aExtraCaretOffset, + "Wrong caret offset for " + aExtraNodeOrID); + } + }; + } + + /** + * Do tests. + */ + var gQueue = null; + + // gA11yEventDumpToConsole = true; + + function doTests() { + // test caret move events and caret offsets + gQueue = new eventQueue(); + + var id = "textbox"; + gQueue.push(new synthFocus(id, new caretMoveChecker(5, true, id))); + gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, false, id))); + gQueue.push(new synthClick(id, new caretMoveChecker(0, true, id))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, true, id))); + + if (!MAC) { + gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, false, id))); + gQueue.push(new synthHomeKey(id, new caretMoveChecker(0, true, id))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, true, id))); + } + else { + todo(false, "Make these tests pass on OSX (bug 650294)"); + } + + id = "textarea"; + gQueue.push(new synthClick(id, new caretMoveChecker(0, true, id))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, true, id))); + gQueue.push(new synthDownKey(id, new caretMoveChecker(12, true, id))); + + id = "textarea_wrapped"; + gQueue.push(new setCaretOffset(id, 4, id)); + gQueue.push(new synthLeftKey(id, new caretMoveChecker(4, true, id))); + + id = "p"; + gQueue.push(new synthClick(id, new caretMoveChecker(0, true, id))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, true, id))); + gQueue.push(new synthDownKey(id, new caretMoveChecker(6, true, id))); + + id = "p1_in_div"; + gQueue.push(new synthClick(id, new clickChecker(0, true, id, "p2_in_div", -1))); + + id = "p"; + gQueue.push(new synthShiftTab(id, new caretMoveChecker(0, true, id))); + id = "textarea"; + gQueue.push(new synthShiftTab(id, new caretMoveChecker(12, true, id))); + id = "p"; + gQueue.push(new synthTab(id, new caretMoveChecker(0, true, id))); + + // Set caret after a child of span element, i.e. after 'text' text. + gQueue.push(new moveCaretToDOMPoint("test1", getNode("test1_span"), 1, + 4, "test1")); + gQueue.push(new moveCaretToDOMPoint("test2", getNode("test2_span"), 1, + 4, "test2")); + + // empty text element + gQueue.push(new moveCaretToDOMPoint("test3", getNode("test3"), 0, + 0, "test3")); + gQueue.push(new moveCaretToDOMPoint("test4", getNode("test4_span"), 0, + 0, "test4")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=454377" + title="Accessible caret move events testing"> + Bug 454377 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=567571" + title="caret-moved events missing at the end of a wrapped line of text"> + Bug 567571 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=824901" + title="HyperTextAccessible::DOMPointToHypertextOffset fails for node and offset equal to node child count"> + Bug 824901 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="textbox" value="hello"/> + + <textarea id="textarea">text<br>text</textarea> + <p id="p" contentEditable="true"><span>text</span><br/>text</p> + <div id="div" contentEditable="true"><p id="p1_in_div">text</p><p id="p2_in_div">text</p></div> + + <p contentEditable="true" id="test1"><span id="test1_span">text</span>ohoho</p> + <p contentEditable="true" id="test2"><span><span id="test2_span">text</span></span>ohoho</p> + <p contentEditable="true" id="test3"></p> + <p contentEditable="true" id="test4"><span id="test4_span"></span></p> + + <textarea id="textarea_wrapped" cols="5">hey friend</textarea> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_coalescence.html b/accessible/tests/mochitest/events/test_coalescence.html new file mode 100644 index 0000000000..0f8ad52a8b --- /dev/null +++ b/accessible/tests/mochitest/events/test_coalescence.html @@ -0,0 +1,817 @@ +<html> + +<head> + <title>Accessible mutation events coalescence testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invoker base classes + + const kRemoveElm = 1; + const kHideElm = 2; + const kAddElm = 3; + const kShowElm = 4; + + /** + * Base class to test of mutation events coalescence. + */ + function coalescenceBase(aChildAction, aParentAction, + aPerformActionOnChildInTheFirstPlace) { + // Invoker interface + + this.invoke = function coalescenceBase_invoke() { + if (aPerformActionOnChildInTheFirstPlace) { + this.invokeAction(this.childNode, aChildAction); + this.invokeAction(this.parentNode, aParentAction); + } else { + this.invokeAction(this.parentNode, aParentAction); + this.invokeAction(this.childNode, aChildAction); + } + }; + + this.getID = function coalescenceBase_getID() { + var childAction = this.getActionName(aChildAction) + " child"; + var parentAction = this.getActionName(aParentAction) + " parent"; + + if (aPerformActionOnChildInTheFirstPlace) + return childAction + " and then " + parentAction; + + return parentAction + " and then " + childAction; + }; + + this.finalCheck = function coalescenceBase_check() { + if (this.getEventType(aChildAction) == EVENT_HIDE) { + testIsDefunct(this.child); + } + if (this.getEventType(aParentAction) == EVENT_HIDE) { + testIsDefunct(this.parent); + } + }; + + // Implementation details + + this.invokeAction = function coalescenceBase_invokeAction(aNode, aAction) { + switch (aAction) { + case kRemoveElm: + aNode.remove(); + break; + + case kHideElm: + aNode.style.display = "none"; + break; + + case kAddElm: + if (aNode == this.parentNode) + this.hostNode.appendChild(this.parentNode); + else + this.parentNode.appendChild(this.childNode); + break; + + case kShowElm: + aNode.style.display = "block"; + break; + + default: + return INVOKER_ACTION_FAILED; + } + // 0 means the action succeeded. + return 0; + }; + + this.getEventType = function coalescenceBase_getEventType(aAction) { + switch (aAction) { + case kRemoveElm: case kHideElm: + return EVENT_HIDE; + case kAddElm: case kShowElm: + return EVENT_SHOW; + } + return 0; + }; + + this.getActionName = function coalescenceBase_getActionName(aAction) { + switch (aAction) { + case kRemoveElm: + return "remove"; + case kHideElm: + return "hide"; + case kAddElm: + return "add"; + case kShowElm: + return "show"; + default: + return "??"; + } + }; + + this.initSequence = function coalescenceBase_initSequence() { + // expected events + var eventType = this.getEventType(aParentAction); + this.eventSeq = [ + new invokerChecker(eventType, this.parentNode), + new invokerChecker(EVENT_REORDER, this.hostNode), + ]; + + // unexpected events + this.unexpectedEventSeq = [ + new invokerChecker(this.getEventType(aChildAction), this.childNode), + new invokerChecker(EVENT_REORDER, this.parentNode), + ]; + }; + } + + /** + * Remove or hide mutation events coalescence testing. + */ + function removeOrHideCoalescenceBase(aChildID, aParentID, + aChildAction, aParentAction, + aPerformActionOnChildInTheFirstPlace) { + this.__proto__ = new coalescenceBase(aChildAction, aParentAction, + aPerformActionOnChildInTheFirstPlace); + + this.init = function removeOrHideCoalescenceBase_init() { + this.childNode = getNode(aChildID); + this.parentNode = getNode(aParentID); + this.child = getAccessible(this.childNode); + this.parent = getAccessible(this.parentNode); + this.hostNode = this.parentNode.parentNode; + }; + + // Initalization + + this.init(); + this.initSequence(); + } + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + /** + * Remove child node and then its parent node from DOM tree. + */ + function removeChildNParent(aChildID, aParentID) { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kRemoveElm, kRemoveElm, + true); + } + + /** + * Remove parent node and then its child node from DOM tree. + */ + function removeParentNChild(aChildID, aParentID) { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kRemoveElm, kRemoveElm, + false); + } + + /** + * Hide child node and then its parent node. + */ + function hideChildNParent(aChildID, aParentID) { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kHideElm, kHideElm, + true); + } + + /** + * Hide parent node and then its child node. + */ + function hideParentNChild(aChildID, aParentID) { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kHideElm, kHideElm, + false); + } + + /** + * Hide child node and then remove its parent node. + */ + function hideChildNRemoveParent(aChildID, aParentID) { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kHideElm, kRemoveElm, + true); + } + + /** + * Hide parent node and then remove its child node. + */ + function hideParentNRemoveChild(aChildID, aParentID) { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kRemoveElm, kHideElm, + false); + } + + /** + * Remove child node and then hide its parent node. + */ + function removeChildNHideParent(aChildID, aParentID) { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kRemoveElm, kHideElm, + true); + } + + /** + * Remove parent node and then hide its child node. + */ + function removeParentNHideChild(aChildID, aParentID) { + this.__proto__ = new removeOrHideCoalescenceBase(aChildID, aParentID, + kHideElm, kRemoveElm, + false); + } + + /** + * Create and append parent node and create and append child node to it. + */ + function addParentNChild(aHostID, aPerformActionOnChildInTheFirstPlace) { + this.init = function addParentNChild_init() { + this.hostNode = getNode(aHostID); + this.parentNode = document.createElement("select"); + this.childNode = document.createElement("option"); + this.childNode.textContent = "testing"; + }; + + this.__proto__ = new coalescenceBase(kAddElm, kAddElm, + aPerformActionOnChildInTheFirstPlace); + + this.init(); + this.initSequence(); + } + + /** + * Show parent node and show child node to it. + */ + function showParentNChild(aParentID, aChildID, + aPerformActionOnChildInTheFirstPlace) { + this.init = function showParentNChild_init() { + this.parentNode = getNode(aParentID); + this.hostNode = this.parentNode.parentNode; + this.childNode = getNode(aChildID); + }; + + this.__proto__ = new coalescenceBase(kShowElm, kShowElm, + aPerformActionOnChildInTheFirstPlace); + + this.init(); + this.initSequence(); + } + + /** + * Create and append child node to the DOM and then show parent node. + */ + function showParentNAddChild(aParentID, + aPerformActionOnChildInTheFirstPlace) { + this.init = function showParentNAddChild_init() { + this.parentNode = getNode(aParentID); + this.hostNode = this.parentNode.parentNode; + this.childNode = document.createElement("option"); + this.childNode.textContent = "testing"; + }; + + this.__proto__ = new coalescenceBase(kAddElm, kShowElm, + aPerformActionOnChildInTheFirstPlace); + + this.init(); + this.initSequence(); + } + + /** + * Remove children and parent + */ + function removeGrandChildrenNHideParent(aChild1Id, aChild2Id, aParentId) { + this.child1 = getNode(aChild1Id); + this.child2 = getNode(aChild2Id); + this.parent = getNode(aParentId); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getAccessible(aParentId)), + new invokerChecker(EVENT_REORDER, getNode(aParentId).parentNode), + new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild1Id)), + new unexpectedInvokerChecker(EVENT_HIDE, getAccessible(aChild2Id)), + new unexpectedInvokerChecker(EVENT_REORDER, getAccessible(aParentId)), + ]; + + this.invoke = function removeGrandChildrenNHideParent_invoke() { + this.child1.remove(); + this.child2.remove(); + this.parent.hidden = true; + }; + + this.getID = function removeGrandChildrenNHideParent_getID() { + return "remove grand children of different parents and then hide their grand parent"; + }; + } + + /** + * Remove a child, and then its parent. + */ + function test3() { + this.o = getAccessible("t3_o"); + this.ofc = getAccessible("t3_o").firstChild; + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.o), + new invokerChecker(EVENT_REORDER, "t3_lb"), + new unexpectedInvokerChecker(EVENT_HIDE, this.ofc), + new unexpectedInvokerChecker(EVENT_REORDER, this.o), + ]; + + this.invoke = function test3_invoke() { + getNode("t3_o").textContent = ""; + getNode("t3_lb").removeChild(getNode("t3_o")); + }; + + this.finalCheck = function test3_finalCheck() { + testIsDefunct(this.o); + testIsDefunct(this.ofc); + }; + + this.getID = function test3_getID() { + return "remove a child, and then its parent"; + }; + } + + /** + * Remove children, and then a parent of 2nd child. + */ + function test4() { + this.o1 = getAccessible("t4_o1"); + this.o1fc = this.o1.firstChild; + this.o2 = getAccessible("t4_o2"); + this.o2fc = this.o2.firstChild; + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.o1fc), + new invokerChecker(EVENT_HIDE, this.o2), + new invokerChecker(EVENT_REORDER, "t4_lb"), + new unexpectedInvokerChecker(EVENT_HIDE, this.o2fc), + new unexpectedInvokerChecker(EVENT_REORDER, this.o1), + new unexpectedInvokerChecker(EVENT_REORDER, this.o2), + ]; + + this.invoke = function test4_invoke() { + getNode("t4_o1").textContent = ""; + getNode("t4_o2").textContent = ""; + getNode("t4_lb").removeChild(getNode("t4_o2")); + }; + + this.finalCheck = function test4_finalCheck() { + testIsDefunct(this.o1fc); + testIsDefunct(this.o2); + testIsDefunct(this.o2fc); + }; + + this.getID = function test4_getID() { + return "remove children, and then a parent of 2nd child"; + }; + } + + /** + * Remove a child, remove a parent sibling, remove the parent + */ + function test5() { + this.o = getAccessible("t5_o"); + this.ofc = this.o.firstChild; + this.b = getAccessible("t5_b"); + this.lb = getAccessible("t5_lb"); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.b), + new invokerChecker(EVENT_HIDE, this.o), + new invokerChecker(EVENT_REORDER, "t5"), + new unexpectedInvokerChecker(EVENT_HIDE, this.ofc), + new unexpectedInvokerChecker(EVENT_REORDER, this.o), + new unexpectedInvokerChecker(EVENT_REORDER, this.lb), + ]; + + this.invoke = function test5_invoke() { + getNode("t5_o").textContent = ""; + getNode("t5").removeChild(getNode("t5_b")); + getNode("t5_lb").removeChild(getNode("t5_o")); + }; + + this.finalCheck = function test5_finalCheck() { + testIsDefunct(this.ofc); + testIsDefunct(this.o); + testIsDefunct(this.b); + }; + + this.getID = function test5_getID() { + return "remove a child, remove a parent sibling, remove the parent"; + }; + } + + /** + * Insert accessibles with a child node moved by aria-owns + * Markup: + * <div id="t6_fc"> + * <div id="t6_owns"></div> + * </div> + * <div id="t6_sc" aria-owns="t6_owns"></div> + */ + function test6() { + this.parent = getNode("t6"); + this.fc = document.createElement("div"); + this.fc.setAttribute("id", "t6_fc"); + this.owns = document.createElement("div"); + this.owns.setAttribute("id", "t6_owns"); + this.sc = document.createElement("div"); + this.sc.setAttribute("id", "t6_sc"); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.fc), + new invokerChecker(EVENT_SHOW, this.sc), + new invokerChecker(EVENT_REORDER, this.parent), + new unexpectedInvokerChecker(EVENT_REORDER, this.fc), + new unexpectedInvokerChecker(EVENT_REORDER, this.sc), + new unexpectedInvokerChecker(EVENT_HIDE, this.owns), + new unexpectedInvokerChecker(EVENT_SHOW, this.owns), + ]; + + this.invoke = function test6_invoke() { + getNode("t6").appendChild(this.fc); + getNode("t6_fc").appendChild(this.owns); + getNode("t6").appendChild(this.sc); + getNode("t6_sc").setAttribute("aria-owns", "t6_owns"); + }; + + this.getID = function test6_getID() { + return "Insert accessibles with a child node moved by aria-owns"; + }; + } + + /** + * Insert text nodes under direct and grand children, and then hide + * their container by means of aria-owns. + * + * Markup: + * <div id="t7_moveplace" aria-owns="t7_c"></div> + * <div id="t7_c"> + * <div id="t7_c_directchild">ha</div> + * <div><div id="t7_c_grandchild">ha</div></div> + * </div> + */ + function test7() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t7_c")), + new invokerChecker(EVENT_SHOW, getNode("t7_c")), + new invokerChecker(EVENT_REORDER, getNode("t7")), + new unexpectedInvokerChecker(EVENT_REORDER, getNode("t7_c_directchild")), + new unexpectedInvokerChecker(EVENT_REORDER, getNode("t7_c_grandchild")), + new unexpectedInvokerChecker(EVENT_SHOW, () => getNode("t7_c_directchild").firstChild), + new unexpectedInvokerChecker(EVENT_SHOW, () => getNode("t7_c_grandchild").firstChild), + ]; + + this.invoke = function test7_invoke() { + getNode("t7_c_directchild").textContent = "ha"; + getNode("t7_c_grandchild").textContent = "ha"; + getNode("t7_moveplace").setAttribute("aria-owns", "t7_c"); + }; + + this.getID = function test7_getID() { + return "Show child accessibles and then hide their container"; + }; + } + + /** + * Move a node by aria-owns from right to left in the tree, so that + * the eventing looks this way: + * reorder for 't8_c1' + * hide for 't8_c1_child' + * show for 't8_c2_moved' + * reorder for 't8_c2' + * hide for 't8_c2_moved' + * + * The hide event should be delivered before the paired show event. + */ + function test8() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t8_c1_child")), + new invokerChecker(EVENT_HIDE, "t8_c2_moved"), + new invokerChecker(EVENT_SHOW, "t8_c2_moved"), + new invokerChecker(EVENT_REORDER, "t8_c2"), + new invokerChecker(EVENT_REORDER, "t8_c1"), + ]; + + this.invoke = function test8_invoke() { + // Remove a node from 't8_c1' container to give the event tree a + // desired structure (the 't8_c1' container node goes first in the event + // tree) + getNode("t8_c1_child").remove(); + // then move 't8_c2_moved' from 't8_c2' to 't8_c1'. + getNode("t8_c1").setAttribute("aria-owns", "t8_c2_moved"); + }; + + this.getID = function test8_getID() { + return "Move a node by aria-owns to left within the tree"; + }; + } + + /** + * Move 't9_c3_moved' node under 't9_c2_moved', and then move 't9_c2_moved' + * node by aria-owns (same as test10 but has different aria-owns + * ordering), the eventing looks same way as in test10: + * reorder for 't9_c1' + * hide for 't9_c1_child' + * show for 't9_c2_moved' + * reorder for 't9_c2' + * hide for 't9_c2_child' + * hide for 't9_c2_moved' + * reorder for 't9_c3' + * hide for 't9_c3_moved' + * + * The hide events for 't9_c2_moved' and 't9_c3_moved' should be delivered + * before the show event for 't9_c2_moved'. + */ + function test9() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t9_c1_child")), + new invokerChecker(EVENT_HIDE, getNode("t9_c2_child")), + new invokerChecker(EVENT_HIDE, "t9_c3_moved"), + new invokerChecker(EVENT_HIDE, "t9_c2_moved"), + new invokerChecker(EVENT_SHOW, "t9_c2_moved"), + new invokerChecker(EVENT_REORDER, "t9_c3"), + new invokerChecker(EVENT_REORDER, "t9_c2"), + new invokerChecker(EVENT_REORDER, "t9_c1"), + new unexpectedInvokerChecker(EVENT_SHOW, "t9_c3_moved"), + ]; + + this.invoke = function test9_invoke() { + // Remove child nodes from 't9_c1' and 't9_c2' containers to give + // the event tree a needed structure ('t9_c1' and 't9_c2' nodes go + // first in the event tree), + getNode("t9_c1_child").remove(); + getNode("t9_c2_child").remove(); + // then do aria-owns magic. + getNode("t9_c2_moved").setAttribute("aria-owns", "t9_c3_moved"); + getNode("t9_c1").setAttribute("aria-owns", "t9_c2_moved"); + }; + + this.getID = function test9_getID() { + return "Move node #1 by aria-owns and then move node #2 into node #1"; + }; + } + + /** + * Move a node 't10_c3_moved' by aria-owns under a node 't10_c2_moved', + * moved by under 't10_1', so that the eventing looks this way: + * reorder for 't10_c1' + * hide for 't10_c1_child' + * show for 't10_c2_moved' + * reorder for 't10_c2' + * hide for 't10_c2_child' + * hide for 't10_c2_moved' + * reorder for 't10_c3' + * hide for 't10_c3_moved' + * + * The hide events for 't10_c2_moved' and 't10_c3_moved' should be delivered + * before the show event for 't10_c2_moved'. + */ + function test10() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t10_c1_child")), + new invokerChecker(EVENT_HIDE, getNode("t10_c2_child")), + new invokerChecker(EVENT_HIDE, getNode("t10_c2_moved")), + new invokerChecker(EVENT_HIDE, getNode("t10_c3_moved")), + new invokerChecker(EVENT_SHOW, getNode("t10_c2_moved")), + new invokerChecker(EVENT_REORDER, "t10_c2"), + new invokerChecker(EVENT_REORDER, "t10_c1"), + new invokerChecker(EVENT_REORDER, "t10_c3"), + ]; + + this.invoke = function test10_invoke() { + // Remove child nodes from 't10_c1' and 't10_c2' containers to give + // the event tree a needed structure ('t10_c1' and 't10_c2' nodes go first + // in the event tree), + getNode("t10_c1_child").remove(); + getNode("t10_c2_child").remove(); + // then do aria-owns stuff. + getNode("t10_c1").setAttribute("aria-owns", "t10_c2_moved"); + getNode("t10_c2_moved").setAttribute("aria-owns", "t10_c3_moved"); + }; + + this.getID = function test10_getID() { + return "Move a node by aria-owns into a node moved by aria-owns to left within the tree"; + }; + } + + /** + * Move a node by aria-owns from right to left in the tree, and then + * move its parent too by aria-owns. No hide event should be fired for + * original node. + */ + function test11() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t11_c1_child")), + new invokerChecker(EVENT_HIDE, getNode("t11_c2")), + new orderChecker(), + new asyncInvokerChecker(EVENT_SHOW, "t11_c2_child"), + new asyncInvokerChecker(EVENT_SHOW, "t11_c2"), + new orderChecker(), + new invokerChecker(EVENT_REORDER, "t11"), + new unexpectedInvokerChecker(EVENT_HIDE, "t11_c2_child"), + new unexpectedInvokerChecker(EVENT_REORDER, "t11_c1"), + new unexpectedInvokerChecker(EVENT_REORDER, "t11_c2"), + new unexpectedInvokerChecker(EVENT_REORDER, "t11_c3"), + ]; + + this.invoke = function test11_invoke() { + // Remove a node from 't11_c1' container to give the event tree a + // desired structure (the 't11_c1' container node goes first in + // the event tree), + getNode("t11_c1_child").remove(); + // then move 't11_c2_moved' from 't11_c2' to 't11_c1', and then move + // 't11_c2' to 't11_c3'. + getNode("t11_c1").setAttribute("aria-owns", "t11_c2_child"); + getNode("t11_c3").setAttribute("aria-owns", "t11_c2"); + }; + + this.getID = function test11_getID() { + return "Move a node by aria-owns to left within the tree"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests. + + gA11yEventDumpToConsole = true; // debug stuff + // enableLogging("eventTree"); + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new removeChildNParent("option1", "select1")); + gQueue.push(new removeParentNChild("option2", "select2")); + gQueue.push(new hideChildNParent("option3", "select3")); + gQueue.push(new hideParentNChild("option4", "select4")); + gQueue.push(new hideChildNRemoveParent("option5", "select5")); + gQueue.push(new hideParentNRemoveChild("option6", "select6")); + gQueue.push(new removeChildNHideParent("option7", "select7")); + gQueue.push(new removeParentNHideChild("option8", "select8")); + + gQueue.push(new addParentNChild("testContainer", false)); + gQueue.push(new addParentNChild("testContainer", true)); + gQueue.push(new showParentNChild("select9", "option9", false)); + gQueue.push(new showParentNChild("select10", "option10", true)); + gQueue.push(new showParentNAddChild("select11", false)); + gQueue.push(new showParentNAddChild("select12", true)); + + gQueue.push(new removeGrandChildrenNHideParent("t1_child1", "t1_child2", "t1_parent")); + gQueue.push(new test3()); + gQueue.push(new test4()); + gQueue.push(new test5()); + gQueue.push(new test6()); + gQueue.push(new test7()); + gQueue.push(new test8()); + gQueue.push(new test9()); + gQueue.push(new test10()); + gQueue.push(new test11()); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=513213" + title="coalesce events when new event is appended to the queue"> + Mozilla Bug 513213 + </a><br> + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="testContainer"> + <select id="select1"> + <option id="option1">option</option> + </select> + <select id="select2"> + <option id="option2">option</option> + </select> + <select id="select3"> + <option id="option3">option</option> + </select> + <select id="select4"> + <option id="option4">option</option> + </select> + <select id="select5"> + <option id="option5">option</option> + </select> + <select id="select6"> + <option id="option6">option</option> + </select> + <select id="select7"> + <option id="option7">option</option> + </select> + <select id="select8"> + <option id="option8">option</option> + </select> + + <select id="select9" style="display: none"> + <option id="option9" style="display: none">testing</option> + </select> + <select id="select10" style="display: none"> + <option id="option10" style="display: none">testing</option> + </select> + <select id="select11" style="display: none"></select> + <select id="select12" style="display: none"></select> + </div> + + <div id="testContainer2"> + <div id="t1_parent"> + <div id="t1_mid1"><div id="t1_child1"></div></div> + <div id="t1_mid2"><div id="t1_child2"></div></div> + </div> + </div> + + <div id="t3"> + <div role="listbox" id="t3_lb"> + <div role="option" id="t3_o">opt</div> + </div> + </div> + + <div id="t4"> + <div role="listbox" id="t4_lb"> + <div role="option" id="t4_o1">opt1</div> + <div role="option" id="t4_o2">opt2</div> + </div> + </div> + + <div id="t5"> + <div role="button" id="t5_b">btn</div> + <div role="listbox" id="t5_lb"> + <div role="option" id="t5_o">opt</div> + </div> + </div> + + <div id="t6"> + </div> + + <div id="t7"> + <div id="t7_moveplace"></div> + <div id="t7_c"> + <div><div id="t7_c_grandchild"></div></div> + <div id="t7_c_directchild"></div> + </div> + </div> + + <div id="t8"> + <div id="t8_c1"><div id="t8_c1_child"></div></div> + <div id="t8_c2"> + <div id="t8_c2_moved"></div> + </div> + </div> + + <div id="t9"> + <div id="t9_c1"><div id="t9_c1_child"></div></div> + <div id="t9_c2"> + <div id="t9_c2_child"></div> + <div id="t9_c2_moved"></div> + </div> + <div id="t9_c3"> + <div id="t9_c3_moved"></div> + </div> + </div> + + <div id="t10"> + <div id="t10_c1"><div id="t10_c1_child"></div></div> + <div id="t10_c2"> + <div id="t10_c2_child"></div> + <div id="t10_c2_moved"></div> + </div> + <div id="t10_c3"> + <div id="t10_c3_moved"></div> + </div> + </div> + + <div id="t11"> + <div id="t11_c1"><div id="t11_c1_child"></div></div> + <div id="t11_c2"><div id="t11_c2_child"></div></div> + <div id="t11_c3"></div> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_contextmenu.html b/accessible/tests/mochitest/events/test_contextmenu.html new file mode 100644 index 0000000000..e729f071e7 --- /dev/null +++ b/accessible/tests/mochitest/events/test_contextmenu.html @@ -0,0 +1,131 @@ +<html> + +<head> + <title>Context menu tests</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function showContextMenu(aID) { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_MENUPOPUP_START, getContextMenuNode()), + ]; + + this.invoke = function showContextMenu_invoke() { + synthesizeMouse(this.DOMNode, 4, 4, { type: "contextmenu", button: 2 }); + }; + + this.getID = function showContextMenu_getID() { + return "show context menu"; + }; + } + + function selectMenuItem() { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getFocusedMenuItem), + ]; + + this.invoke = function selectMenuItem_invoke() { + synthesizeKey("KEY_ArrowDown"); + }; + + this.getID = function selectMenuItem_getID() { + return "select first menuitem"; + }; + } + + function closeContextMenu(aID) { + this.eventSeq = [ + new invokerChecker(EVENT_MENUPOPUP_END, + getAccessible(getContextMenuNode())), + ]; + + this.invoke = function closeContextMenu_invoke() { + synthesizeKey("KEY_Escape"); + }; + + this.getID = function closeContextMenu_getID() { + return "close context menu"; + }; + } + + function getContextMenuNode() { + return getRootAccessible().DOMDocument. + getElementById("contentAreaContextMenu"); + } + + function getFocusedMenuItem() { + var menu = getAccessible(getAccessible(getContextMenuNode())); + for (var idx = 0; idx < menu.childCount; idx++) { + var item = menu.getChildAt(idx); + + if (hasState(item, STATE_FOCUSED)) + return getAccessible(item); + } + return null; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new showContextMenu("input")); + gQueue.push(new selectMenuItem()); + gQueue.push(new closeContextMenu()); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); + if (AppConstants.platform == "macosx" && + Services.prefs.getBoolPref("widget.macos.native-context-menus", false)) { + ok(true, "Native context menus handle accessibility notifications natively and cannot be tested with synthesized key events."); + } else { + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + } + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=580535" + title="Broken accessibility in context menus"> + Mozilla Bug 580535 + </a><br> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="input"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_descrchange.html b/accessible/tests/mochitest/events/test_descrchange.html new file mode 100644 index 0000000000..1eaecd6b59 --- /dev/null +++ b/accessible/tests/mochitest/events/test_descrchange.html @@ -0,0 +1,142 @@ +<html> + +<head> + <title>Accessible description change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + let PromEvents = {}; + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js", + PromEvents); + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function setAttr(aID, aAttr, aValue, aChecker) { + this.eventSeq = [ aChecker ]; + this.invoke = function setAttr_invoke() { + getNode(aID).setAttribute(aAttr, aValue); + }; + + this.getID = function setAttr_getID() { + return "set attr '" + aAttr + "', value '" + aValue + "'"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + // gA11yEventDumpToConsole = true; // debuggin + + var gQueue = null; + async function doTests() { + gQueue = new eventQueue(); + // Later tests use await. + let queueFinished = new Promise(resolve => { + gQueue.onFinish = function() { + resolve(); + return DO_NOT_FINISH_TEST; + }; + }); + + gQueue.push(new setAttr("tst1", "aria-describedby", "display", + new invokerChecker(EVENT_DESCRIPTION_CHANGE, "tst1"))); + gQueue.push(new setAttr("tst1", "title", "title", + new unexpectedInvokerChecker(EVENT_DESCRIPTION_CHANGE, "tst1"))); + + // A title has lower priority over text content. There should be no name change event. + gQueue.push(new setAttr("tst2", "title", "title", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2"))); + + gQueue.invoke(); + await queueFinished; + // Tests beyond this point use await rather than eventQueue. + + const describedBy = getNode("describedBy"); + const description = getNode("description"); + let descChanged = PromEvents.waitForEvent( + EVENT_DESCRIPTION_CHANGE, + describedBy + ); + info("Changing text of aria-describedby target"); + description.textContent = "d2"; + await descChanged; + descChanged = PromEvents.waitForEvent( + EVENT_DESCRIPTION_CHANGE, + describedBy + ); + info("Adding node to aria-describedby target"); + description.innerHTML = '<p id="descriptionChild">d3</p>'; + await descChanged; + descChanged = PromEvents.waitForEvent( + EVENT_DESCRIPTION_CHANGE, + describedBy + ); + info("Changing text of aria-describedby target's child"); + getNode("descriptionChild").textContent = "d4"; + await descChanged; + + const lateDescribedBy = getNode("lateDescribedBy"); + descChanged = PromEvents.waitForEvent( + EVENT_DESCRIPTION_CHANGE, + lateDescribedBy + ); + info("Setting aria-describedby"); + lateDescribedBy.setAttribute("aria-describedby", "lateDescription"); + await descChanged; + descChanged = PromEvents.waitForEvent( + EVENT_DESCRIPTION_CHANGE, + lateDescribedBy + ); + info("Changing text of late aria-describedby target's child"); + getNode("lateDescriptionChild").textContent = "d2"; + await descChanged; + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=991969" + title="Event not fired when description changes"> + Bug 991969 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <button id="tst1">btn1</button> + <button id="tst2">btn2</button> + + <div id="describedBy" aria-describedby="description"></div> + <div id="description">d1</div> + + <div id="lateDescribedBy"></div> + <div id="lateDescription"><p id="lateDescriptionChild">d1</p></div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_dragndrop.html b/accessible/tests/mochitest/events/test_dragndrop.html new file mode 100644 index 0000000000..2613a310a2 --- /dev/null +++ b/accessible/tests/mochitest/events/test_dragndrop.html @@ -0,0 +1,106 @@ +<html> + +<head> + <title>Accessible drag and drop event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + + /** + * Do tests. + */ + var gQueue = null; + + // aria grabbed invoker + function changeGrabbed(aNodeOrID, aGrabValue) { + this.DOMNode = getNode(aNodeOrID); + + this.invoke = function changeGrabbed_invoke() { + if (aGrabValue != undefined) { + this.DOMNode.setAttribute("aria-grabbed", aGrabValue); + } + }; + + this.check = function changeGrabbed_check() { + testAttrs(aNodeOrID, {"grabbed": aGrabValue}, true); + }; + + this.getID = function changeGrabbed_getID() { + return prettyName(aNodeOrID) + " aria-grabbed changed"; + }; + } + + // aria dropeffect invoker + function changeDropeffect(aNodeOrID, aDropeffectValue) { + this.DOMNode = getNode(aNodeOrID); + + this.invoke = function changeDropeffect_invoke() { + if (aDropeffectValue != undefined) { + this.DOMNode.setAttribute("aria-dropeffect", aDropeffectValue); + } + }; + + this.check = function changeDropeffect_check() { + testAttrs(aNodeOrID, {"dropeffect": aDropeffectValue}, true); + }; + + this.getID = function changeDropeffect_getID() { + return prettyName(aNodeOrID) + " aria-dropeffect changed"; + }; + } + + function doTests() { + // Test aria attribute mutation events + gQueue = new eventQueue(nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED); + + let id = "grabbable"; + gQueue.push(new changeGrabbed(id, "true")); + gQueue.push(new changeGrabbed(id, "false")); + todo(false, "uncomment this test when 472142 is fixed."); + // gQueue.push(new changeGrabbed(id, "undefined")); + + id = "dropregion"; + gQueue.push(new changeDropeffect(id, "copy")); + gQueue.push(new changeDropeffect(id, "execute")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=510441" + title="Add support for nsIAccessibleEvent::OBJECT_ATTRIBUTE_CHANGED"> + Mozilla Bug 510441 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <!-- ARIA grabbed --> + <div id="grabbable" role="button" aria-grabbed="foo">button</div> + + <!-- ARIA dropeffect --> + <div id="dropregion" role="region" aria-dropeffect="none">button</div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_flush.html b/accessible/tests/mochitest/events/test_flush.html new file mode 100644 index 0000000000..7d7b60b81e --- /dev/null +++ b/accessible/tests/mochitest/events/test_flush.html @@ -0,0 +1,74 @@ +<html> + +<head> + <title>Flush delayed events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + SimpleTest.expectAssertions(0, 1); + + var gFocusHandler = { + handleEvent(aEvent) { + switch (this.count) { + case 0: + is(aEvent.DOMNode, getNode("input1"), + "Focus event for input1 was expected!"); + getAccessible("input2").takeFocus(); + break; + + case 1: + is(aEvent.DOMNode, getNode("input2"), + "Focus event for input2 was expected!"); + + unregisterA11yEventListener(EVENT_FOCUS, gFocusHandler); + SimpleTest.finish(); + break; + + default: + ok(false, "Wrong focus event!"); + } + + this.count++; + }, + + count: 0, + }; + + function doTests() { + registerA11yEventListener(EVENT_FOCUS, gFocusHandler); + + getAccessible("input1").takeFocus(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=477551" + title="DocAccessible::FlushPendingEvents isn't robust"> + Mozilla Bug 477551 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="input1"> + <input id="input2"> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html new file mode 100644 index 0000000000..661284619a --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html @@ -0,0 +1,327 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=429547 +--> +<head> + <title>aria-activedescendant focus tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + let PromEvents = {}; + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js", + PromEvents); + // gA11yEventDumpToConsole = true; // debugging + + function changeARIAActiveDescendant(aContainer, aItem, aPrevItemId) { + let itemID = Node.isInstance(aItem) ? aItem.id : aItem; + this.eventSeq = [new focusChecker(aItem)]; + + if (aPrevItemId) { + this.eventSeq.push( + new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId) + ); + } + + this.eventSeq.push( + new stateChangeChecker(EXT_STATE_ACTIVE, true, true, aItem) + ); + + this.invoke = function changeARIAActiveDescendant_invoke() { + getNode(aContainer).setAttribute("aria-activedescendant", itemID); + }; + + this.getID = function changeARIAActiveDescendant_getID() { + return "change aria-activedescendant on " + itemID; + }; + } + + function clearARIAActiveDescendant(aID, aPrevItemId) { + this.eventSeq = [ + new focusChecker(aID), + ]; + + if (aPrevItemId) { + this.eventSeq.push( + new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId) + ); + } + + this.invoke = function clearARIAActiveDescendant_invoke() { + getNode(aID).removeAttribute("aria-activedescendant"); + }; + + this.getID = function clearARIAActiveDescendant_getID() { + return "clear aria-activedescendant on container " + aID; + }; + } + + /** + * Change aria-activedescendant to an invalid (non-existent) id. + * Ensure that focus is fired on the element itself. + */ + function changeARIAActiveDescendantInvalid(aID, aInvalidID, aPrevItemId) { + if (!aInvalidID) { + aInvalidID = "invalid"; + } + + this.eventSeq = [ + new focusChecker(aID), + ]; + + if (aPrevItemId) { + this.eventSeq.push( + new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId) + ); + } + + this.invoke = function changeARIAActiveDescendant_invoke() { + getNode(aID).setAttribute("aria-activedescendant", aInvalidID); + }; + + this.getID = function changeARIAActiveDescendant_getID() { + return "change aria-activedescendant to invalid id"; + }; + } + + function insertItemNFocus(aID, aNewItemID, aPrevItemId) { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, aNewItemID), + new focusChecker(aNewItemID), + ]; + + if (aPrevItemId) { + this.eventSeq.push( + new stateChangeChecker(EXT_STATE_ACTIVE, true, false, aPrevItemId) + ); + } + + this.eventSeq.push( + new stateChangeChecker(EXT_STATE_ACTIVE, true, true, aNewItemID) + ); + + this.invoke = function insertItemNFocus_invoke() { + var container = getNode(aID); + + var itemNode = document.createElement("div"); + itemNode.setAttribute("id", aNewItemID); + itemNode.setAttribute("role", "listitem"); + itemNode.textContent = aNewItemID; + container.appendChild(itemNode); + + container.setAttribute("aria-activedescendant", aNewItemID); + }; + + this.getID = function insertItemNFocus_getID() { + return "insert new node and focus it with ID: " + aNewItemID; + }; + } + + /** + * Change the id of an element to another id which is the target of + * aria-activedescendant. + * If another element already has the desired id, remove it from that + * element first. + * Ensure that focus is fired on the target element which was given the + * desired id. + * @param aFromID The existing id of the target element. + * @param aToID The desired id to be given to the target element. + */ + function moveARIAActiveDescendantID(aFromID, aToID) { + this.eventSeq = [ + new focusChecker(aToID), + new stateChangeChecker(EXT_STATE_ACTIVE, true, true, aToID), + ]; + + this.invoke = function moveARIAActiveDescendantID_invoke() { + let orig = document.getElementById(aToID); + if (orig) { + orig.id = ""; + } + document.getElementById(aFromID).id = aToID; + }; + + this.getID = function moveARIAActiveDescendantID_getID() { + return "move aria-activedescendant id " + aToID; + }; + } + + var gQueue = null; + async function doTest() { + gQueue = new eventQueue(); + // Later tests use await. + let queueFinished = new Promise(resolve => { + gQueue.onFinish = function() { + resolve(); + return DO_NOT_FINISH_TEST; + }; + }); + + gQueue.push(new synthFocus("listbox", new focusChecker("item1"))); + gQueue.push(new changeARIAActiveDescendant("listbox", "item2", "item1")); + gQueue.push(new changeARIAActiveDescendant("listbox", "item3", "item2")); + + gQueue.push(new synthFocus("combobox_entry", new focusChecker("combobox_entry"))); + gQueue.push(new changeARIAActiveDescendant("combobox", "combobox_option2")); + + gQueue.push(new synthFocus("listbox", new focusChecker("item3"))); + gQueue.push(new insertItemNFocus("listbox", "item4", "item3")); + + gQueue.push(new clearARIAActiveDescendant("listbox", "item4")); + gQueue.push(new changeARIAActiveDescendant("listbox", "item1")); + gQueue.push(new changeARIAActiveDescendantInvalid("listbox", "invalid", "item1")); + + gQueue.push(new changeARIAActiveDescendant("listbox", "roaming")); + gQueue.push(new moveARIAActiveDescendantID("roaming2", "roaming")); + gQueue.push(new changeARIAActiveDescendantInvalid("listbox", "roaming3", "roaming")); + gQueue.push(new moveARIAActiveDescendantID("roaming", "roaming3")); + + gQueue.push(new synthFocus("activedesc_nondesc_input", + new focusChecker("activedesc_nondesc_option"))); + + let shadowRoot = document.getElementById("shadow").shadowRoot; + let shadowListbox = shadowRoot.getElementById("shadowListbox"); + let shadowItem1 = shadowRoot.getElementById("shadowItem1"); + let shadowItem2 = shadowRoot.getElementById("shadowItem2"); + gQueue.push(new synthFocus(shadowListbox, new focusChecker(shadowItem1))); + gQueue.push(new changeARIAActiveDescendant(shadowListbox, shadowItem2)); + + gQueue.invoke(); + await queueFinished; + // Tests beyond this point use await rather than eventQueue. + + info("Testing simultaneous insertion, relocation and aria-activedescendant"); + let comboboxWithHiddenList = getNode("comboboxWithHiddenList"); + let evtProm = PromEvents.waitForEvent(EVENT_FOCUS, comboboxWithHiddenList); + comboboxWithHiddenList.focus(); + await evtProm; + testStates(comboboxWithHiddenList, STATE_FOCUSED); + // hiddenList is owned, so unhiding causes insertion and relocation. + getNode("hiddenList").hidden = false; + evtProm = Promise.all([ + PromEvents.waitForEvent(EVENT_FOCUS, "hiddenListOption"), + PromEvents.waitForStateChange("hiddenListOption", EXT_STATE_ACTIVE, true, true), + ]); + comboboxWithHiddenList.setAttribute("aria-activedescendant", "hiddenListOption"); + await evtProm; + testStates("hiddenListOption", STATE_FOCUSED); + + info("Testing active state changes when not focused"); + testStates("listbox", 0, 0, STATE_FOCUSED); + evtProm = Promise.all([ + PromEvents.waitForStateChange("roaming3", EXT_STATE_ACTIVE, false, true), + PromEvents.waitForStateChange("item1", EXT_STATE_ACTIVE, true, true), + ]); + getNode("listbox").setAttribute("aria-activedescendant", "item1"); + await evtProm; + + info("Testing that focus is always fired first"); + const listbox = getNode("listbox"); + evtProm = PromEvents.waitForEvent(EVENT_FOCUS, "item1"); + listbox.focus(); + await evtProm; + const item1 = getNode("item1"); + evtProm = PromEvents.waitForOrderedEvents([ + [EVENT_FOCUS, "item2"], + [EVENT_NAME_CHANGE, item1], + ], "Focus then name change"); + item1.setAttribute("aria-label", "changed"); + listbox.setAttribute("aria-activedescendant", "item2"); + await evtProm; + + info("Setting aria-activedescendant to invalid id on non-focused node"); + const combobox_entry = getNode("combobox_entry"); + evtProm = PromEvents.waitForEvents({ + expected: [[EVENT_FOCUS, combobox_entry]], + unexpected: [[EVENT_FOCUS, listbox]], + }); + combobox_entry.focus(); + listbox.setAttribute("aria-activedescendant", "invalid"); + await evtProm; + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=429547" + title="Support aria-activedescendant usage in nsIAccesible::TakeFocus()"> + Mozilla Bug 429547 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=761102" + title="Focus may be missed when ARIA active-descendant is changed on active composite widget"> + Mozilla Bug 761102 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div role="listbox" aria-activedescendant="item1" id="listbox" tabindex="1" + aria-owns="item3"> + <div role="listitem" id="item1">item1</div> + <div role="listitem" id="item2">item2</div> + <div role="listitem" id="roaming">roaming</div> + <div role="listitem" id="roaming2">roaming2</div> + </div> + <div role="listitem" id="item3">item3</div> + + <div role="combobox" id="combobox"> + <input id="combobox_entry"> + <ul> + <li role="option" id="combobox_option1">option1</li> + <li role="option" id="combobox_option2">option2</li> + </ul> + </div> + + <!-- aria-activedescendant targeting a non-descendant --> + <input id="activedesc_nondesc_input" aria-activedescendant="activedesc_nondesc_option"> + <div role="listbox"> + <div role="option" id="activedesc_nondesc_option">option</div> + </div> + + <div id="shadow"></div> + <script> + let host = document.getElementById("shadow"); + let shadow = host.attachShadow({mode: "open"}); + let listbox = document.createElement("div"); + listbox.id = "shadowListbox"; + listbox.setAttribute("role", "listbox"); + listbox.setAttribute("tabindex", "0"); + shadow.appendChild(listbox); + let item = document.createElement("div"); + item.id = "shadowItem1"; + item.setAttribute("role", "option"); + listbox.appendChild(item); + listbox.setAttribute("aria-activedescendant", "shadowItem1"); + item = document.createElement("div"); + item.id = "shadowItem2"; + item.setAttribute("role", "option"); + listbox.appendChild(item); + </script> + + <div id="comboboxWithHiddenList" tabindex="0" role="combobox" aria-owns="hiddenList"> + </div> + <div id="hiddenList" hidden role="listbox"> + <div id="hiddenListOption" role="option"></div> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_autocomplete.html b/accessible/tests/mochitest/events/test_focus_autocomplete.html new file mode 100644 index 0000000000..c179398cc0 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_autocomplete.html @@ -0,0 +1,83 @@ +<!doctype html> + +<head> + <title>Form Autocomplete Tests</title> + + <link rel="stylesheet" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script src="../common.js"></script> + <script src="../promisified-events.js"></script> + <script src="../role.js"></script> + + <script type="application/javascript"> + const { TestUtils } = ChromeUtils.import( + "resource://testing-common/TestUtils.jsm"); + + async function waitForFocusOnOptionWithname(name) { + let event = await waitForEvent( + EVENT_FOCUS, + evt => evt.accessible.role == ROLE_COMBOBOX_OPTION + ); + while (!event.accessible.name) { + // Sometimes, the name is null for a very short time after the focus + // event. + await waitForEvent(EVENT_NAME_CHANGE, event.accessible); + } + is(event.accessible.name, name, "Got focus on option with name " + name); + } + + async function doTests() { + const input = getNode("input"); + info("Focusing the input"); + let focused = waitForEvent(EVENT_FOCUS, input); + input.focus(); + await focused; + + let shown = waitForEvent(EVENT_SHOW, event => + event.accessible.role == ROLE_GROUPING && + event.accessible.firstChild.role == ROLE_COMBOBOX_LIST); + info("Pressing ArrowDown to open the popup"); + synthesizeKey("KEY_ArrowDown"); + await shown; + // The popup still doesn't seem to be ready even once it's fired an a11y + // show event! + const controller = Cc["@mozilla.org/autocomplete/controller;1"]. + getService(Ci.nsIAutoCompleteController); + info("Waiting for popup to be fully open and ready"); + await TestUtils.waitForCondition(() => controller.input.popupOpen); + + focused = waitForFocusOnOptionWithname("a"); + info("Pressing ArrowDown to focus first item"); + synthesizeKey("KEY_ArrowDown"); + await focused; + + focused = waitForFocusOnOptionWithname("b"); + info("Pressing ArrowDown to focus the second item"); + synthesizeKey("KEY_ArrowDown"); + await focused; + + focused = waitForEvent(EVENT_FOCUS, input); + info("Pressing enter to select the second item"); + synthesizeKey("KEY_Enter"); + await focused; + is(input.value, "b", "input value filled with second item"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> +<body> + <input id="input" list="list"> + <datalist id="list"> + <option id="a" value="a"> + <option id="b" value="b"> + </datalist> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_autocomplete.xhtml b/accessible/tests/mochitest/events/test_focus_autocomplete.xhtml new file mode 100644 index 0000000000..69cdac14c5 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_autocomplete.xhtml @@ -0,0 +1,507 @@ +<?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"?> + +<!-- Firefox searchbar --> +<?xml-stylesheet href="chrome://browser/content/browser.css" + type="text/css"?> +<!-- SeaMonkey searchbar --> +<?xml-stylesheet href="chrome://navigator/content/navigator.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Accessible focus event testing"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript" + src="../autocomplete.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Hacky stuffs + + // This is the hacks needed to use a searchbar without browser.js. + var BrowserSearch = { + updateOpenSearchBadge() {} + }; + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function loadFormAutoComplete(aIFrameID) + { + this.iframeNode = getNode(aIFrameID); + this.iframe = getAccessible(aIFrameID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.iframe) + ]; + + this.invoke = function loadFormAutoComplete_invoke() + { + var url = "data:text/html,<html><body><form id='form'>" + + "<input id='input' name='a11ytest-formautocomplete'>" + + "</form></body></html>"; + this.iframeNode.setAttribute("src", url); + } + + this.getID = function loadFormAutoComplete_getID() + { + return "load form autocomplete page"; + } + } + + function initFormAutoCompleteBy(aIFrameID, aAutoCompleteValue) + { + this.iframe = getAccessible(aIFrameID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.iframe) + ]; + + this.invoke = function initFormAutoCompleteBy_invoke() + { + var iframeDOMDoc = getIFrameDOMDoc(aIFrameID); + + var inputNode = iframeDOMDoc.getElementById("input"); + inputNode.value = aAutoCompleteValue; + var formNode = iframeDOMDoc.getElementById("form"); + formNode.submit(); + } + + this.getID = function initFormAutoCompleteBy_getID() + { + return "init form autocomplete by '" + aAutoCompleteValue + "'"; + } + } + + function loadHTML5ListAutoComplete(aIFrameID) + { + this.iframeNode = getNode(aIFrameID); + this.iframe = getAccessible(aIFrameID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.iframe) + ]; + + this.invoke = function loadHTML5ListAutoComplete_invoke() + { + var url = "data:text/html,<html><body>" + + "<datalist id='cities'><option>hello</option><option>hi</option></datalist>" + + "<input id='input' list='cities'>" + + "</body></html>"; + this.iframeNode.setAttribute("src", url); + } + + this.getID = function loadHTML5ListAutoComplete_getID() + { + return "load HTML5 list autocomplete page"; + } + } + + function removeChar(aID, aCheckerOrEventSeq) + { + this.__proto__ = new synthAction(aID, aCheckerOrEventSeq); + + this.invoke = function removeChar_invoke() + { + synthesizeKey("KEY_ArrowLeft", {shiftKey: true}); + synthesizeKey("KEY_Delete"); + } + + this.getID = function removeChar_getID() + { + return "remove char on " + prettyName(aID); + } + } + + function replaceOnChar(aID, aChar, aCheckerOrEventSeq) + { + this.__proto__ = new synthAction(aID, aCheckerOrEventSeq); + + this.invoke = function replaceOnChar_invoke() + { + this.DOMNode.select(); + sendString(aChar); + } + + this.getID = function replaceOnChar_getID() + { + return "replace on char '" + aChar + "' for" + prettyName(aID); + } + } + + function focusOnMouseOver(aIDFunc, aIDFuncArg) + { + this.eventSeq = [ new focusChecker(aIDFunc, aIDFuncArg) ]; + + this.invoke = function focusOnMouseOver_invoke() + { + this.id = aIDFunc(aIDFuncArg); + this.node = getNode(this.id); + this.window = this.node.ownerGlobal; + + if (this.node.localName == "tree") { + var tree = getAccessible(this.node); + var accessible = getAccessible(this.id); + if (tree != accessible) { + var itemX = {}, itemY = {}, treeX = {}, treeY = {}; + accessible.getBounds(itemX, itemY, {}, {}); + tree.getBounds(treeX, treeY, {}, {}); + this.x = itemX.value - treeX.value; + this.y = itemY.value - treeY.value; + } + } + + // Generate mouse move events in timeouts until autocomplete popup list + // doesn't have it, the reason is do that because autocomplete popup + // ignores mousemove events firing in too short range. + synthesizeMouse(this.node, this.x, this.y, { type: "mousemove" }); + this.doMouseMoveFlood(this); + } + + this.finalCheck = function focusOnMouseOver_getID() + { + this.isFlooding = false; + } + + this.getID = function focusOnMouseOver_getID() + { + return "mouse over on " + prettyName(aIDFunc(aIDFuncArg)); + } + + this.doMouseMoveFlood = function focusOnMouseOver_doMouseMoveFlood(aThis) + { + synthesizeMouse(aThis.node, aThis.x + 1, aThis.y + 1, + { type: "mousemove" }, aThis.window); + + if (aThis.isFlooding) + aThis.window.setTimeout(aThis.doMouseMoveFlood, 0, aThis); + } + + this.id = null; + this.node = null; + this.window = null; + + this.isFlooding = true; + this.x = 1; + this.y = 1; + } + + function selectByClick(aIDFunc, aIDFuncArg, + aFocusTargetFunc, aFocusTargetFuncArg) + { + this.eventSeq = [ new focusChecker(aFocusTargetFunc, aFocusTargetFuncArg) ]; + + this.invoke = function selectByClick_invoke() + { + var id = aIDFunc(aIDFuncArg); + var node = getNode(id); + var targetWindow = node.ownerGlobal; + + if (node.localName == "tree") { + var tree = getAccessible(node); + var accessible = getAccessible(id); + if (tree != accessible) { + var itemX = {}, itemY = {}, treeX = {}, treeY = {}; + accessible.getBounds(itemX, itemY, {}, {}); + tree.getBounds(treeX, treeY, {}, {}); + this.x = itemX.value - treeX.value; + this.y = itemY.value - treeY.value; + } + } + + synthesizeMouseAtCenter(node, {}, targetWindow); + } + + this.getID = function selectByClick_getID() + { + return "select by click " + prettyName(aIDFunc(aIDFuncArg)); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Target getters + + function getItem(aItemObj) + { + var autocompleteNode = aItemObj.autocompleteNode; + + // XUL searchbar + if (autocompleteNode.localName == "searchbar") { + let popupNode = autocompleteNode._popup; + if (popupNode) { + let list = getAccessible(popupNode); + return list.getChildAt(aItemObj.index); + } + } + + // XUL autocomplete + let popupNode = autocompleteNode.popup; + if (!popupNode) { + // HTML form autocomplete + var controller = Cc["@mozilla.org/autocomplete/controller;1"]. + getService(Ci.nsIAutoCompleteController); + popupNode = controller.input.popup; + } + + if (popupNode) { + if ("richlistbox" in popupNode) { + let list = getAccessible(popupNode.richlistbox); + return list.getChildAt(aItemObj.index); + } + + let list = getAccessible(popupNode.tree); + return list.getChildAt(aItemObj.index + 1); + } + return null; + } + + function getTextEntry(aID) + { + // For form autocompletes the autocomplete widget and text entry widget + // is the same widget, for XUL autocompletes the text entry is a first + // child. + var localName = getNode(aID).localName; + + // HTML form autocomplete + if (localName == "input") + return getAccessible(aID); + + // XUL searchbar + if (localName == "searchbar") + return getAccessible(getNode(aID).textbox); + + return null; + } + + function itemObj(aID, aIdx) + { + this.autocompleteNode = getNode(aID); + + this.autocomplete = this.autocompleteNode.localName == "searchbar" ? + getAccessible(this.autocompleteNode.textbox) : + getAccessible(this.autocompleteNode); + + this.index = aIdx; + } + + function getIFrameDOMDoc(aIFrameID) + { + return getNode(aIFrameID).contentDocument; + } + + //////////////////////////////////////////////////////////////////////////// + // Test helpers + + function queueAutoCompleteTests(aID) + { + // focus autocomplete text entry + gQueue.push(new synthFocus(aID, new focusChecker(getTextEntry, aID))); + + // open autocomplete popup + gQueue.push(new synthDownKey(aID, new nofocusChecker())); + + // select second option ('hi' option), focus on it + gQueue.push(new synthUpKey(aID, + new focusChecker(getItem, new itemObj(aID, 1)))); + + // choose selected option, focus on text entry + gQueue.push(new synthEnterKey(aID, new focusChecker(getTextEntry, aID))); + + // remove char, autocomplete popup appears + gQueue.push(new removeChar(aID, new nofocusChecker())); + + // select first option ('hello' option), focus on it + gQueue.push(new synthDownKey(aID, + new focusChecker(getItem, new itemObj(aID, 0)))); + + // mouse move on second option ('hi' option), focus on it + gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 1))); + + // autocomplete popup updated (no selected item), focus on textentry + gQueue.push(new synthKey(aID, "e", null, new focusChecker(getTextEntry, aID))); + + // select first option ('hello' option), focus on it + gQueue.push(new synthDownKey(aID, + new focusChecker(getItem, new itemObj(aID, 0)))); + + // popup gets hidden, focus on textentry + gQueue.push(new synthRightKey(aID, new focusChecker(getTextEntry, aID))); + + // popup gets open, no focus + gQueue.push(new synthOpenComboboxKey(aID, new nofocusChecker())); + + // select first option again ('hello' option), focus on it + gQueue.push(new synthDownKey(aID, + new focusChecker(getItem, new itemObj(aID, 0)))); + + // no option is selected, focus on text entry + gQueue.push(new synthUpKey(aID, new focusChecker(getTextEntry, aID))); + + // close popup, no focus + gQueue.push(new synthEscapeKey(aID, new nofocusChecker())); + + // autocomplete popup appears (no selected item), focus stays on textentry + gQueue.push(new replaceOnChar(aID, "h", new nofocusChecker())); + + // mouse move on first option ('hello' option), focus on it + gQueue.push(new focusOnMouseOver(getItem, new itemObj(aID, 0))); + + // click first option ('hello' option), popup closes, focus on text entry + gQueue.push(new selectByClick(getItem, new itemObj(aID, 0), getTextEntry, aID)); + } + + //////////////////////////////////////////////////////////////////////////// + // Tests + + //gA11yEventDumpToConsole = true; // debug stuff + + var gInitQueue = null; + function initTests() + { + if (SEAMONKEY || MAC) { + todo(false, "Skipping this test on SeaMonkey ftb. (Bug 718237), and on Mac (bug 746177)"); + shutdownAutoComplete(); + SimpleTest.finish(); + return; + } + + gInitQueue = new eventQueue(); + gInitQueue.push(new loadFormAutoComplete("iframe")); + gInitQueue.push(new initFormAutoCompleteBy("iframe", "hello")); + gInitQueue.push(new initFormAutoCompleteBy("iframe", "hi")); + gInitQueue.push(new loadHTML5ListAutoComplete("iframe2")); + gInitQueue.onFinish = function initQueue_onFinish() + { + SimpleTest.executeSoon(doTests); + return DO_NOT_FINISH_TEST; + } + gInitQueue.invoke(); + } + + var gQueue = null; + function doTests() + { + // Test focus events. + gQueue = new eventQueue(); + + //////////////////////////////////////////////////////////////////////////// + // tree popup autocomplete tests + queueAutoCompleteTests("autocomplete"); + + //////////////////////////////////////////////////////////////////////////// + // richlistbox popup autocomplete tests + queueAutoCompleteTests("richautocomplete"); + + //////////////////////////////////////////////////////////////////////////// + // HTML form autocomplete tests + queueAutoCompleteTests(getIFrameDOMDoc("iframe").getElementById("input")); + + //////////////////////////////////////////////////////////////////////////// + // HTML5 list autocomplete tests + queueAutoCompleteTests(getIFrameDOMDoc("iframe2").getElementById("input")); + + //////////////////////////////////////////////////////////////////////////// + // searchbar tests + + // focus searchbar, focus on text entry + gQueue.push(new synthFocus("searchbar", + new focusChecker(getTextEntry, "searchbar"))); + // open search engine popup, no focus + gQueue.push(new synthOpenComboboxKey("searchbar", new nofocusChecker())); + // select first item, focus on it + gQueue.push(new synthDownKey("searchbar", + new focusChecker(getItem, new itemObj("searchbar", 0)))); + // mouse over on second item, focus on it + gQueue.push(new focusOnMouseOver(getItem, new itemObj("searchbar", 1))); + // press enter key, focus on text entry + gQueue.push(new synthEnterKey("searchbar", + new focusChecker(getTextEntry, "searchbar"))); + // click on search button, open popup, focus goes to document + var searchBtn = getAccessible(getNode("searchbar").searchButton); + gQueue.push(new synthClick(searchBtn, new focusChecker(document))); + // select first item, focus on it + gQueue.push(new synthDownKey("searchbar", + new focusChecker(getItem, new itemObj("searchbar", 0)))); + // close popup, focus goes on document + gQueue.push(new synthEscapeKey("searchbar", new focusChecker(document))); + + gQueue.onFinish = function() + { + // unregister 'test-a11y-search' autocomplete search + shutdownAutoComplete(); + } + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + // Register 'test-a11y-search' autocomplete search. + // XPFE AutoComplete needs to register early. + initAutoComplete([ "hello", "hi" ], + [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]); + + addA11yLoadEvent(initTests); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=383759" + title="Focus event inconsistent for search box autocomplete"> + Mozilla Bug 383759 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=559766" + title="Add accessibility support for @list on HTML input and for HTML datalist"> + Mozilla Bug 559766 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <html:input is="autocomplete-input" + id="autocomplete" + autocompletesearch="test-a11y-search"/> + + <html:input is="autocomplete-input" + id="richautocomplete" + autocompletesearch="test-a11y-search" + autocompletepopup="richpopup"/> + <panel is="autocomplete-richlistbox-popup" + id="richpopup" + type="autocomplete-richlistbox" + noautofocus="true"/> + + <iframe id="iframe"/> + + <iframe id="iframe2"/> + + <searchbar id="searchbar"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_canvas.html b/accessible/tests/mochitest/events/test_focus_canvas.html new file mode 100644 index 0000000000..e2464e41a6 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_canvas.html @@ -0,0 +1,58 @@ +<html> + +<head> + <title>Accessible focus testing in canvas subdom</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + // gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new synthFocus("button")); + gQueue.push(new synthTab("button", new focusChecker("textbox"))); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + <a target="_blank" + title="Expose content in Canvas element" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912"> + Mozilla Bug 495912 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <canvas> + <input id="button" type="button"> + <input id="textbox"> + </canvas> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_contextmenu.xhtml b/accessible/tests/mochitest/events/test_focus_contextmenu.xhtml new file mode 100644 index 0000000000..a0c92212dc --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_contextmenu.xhtml @@ -0,0 +1,98 @@ +<?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="Context menu focus testing"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug stuff + + var winLowerThanVista = navigator.platform.indexOf("Win") == 0; + if (winLowerThanVista) { + var version = Services.sysinfo.getProperty("version"); + version = parseFloat(version); + winLowerThanVista = !(version >= 6.0); + } + + var gQueue = null; + function doTests() + { + // bug 746183 - Whole file times out on OS X + if (MAC || winLowerThanVista) { + todo(false, "Reenable on mac after fixing bug 746183!"); + SimpleTest.finish(); + return; + } + + // Test focus events. + gQueue = new eventQueue(); + + gQueue.push(new synthFocus("button")); + gQueue.push(new synthContextMenu("button", [ + new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu"), + new invokerChecker("popupshown", "contextmenu"), + ])); + gQueue.push(new synthDownKey("button", new focusChecker("item1"))); + gQueue.push(new synthEscapeKey("contextmenu", new focusChecker("button"))); + + gQueue.push(new synthContextMenu("button", + new invokerChecker(EVENT_MENUPOPUP_START, "contextmenu"))); + gQueue.push(new synthDownKey("contextmenu", new focusChecker("item1"))); + gQueue.push(new synthDownKey("item1", new focusChecker("item2"))); + gQueue.push(new synthRightKey("item2", new focusChecker("item2.1"))); + if (WIN) { + todo(false, "synthEscapeKey for item2.1 and item2 disabled due to bug 691580"); + } else { + gQueue.push(new synthEscapeKey("item2.1", new focusChecker("item2"))); + gQueue.push(new synthEscapeKey("item2", new focusChecker("button"))); + } + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <button id="button" context="contextmenu" label="button"/> + <menupopup id="contextmenu"> + <menuitem id="item1" label="item1"/> + <menu id="item2" label="item2"> + <menupopup> + <menuitem id="item2.1" label="item2.1"/> + </menupopup> + </menu> + </menupopup> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_controls.html b/accessible/tests/mochitest/events/test_focus_controls.html new file mode 100644 index 0000000000..4d25e78908 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_controls.html @@ -0,0 +1,76 @@ +<html> + +<head> + <title>Accessible focus testing on HTML controls</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + // gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(EVENT_FOCUS); + + gQueue.push(new synthFocus("textbox")); + gQueue.push(new synthFocus("textarea")); + gQueue.push(new synthFocus("button1")); + gQueue.push(new synthFocus("button2")); + gQueue.push(new synthFocus("checkbox")); + gQueue.push(new synthFocus("radio1")); + gQueue.push(new synthDownKey("radio1", new focusChecker("radio2"))); + + // no focus events for checkbox or radio inputs when they are checked + // programmatically + gQueue.push(new changeCurrentItem("checkbox")); + gQueue.push(new changeCurrentItem("radio1")); + + let fileBrowseButton = getAccessible("file").firstChild; + gQueue.push(new synthFocus("file", new focusChecker(fileBrowseButton))); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="textbox"> + <textarea id="textarea"></textarea> + + <input id="button1" type="button" value="button"> + <button id="button2">button</button> + <input id="checkbox" type="checkbox"> + <input id="radio1" type="radio" name="radiogroup"> + <input id="radio2" type="radio" name="radiogroup"> + <input id="file" type="file"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_doc.html b/accessible/tests/mochitest/events/test_focus_doc.html new file mode 100644 index 0000000000..a35fc06ed0 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_doc.html @@ -0,0 +1,92 @@ +<html> + +<head> + <title>Accessible document focus event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + var gQueue = null; + + // var gA11yEventDumpID = "eventdump"; + // gA11yEventDumpToConsole = true; + + function doTests() { + // setup + var frameDoc = document.getElementById("iframe").contentDocument; + frameDoc.designMode = "on"; + var frameDocAcc = getAccessible(frameDoc, [nsIAccessibleDocument]); + var buttonAcc = getAccessible("b1"); + + var frame2Doc = document.getElementById("iframe2").contentDocument; + var frame2Input = frame2Doc.getElementById("input"); + var frame2DocAcc = getAccessible(frame2Doc); + var frame2InputAcc = getAccessible(frame2Input); + + // Test focus events. + gQueue = new eventQueue(); + + // try to give focus to contentEditable frame twice to cover bug 512059 + gQueue.push(new synthFocus(buttonAcc)); + gQueue.push(new synthTab(frameDocAcc, new focusChecker(frameDocAcc))); + gQueue.push(new synthFocus(buttonAcc)); + gQueue.push(new synthTab(frameDocAcc, new focusChecker(frameDocAcc))); + + // focus on not editable document + gQueue.push(new synthFocus(frame2InputAcc)); + gQueue.push(new synthShiftTab(frame2DocAcc, new focusChecker(frame2DocAcc))); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=512058" + title="Can't set focus to designMode document via accessibility APIs"> + Mozilla Bug 512058 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=512059" + title="Accessibility focus event never fired for designMode document after the first focus"> + Mozilla Bug 512059 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=618046" + title="No focus change event when Shift+Tab at top of screen"> + Mozilla Bug 618046 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="eventdump"></div> + + <div id="testContainer"> + <button id="b1">a button</button> + <iframe id="iframe" src="about:blank"></iframe> + <button id="b2">a button</button> + <iframe id="iframe2" src="data:text/html,<html><input id='input'></html>"></iframe> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_general.html b/accessible/tests/mochitest/events/test_focus_general.html new file mode 100644 index 0000000000..6919ed8860 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_general.html @@ -0,0 +1,176 @@ +<html> + +<head> + <title>Accessible focus testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + function focusElmWhileSubdocIsFocused(aID) { + this.DOMNode = getNode(aID); + + this.invoke = function focusElmWhileSubdocIsFocused_invoke() { + this.DOMNode.focus(); + }; + + this.eventSeq = [ + new focusChecker(this.DOMNode), + ]; + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_FOCUS, this.DOMNode.ownerDocument), + ]; + + this.getID = function focusElmWhileSubdocIsFocused_getID() { + return "Focus element while subdocument is focused " + prettyName(aID); + }; + } + + function imageMapChecker(aID) { + var node = getNode(aID); + this.type = EVENT_FOCUS; + this.match = function imageMapChecker_match(aEvent) { + return aEvent.DOMNode == node; + }; + } + + function topMenuChecker() { + this.type = EVENT_FOCUS; + this.match = function topMenuChecker_match(aEvent) { + return aEvent.accessible.role == ROLE_PARENT_MENUITEM; + }; + } + + function contextMenuChecker() { + this.type = EVENT_MENUPOPUP_START; + this.match = function contextMenuChecker_match(aEvent) { + return aEvent.accessible.role == ROLE_MENUPOPUP; + }; + } + + function focusContextMenuItemChecker() { + this.__proto__ = new focusChecker(); + + this.match = function focusContextMenuItemChecker_match(aEvent) { + return aEvent.accessible.role == ROLE_MENUITEM; + }; + } + + /** + * Do tests. + */ + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTests() { + var frameDoc = document.getElementById("iframe").contentDocument; + + var editableDoc = document.getElementById("editabledoc").contentDocument; + editableDoc.designMode = "on"; + + gQueue = new eventQueue(); + + gQueue.push(new synthFocus("editablearea")); + gQueue.push(new synthFocus("navarea")); + gQueue.push(new synthTab("navarea", new focusChecker(frameDoc))); + gQueue.push(new focusElmWhileSubdocIsFocused("link")); + + gQueue.push(new synthTab(editableDoc, new focusChecker(editableDoc))); + if (WIN || LINUX) { + // Alt key is used to active menubar and focus menu item on Windows, + // other platforms requires setting a ui.key.menuAccessKeyFocuses + // preference. + gQueue.push(new toggleTopMenu(editableDoc, new topMenuChecker())); + gQueue.push(new toggleTopMenu(editableDoc, new focusChecker(editableDoc))); + } + if (!(MAC && Services.prefs.getBoolPref("widget.macos.native-context-menus", false))) { + // Context menu accessibility is handled natively and not testable when + // native context menus are used on macOS. + gQueue.push(new synthContextMenu(editableDoc, new contextMenuChecker())); + gQueue.push(new synthDownKey(editableDoc, new focusContextMenuItemChecker())); + gQueue.push(new synthEscapeKey(editableDoc, new focusChecker(editableDoc))); + } else { + // If this test is run as part of multiple tests, it is displayed in the test harness iframe. + // In the non-native context menu case, right-clicking the editableDoc causes the editableDoc + // to scroll fully into view, and as a side-effect, the img below it ends up on the screen. + // When we're skipping the context menu check, scroll img onto the screen manually, because + // otherwise it may remain out-of-view and clipped by the test harness iframe. + var img = document.querySelector("img"); + gQueue.push(new scrollIntoView(img, new nofocusChecker(img))); + } + if (SEAMONKEY) { + todo(false, "shift tab from editable document fails on (Windows) SeaMonkey! (Bug 718235)"); + } else if (LINUX || MAC) { + todo(false, "shift tab from editable document fails on linux and Mac, bug 746519!"); + } else { + gQueue.push(new synthShiftTab("link", new focusChecker("link"))); + } // ! SEAMONKEY + + gQueue.push(new synthFocus("a", new imageMapChecker("a"))); + gQueue.push(new synthFocus("b", new imageMapChecker("b"))); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=352220" + title="Inconsistent focus events when returning to a document frame"> + Mozilla Bug 352220 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=550338" + title="Broken focus when returning to editable documents from menus"> + Mozilla Bug 550338 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=961696" + title="Accessible object:state-changed:focused events for imagemap links are broken"> + Mozilla Bug 961696 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="editablearea" contentEditable="true">editable area</div> + <div id="navarea" tabindex="0">navigable area</div> + <iframe id="iframe" src="data:text/html,<html></html>"></iframe> + <a id="link" href="">link</a> + <iframe id="editabledoc" src="about:blank"></iframe> + + <map name="atoz_map"> + <area id="a" coords="0,0,13,14" shape="rect"> + <area id="b" coords="17,0,30,14" shape="rect"> + </map> + <img width="447" height="15" usemap="#atoz_map" src="../letters.gif"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_general.xhtml b/accessible/tests/mochitest/events/test_focus_general.xhtml new file mode 100644 index 0000000000..c446359b32 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_general.xhtml @@ -0,0 +1,124 @@ +<?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" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Accessible focus event testing"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTests() + { + // Test focus events. + gQueue = new eventQueue(); + + gQueue.push(new synthFocus("textbox", + new focusChecker(getNode("textbox")))); + gQueue.push(new synthFocusOnFrame("editabledoc")); + gQueue.push(new synthFocus("radioclothes", + new focusChecker("radiosweater"))); + gQueue.push(new synthDownKey("radiosweater", + new focusChecker("radiojacket"))); + gQueue.push(new synthFocus("checkbox")); + gQueue.push(new synthFocus("button")); + gQueue.push(new synthFocus("checkbutton")); + gQueue.push(new synthFocus("radiobutton")); + + // focus menubutton + gQueue.push(new synthFocus("menubutton")); + // click menubutton, open popup, focus stays on menu button + gQueue.push(new synthClick("menubutton", new nofocusChecker())); + // select first menu item ("item 1"), focus on menu item + gQueue.push(new synthDownKey("menubutton", new focusChecker("mb_item1"))); + // choose select menu item, focus gets back to menubutton + gQueue.push(new synthEnterKey("mb_item1", new focusChecker("menubutton"))); + // press enter to open popup, focus stays on menubutton + gQueue.push(new synthEnterKey("menubutton", new nofocusChecker())); + // select second menu item ("item 2"), focus on menu item + gQueue.push(new synthUpKey("menubutton", new focusChecker("mb_item2"))); + // close the popup + gQueue.push(new synthEscapeKey("menubutton", new focusChecker("menubutton"))); + + // clicking on button having associated popup doesn't change focus + gQueue.push(new synthClick("popupbutton", [ + new nofocusChecker(), + new invokerChecker("popupshown", "backpopup") + ])); + + // select first menu item ("item 1"), focus on menu item + gQueue.push(new synthDownKey("popupbutton", new focusChecker("bp_item1"))); + // choose select menu item, focus gets back to menubutton + gQueue.push(new synthEnterKey("bp_item1", new focusChecker("menubutton"))); + // show popup again for the next test + gQueue.push(new synthClick("popupbutton", new nofocusChecker())); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=552368" + title=" fire focus event on document accessible whenever the root or body element is focused"> + Mozilla Bug 552368 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <html:input id="textbox" value="hello"/> + <iframe id="editabledoc" src="focus.html"/> + <radiogroup id="radioclothes"> + <radio id="radiosweater" label="radiosweater"/> + <radio id="radiocap" label="radiocap" disabled="true"/> + <radio id="radiojacket" label="radiojacket"/> + </radiogroup> + <checkbox id="checkbox" label="checkbox"/> + <button id="button" label="button"/> + + <button id="menubutton" type="menu" label="menubutton"> + <menupopup> + <menuitem id="mb_item1" label="item1"/> + <menuitem id="mb_item2" label="item2"/> + </menupopup> + </button> + + <button id="checkbutton" type="checkbox" label="checkbutton"/> + <button id="radiobutton" type="radio" group="rbgroup" label="radio1"/> + + <popupset> + <menupopup id="backpopup" position="after_start"> + <menuitem id="bp_item1" label="Page 1"/> + <menuitem id="bp_item2" label="Page 2"/> + </menupopup> + </popupset> + <button id="popupbutton" label="Pop Me Up" popup="backpopup"/> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_listcontrols.xhtml b/accessible/tests/mochitest/events/test_focus_listcontrols.xhtml new file mode 100644 index 0000000000..848657d3b3 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_listcontrols.xhtml @@ -0,0 +1,153 @@ +<?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="Accessible focus event testing"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + let PromEvents = {}; + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js", + PromEvents); + //gA11yEventDumpID = "eventdump"; // debug stuff + gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + async function doTests() + { + // Test focus events. + gQueue = new eventQueue(); + // Later tests use await. + let queueFinished = new Promise(resolve => { + gQueue.onFinish = function() { + resolve(); + return DO_NOT_FINISH_TEST; + }; + }); + + gQueue.push(new synthFocus("richlistbox", new focusChecker("rlb_item1"))); + gQueue.push(new synthDownKey("rlb_item1", new focusChecker("rlb_item2"))); + gQueue.push(new synthFocus("multiselrichlistbox", new focusChecker("msrlb_item1"))); + gQueue.push(new synthDownKey("msrlb_item1", new focusChecker("msrlb_item2"), { shiftKey: true })); + gQueue.push(new synthFocus("emptyrichlistbox", new focusChecker("emptyrichlistbox"))); + + gQueue.push(new synthFocus("menulist")); + gQueue.push(new synthClick("menulist", new focusChecker("ml_tangerine"), + { where: "center" })); + gQueue.push(new synthDownKey("ml_tangerine", new focusChecker("ml_marmalade"))); + gQueue.push(new synthEscapeKey("ml_marmalade", new focusChecker("menulist"))); + + // On Windows, items get selected during navigation. + let expectedItem = WIN ? "ml_strawberry" : "ml_marmalade"; + gQueue.push(new synthDownKey("menulist", new nofocusChecker(expectedItem))); + gQueue.push(new synthOpenComboboxKey("menulist", new focusChecker(expectedItem))); + gQueue.push(new synthEnterKey(expectedItem, new focusChecker("menulist"))); + + // no focus events for unfocused list controls when current item is + // changed. + gQueue.push(new synthFocus("emptyrichlistbox")); + + gQueue.push(new changeCurrentItem("richlistbox", "rlb_item1")); + gQueue.push(new changeCurrentItem("menulist", WIN ? "ml_marmalade" : "ml_tangerine")); + + gQueue.invoke(); + await queueFinished; + // Tests beyond this point use await rather than eventQueue. + + // When a menulist contains something other than XUL menuitems, we need + // to manage focus with aria-activedescendant. + info("Testing opening a menupopup with aria-activedescendant"); + let popupDiv1 = getNode("menupopup_ad_div1"); + let focused = PromEvents.waitForEvent(EVENT_FOCUS, popupDiv1); + let popup = getNode("menupopup_ad"); + popup.openPopup(); + await focused; + info("Testing removal of previous active descendant + setting new active descendant"); + focused = PromEvents.waitForEvent(EVENT_FOCUS, "menupopup_ad_div2"); + popupDiv1.remove(); + popup.setAttribute("aria-activedescendant", "menupopup_ad_div2"); + await focused; + popup.hidePopup(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=433418" + title="Accessibles for focused HTML Select elements are not getting focused state"> + Mozilla Bug 433418 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=474893" + title="List controls should fire a focus event on the selected child when tabbing or when the selected child changes while the list is focused"> + Mozilla Bug 474893 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=552368" + title=" fire focus event on document accessible whenever the root or body element is focused"> + Mozilla Bug 552368 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <richlistbox id="richlistbox"> + <richlistitem id="rlb_item1"> + <description>A XUL Description!</description> + </richlistitem> + <richlistitem id="rlb_item2"> + <button label="A XUL Button"/> + </richlistitem> + </richlistbox> + <richlistbox id="multiselrichlistbox" seltype="multiple"> + <richlistitem id="msrlb_item1"> + <description>A XUL Description!</description> + </richlistitem> + <richlistitem id="msrlb_item2"> + <button label="A XUL Button"/> + </richlistitem> + </richlistbox> + <richlistbox id="emptyrichlistbox" seltype="multiple"/> + + <menulist id="menulist"> + <menupopup> + <menuitem id="ml_tangerine" label="tangerine trees"/> + <menuitem id="ml_marmalade" label="marmalade skies"/> + <menuitem id="ml_strawberry" label="strawberry fields"/> + </menupopup> + </menulist> + + <menulist> + <menupopup id="menupopup_ad" aria-activedescendant="menupopup_ad_div1"> + <div id="menupopup_ad_div1" role="option"></div> + <div id="menupopup_ad_div2" role="option"></div> + </menupopup> + </menulist> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_menu.xhtml b/accessible/tests/mochitest/events/test_focus_menu.xhtml new file mode 100644 index 0000000000..dda10517eb --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_menu.xhtml @@ -0,0 +1,117 @@ +<?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="Menu focus testing"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + // gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTests() + { + // Test focus events. + gQueue = new eventQueue(); + + if (WIN) { + gQueue.push(new toggleTopMenu("fruit", new focusChecker("fruit"))); + gQueue.push(new synthRightKey("fruit", new focusChecker("vehicle"))); + gQueue.push(new synthEscapeKey("vehicle", new focusChecker(document))); + } + + // mouse move activate items but no focus event until menubar is active + gQueue.push(new synthMouseMove("fruit", new nofocusChecker("apple"))); + + // mouseover and click on menuitem makes it active before menubar is + // active + gQueue.push(new synthClick("fruit", new focusChecker("fruit"), { where: "center" })); + + // mouseover on menuitem when menubar is active + gQueue.push(new synthMouseMove("apple", new focusChecker("apple"))); + + // keydown on disabled menuitem (disabled items are skipped on linux) + if (WIN) + gQueue.push(new synthDownKey("apple", new focusChecker("orange"))); + + // menu and menuitem are both active + // XXX: intermitent failure because two focus events may be coalesced, + // think to workaround or fix this issue, when done enable queue invoker + // below and remove next two. + //gQueue.push(new synthRightKey("apple", + // [ new focusChecker("vehicle"), + // new focusChecker("cycle")])); + gQueue.push(new synthMouseMove("vehicle", new focusChecker("vehicle"))); + gQueue.push(new synthDownKey("vehicle", new focusChecker("cycle"))); + + // open submenu + gQueue.push(new synthRightKey("cycle", new focusChecker("tricycle"))); + + // move to first menu in cycle, DOMMenuItemActive is fired for fruit, + // cycle and apple menuitems (bug 685191) + todo(false, "focus is fired for 'cycle' menuitem"); + //gQueue.push(new synthRightKey("vehicle", new focusChecker("apple"))); + + // click menuitem to close menu, focus gets back to document + gQueue.push(new synthClick("tricycle", new focusChecker(document), { where: "center" })); + + //enableLogging("focus,DOMEvents,tree"); // logging for bug708927 + //gQueue.onFinish = function() { disableLogging(); } + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673958" + title="Rework accessible focus handling"> + Mozilla Bug 673958 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <menubar> + <menu id="fruit" label="Fruit"> + <menupopup> + <menuitem id="apple" label="Apple"/> + <menuitem id="orange" label="Orange" disabled="true"/> + </menupopup> + </menu> + <menu id="vehicle" label="Vehicle"> + <menupopup id="vehiclePopup"> + <menu id="cycle" label="cycle"> + <menupopup> + <menuitem id="tricycle" label="tricycle"/> + </menupopup> + </menu> + <menuitem id="car" label="Car" disabled="true"/> + </menupopup> + </menu> + </menubar> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_name.html b/accessible/tests/mochitest/events/test_focus_name.html new file mode 100644 index 0000000000..aa77923909 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_name.html @@ -0,0 +1,116 @@ +<html> + +<head> + <title>Accessible name testing on focus</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + /** + * Checker for invokers. + */ + function actionChecker(aID, aDescription) { + this.__proto__ = new invokerChecker(EVENT_FOCUS, aID); + + this.check = function actionChecker_check(aEvent) { + var target = aEvent.accessible; + is(target.description, aDescription, + "Wrong description for " + prettyName(target)); + }; + } + + var gFocusHandler = { + handleEvent: function gFocusHandler_handleEvent(aEvent) { + var elm = aEvent.target; + if (elm.nodeType != Node.ELEMENT_NODE) + return; + + gTooltipElm.style.display = "block"; + + elm.setAttribute("aria-describedby", "tooltip"); + }, + }; + + var gBlurHandler = { + handleEvent: function gBlurHandler_handleEvent(aEvent) { + gTooltipElm.style.display = "none"; + + var elm = aEvent.target; + if (elm.nodeType == Node.ELEMENT_NODE) + elm.removeAttribute("aria-describedby"); + }, + }; + + /** + * Do tests. + */ + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + var gButtonElm = null; + var gTextboxElm = null; + var gTooltipElm = null; + + function doTests() { + gButtonElm = getNode("button"); + gTextboxElm = getNode("textbox"); + gTooltipElm = getNode("tooltip"); + + gButtonElm.addEventListener("focus", gFocusHandler); + gButtonElm.addEventListener("blur", gBlurHandler); + gTextboxElm.addEventListener("focus", gFocusHandler); + gTextboxElm.addEventListener("blur", gBlurHandler); + + // The aria-describedby is changed on DOM focus. Accessible description + // should be updated when a11y focus is fired. + gQueue = new eventQueue(nsIAccessibleEvent.EVENT_FOCUS); + gQueue.onFinish = function() { + gButtonElm.removeEventListener("focus", gFocusHandler); + gButtonElm.removeEventListener("blur", gBlurHandler); + gTextboxElm.removeEventListener("focus", gFocusHandler); + gTextboxElm.removeEventListener("blur", gBlurHandler); + }; + + var descr = "It's a tooltip"; + gQueue.push(new synthFocus("button", new actionChecker("button", descr))); + gQueue.push(new synthTab("textbox", new actionChecker("textbox", descr))); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=520709" + title="mochitest to ensure name/description are updated on a11y focus if they were changed on DOM focus"> + Mozilla Bug 520709 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="tooltip" style="display: none" aria-hidden="true">It's a tooltip</div> + <button id="button">button</button> + <input id="textbox"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_removal.html b/accessible/tests/mochitest/events/test_focus_removal.html new file mode 100644 index 0000000000..eb47b07075 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_removal.html @@ -0,0 +1,95 @@ +<html> + +<head> + <title>Test removal of focused accessible</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + async function setFocus(aNodeToFocus, aExpectedFocus) { + let expected = aExpectedFocus || aNodeToFocus; + let focused = waitForEvent(EVENT_FOCUS, expected); + info("Focusing " + aNodeToFocus.id); + aNodeToFocus.focus(); + await focused; + ok(true, expected.id + " focused after " + + aNodeToFocus.id + ".focus()"); + } + + async function expectFocusAfterRemove(aNodeToRemove, aExpectedFocus, aDisplayNone = false) { + let focused = waitForEvent(EVENT_FOCUS, aExpectedFocus); + info("Removing " + aNodeToRemove.id); + if (aDisplayNone) { + aNodeToRemove.style.display = "none"; + } else { + aNodeToRemove.remove(); + } + await focused; + let friendlyExpected = aExpectedFocus == document ? + "document" : aExpectedFocus.id; + ok(true, friendlyExpected + " focused after " + + aNodeToRemove.id + " removed"); + } + + async function doTests() { + info("Testing removal of focused node itself"); + let button = getNode("button"); + await setFocus(button); + await expectFocusAfterRemove(button, document); + + info("Testing removal of focused node's parent"); + let dialog = getNode("dialog"); + let dialogButton = getNode("dialogButton"); + await setFocus(dialogButton); + await expectFocusAfterRemove(dialog, document); + + info("Testing removal of aria-activedescendant target"); + let listbox = getNode("listbox"); + let option = getNode("option"); + await setFocus(listbox, option); + await expectFocusAfterRemove(option, listbox); + + info("Test hiding focused element with display: none"); + let groupingButton = getNode("groupingButton"); + await setFocus(groupingButton); + await expectFocusAfterRemove(groupingButton, document, true); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <button id="button"></button> + + <div role="dialog" id="dialog"> + <button id="dialogButton"></button> + </div> + + <div role="listbox" id="listbox" tabindex="0" aria-activedescendant="option"> + <div role="option" id="option"></div> + </div> + + <div role="grouping" id="grouping"> + <button id="groupingButton"> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_selects.html b/accessible/tests/mochitest/events/test_focus_selects.html new file mode 100644 index 0000000000..881c8049fd --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_selects.html @@ -0,0 +1,173 @@ +<html> + +<head> + <title>Accessible focus testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + var gQueue = null; + + async function doTests() { + // Bug 746534 - File causes crash or hang on OS X + if (MAC) { + todo(false, "Bug 746534 - test file causes crash or hang on OS X"); + SimpleTest.finish(); + return; + } + + let p = waitForEvent(EVENT_FOCUS, "orange"); + // first item is focused until there's selection + getNode("list").focus(); + await p; + + p = waitForEvents({ + expected: [[EVENT_SELECTION, "orange"]], + unexpected: [ + [EVENT_FOCUS], + stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true), + ], + }); + // item is selected and stays focused and active + synthesizeKey("VK_DOWN"); + await p; + + p = waitForEvents([ + stateChangeEventArgs("orange", EXT_STATE_ACTIVE, false, true), + stateChangeEventArgs("apple", EXT_STATE_ACTIVE, true, true), + [EVENT_FOCUS, "apple"], + ]); + // last selected item is focused + synthesizeKey("VK_DOWN", { shiftKey: true }); + await p; + + p = waitForEvents({ + expected: [ + [EVENT_FOCUS, "orange"], + stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true), + ], + unexpected: [ + [EVENT_FOCUS, "apple"], + stateChangeEventArgs("apple", EXT_STATE_ACTIVE, true, true), + ], + }); + // no focus event if nothing is changed + synthesizeKey("VK_DOWN"); + // current item is focused + synthesizeKey("VK_UP", { ctrlKey: true }); + await p; + + p = waitForEvent(EVENT_FOCUS, "emptylist"); + // focus on empty list (no items to be focused) + synthesizeKey("VK_TAB"); + await p; + + p = waitForEvents({ + expected: [[EVENT_FOCUS, "orange"]], + unexpected: [stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true)], + }); + // current item is focused + synthesizeKey("VK_TAB", { shiftKey: true }); + await p; + + p = waitForEvent(EVENT_FOCUS, "combobox"); + getNode("combobox").focus(); + await p; + + p = waitForEvents({ + expected: [[EVENT_SELECTION, "cb_apple"]], + unexpected: [ + [EVENT_FOCUS], + stateChangeEventArgs("cb_apple", EXT_STATE_ACTIVE, true, true), + ], + }); + // collapsed combobox keeps a focus + synthesizeKey("VK_DOWN"); + await p; + + // no focus events for unfocused list controls when current item is + // changed + + p = waitForEvent(EVENT_FOCUS, "emptylist"); + getNode("emptylist").focus(); + await p; + + p = waitForEvents({ + expected: [[EVENT_SELECTION, "orange"]], + unexpected: [ + [EVENT_FOCUS], + stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true), + ], + }); + // An unfocused selectable list gets selection change events, + // but not active or focus change events. + getNode("list").selectedIndex = getNode("orange").index; + await p; + + p = waitForEvents({ + expected: [[EVENT_SELECTION, "cb_orange"]], + unexpected: [ + [EVENT_FOCUS], + stateChangeEventArgs("cb_orange", EXT_STATE_ACTIVE, true, true), + ], + }); + // An unfocused selectable combobox gets selection change events, + // but not focus events nor active state change events. + getNode("cb_orange").selected = true; + await p; + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=433418" + title="Accessibles for focused HTML Select elements are not getting focused state"> + Mozilla Bug 433418 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=474893" + title="List controls should fire a focus event on the selected child when tabbing or when the selected child changes while the list is focused"> + Mozilla Bug 474893 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select id="list" size="5" multiple=""> + <option id="orange">Orange</option> + <option id="apple">Apple</option> + </select> + + <select id="emptylist" size="5"></select> + + <select id="combobox"> + <option id="cb_orange">Orange</option> + <option id="cb_apple">Apple</option> + </select> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_tabbox.xhtml b/accessible/tests/mochitest/events/test_focus_tabbox.xhtml new file mode 100644 index 0000000000..1b808831dc --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_tabbox.xhtml @@ -0,0 +1,102 @@ +<?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" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Tabbox focus testing"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTests() + { + if (MAC) { + todo(false, "Tests disabled because of imminent failure."); + SimpleTest.finish(); + return; + } + + // Test focus events. + gQueue = new eventQueue(); + + var input = getNode("input"); + gQueue.push(new synthClick("tab1", new focusChecker("tab1"))); + gQueue.push(new synthTab("tab1", new focusChecker("checkbox1"))); + gQueue.push(new synthKey("tab1", "VK_TAB", { ctrlKey: true }, + new focusChecker(input))); + gQueue.push(new synthKey("tab2", "VK_TAB", { ctrlKey: true }, + new focusChecker("tab3"))); + gQueue.push(new synthKey("tab3", "VK_TAB", { ctrlKey: true }, + new focusChecker("tab1"))); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=370396" + title="Control+Tab to an empty tab panel in a tabbox causes focus to leave the tabbox"> + Mozilla Bug 370396 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tabbox> + <tabs> + <tab id="tab1" label="Tab1" selected="true"/> + <tab id="tab2" label="Tab2" /> + <tab id="tab3" label="Tab3" /> + </tabs> + <tabpanels> + <tabpanel orient="vertical"> + <groupbox orient="vertical"> + <checkbox id="checkbox1" label="Monday" width="75"/> + <checkbox label="Tuesday" width="75"/> + <checkbox label="Wednesday" width="75"/> + <checkbox label="Thursday" width="75"/> + <checkbox label="Friday" width="75"/> + <checkbox label="Saturday" width="75"/> + <checkbox label="Sunday" width="75"/> + </groupbox> + + <spacer style="height: 10px" /> + <label value="Label After checkboxes" /> + </tabpanel> + <tabpanel orient="vertical"> + <html:input id="input" /> + </tabpanel> + <tabpanel orient="vertical"> + <description>Tab 3 content</description> + </tabpanel> + </tabpanels> + </tabbox> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_tree.xhtml b/accessible/tests/mochitest/events/test_focus_tree.xhtml new file mode 100644 index 0000000000..f36816c788 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_tree.xhtml @@ -0,0 +1,117 @@ +<?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="XUL tree focus testing"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function focusTree(aTreeID) + { + var checker = new focusChecker(getFirstTreeItem, aTreeID); + this.__proto__ = new synthFocus(aTreeID, [ checker ]); + } + + function moveToNextItem(aTreeID) + { + var checker = new focusChecker(getSecondTreeItem, aTreeID); + this.__proto__ = new synthDownKey(aTreeID, [ checker ]); + } + + //////////////////////////////////////////////////////////////////////////// + // Helpers + + function getTreeItemAt(aTreeID, aIdx) + { return getAccessible(aTreeID).getChildAt(aIdx + 1); } + + function getFirstTreeItem(aTreeID) + { return getTreeItemAt(aTreeID, 0); } + + function getSecondTreeItem(aTreeID) + { return getTreeItemAt(aTreeID, 1); } + + //////////////////////////////////////////////////////////////////////////// + // Test + + var gQueue = null; + + //gA11yEventDumpID = "debug"; // debugging + //gA11yEventDumpToConsole = true; // debugging + + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new focusTree("tree")); + gQueue.push(new moveToNextItem("tree")); + gQueue.push(new synthFocus("emptytree")); + + // no focus event for changed current item for unfocused tree + gQueue.push(new changeCurrentItem("tree", 0)); + + gQueue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(5)); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=386821" + title="Need better solution for firing delayed event against xul tree"> + Mozilla Bug 386821 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=406308" + title="Don't fire accessible focus events if widget is not actually in focus, confuses screen readers"> + Mozilla Bug 406308 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="debug"/> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col1" flex="1" primary="true" label="column"/> + <treecol id="col2" flex="1" label="column 2"/> + </treecols> + <treechildren id="treechildren"/> + </tree> + <tree id="emptytree" flex="1"> + <treecols> + <treecol id="emptytree_col1" flex="1" primary="true" label="column"/> + <treecol id="emptytree_col2" flex="1" label="column 2"/> + </treecols> + <treechildren id="emptytree_treechildren"/> + </tree> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/events/test_focusable_statechange.html b/accessible/tests/mochitest/events/test_focusable_statechange.html new file mode 100644 index 0000000000..5501f55187 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focusable_statechange.html @@ -0,0 +1,128 @@ +<html> + +<head> + <title>Test removal of focused accessible</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + function focusableStateChange(id, enabled) { + return [EVENT_STATE_CHANGE, e => { + e.QueryInterface(nsIAccessibleStateChangeEvent); + return getAccessible(id) == e.accessible && + e.state == STATE_FOCUSABLE && (enabled == undefined || e.isEnabled == enabled); + }]; + } + + function editableStateChange(id, enabled) { + return [EVENT_STATE_CHANGE, e => { + e.QueryInterface(nsIAccessibleStateChangeEvent); + return getAccessible(id) == e.accessible && + e.state == EXT_STATE_EDITABLE && e.isExtraState && + (enabled == undefined || e.isEnabled == enabled); + }]; + } + + async function doTests() { + info("disable buttons."); + // Expect focusable change with 'disabled', + // and don't expect it with 'aria-disabled'. + let p = waitForEvents({ + expected: [focusableStateChange("button2", false)], + unexpected: [focusableStateChange("button1")] + }); + getNode("button1").setAttribute("aria-disabled", "true"); + getNode("button2").disabled = true; + await p; + + info("re-enable button"); + // Expect focusable change with 'disabled', + // and don't expect it with 'aria-disabled'. + p = waitForEvents({ + expected: [focusableStateChange("button2", true)], + unexpected: [focusableStateChange("button1")] + }); + getNode("button1").setAttribute("aria-disabled", "false"); + getNode("button2").disabled = false; + await p; + + info("add tabindex"); + // Expect focusable change on non-input, + // and don't expect event on an already focusable input. + p = waitForEvents({ + expected: [focusableStateChange("div", true)], + unexpected: [focusableStateChange("button2")] + }); + getNode("button2").tabIndex = "0"; + getNode("div").tabIndex = "0"; + await p; + + info("remove tabindex"); + // Expect focusable change when removing tabindex. + p = waitForEvent(...focusableStateChange("div", false)); + getNode("div").removeAttribute("tabindex"); + await p; + + p = waitForEvent(...focusableStateChange("link", false)); + getNode("link").removeAttribute("href"); + await p; + + info("add contenteditable"); + // Expect editable change on non-input, + // and don't expect event on a native input. + p = waitForEvents({ + expected: [focusableStateChange("div", true), editableStateChange("div", true)], + unexpected: [focusableStateChange("input"), editableStateChange("input")] + }); + getNode("input").contentEditable = true; + getNode("div").contentEditable = true; + await p; + + info("remove contenteditable"); + // Expect editable change on non-input, + // and don't expect event on a native input. + p = waitForEvents({ + expected: [focusableStateChange("div", false), editableStateChange("div", false)], + unexpected: [focusableStateChange("input"), editableStateChange("input")] + }); + getNode("input").contentEditable = false; + getNode("div").contentEditable = false; + await p; + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <button id="button1"></button> + <button id="button2"></button> + + <div id="div">Hello</div> + + <a id="link" href="#">A link</a> + + <input id="input" value="Hello"> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_fromUserInput.html b/accessible/tests/mochitest/events/test_fromUserInput.html new file mode 100644 index 0000000000..b3617358cf --- /dev/null +++ b/accessible/tests/mochitest/events/test_fromUserInput.html @@ -0,0 +1,112 @@ +<html> + +<head> + <title>Testing of isFromUserInput in text events</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + /** + * Remove text data from HTML input. + */ + function removeTextFromInput(aID, aStart, aEnd, aText, aFromUser) { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new textChangeChecker(aID, aStart, aEnd, aText, false, aFromUser), + ]; + + this.invoke = function removeTextFromInput_invoke() { + this.DOMNode.focus(); + this.DOMNode.setSelectionRange(aStart, aEnd); + + synthesizeKey("KEY_Delete"); + }; + + this.getID = function removeTextFromInput_getID() { + return "Remove text from " + aStart + " to " + aEnd + " for " + + prettyName(aID); + }; + } + + /** + * Remove text data from text node. + */ + function removeTextFromContentEditable(aID, aStart, aEnd, aText, aFromUser) { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new textChangeChecker(aID, aStart, aEnd, aText, false, aFromUser), + ]; + + this.invoke = function removeTextFromContentEditable_invoke() { + this.DOMNode.focus(); + this.textNode = getNode(aID).firstChild; + var selection = window.getSelection(); + var range = document.createRange(); + range.setStart(this.textNode, aStart); + range.setEnd(this.textNode, aEnd); + selection.addRange(range); + + synthesizeKey("KEY_Delete"); + }; + + this.getID = function removeTextFromContentEditable_getID() { + return "Remove text from " + aStart + " to " + aEnd + " for " + + prettyName(aID); + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + // gA11yEventDumpID = "eventdump"; // debug stuff + + var gQueue = null; + + function doTests() { + gQueue = new eventQueue(); + + // Focused editable text node + gQueue.push(new removeTextFromContentEditable("div", 0, 3, "hel", true)); + + // Focused editable HTML input + gQueue.push(new removeTextFromInput("input", 1, 2, "n", true)); + + gQueue.invoke(); // Will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + + </script> +</head> + + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=686909" + title="isFromUserInput flag on accessible text change events not correct"> + Mozilla Bug 686909 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + <div id="eventdump"></div> + + <div id="div" contentEditable="true">hello</div> + <input id="input" value="input"> + +</body> + +</html> diff --git a/accessible/tests/mochitest/events/test_label.xhtml b/accessible/tests/mochitest/events/test_label.xhtml new file mode 100644 index 0000000000..5780629dc6 --- /dev/null +++ b/accessible/tests/mochitest/events/test_label.xhtml @@ -0,0 +1,178 @@ +<?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="Tests: accessible XUL label/description events"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Invokers + + const kRecreated = 0; + const kTextRemoved = 1; + const kTextChanged = 2; + + const kNoValue = 0; + + /** + * Set/remove @value attribute. + */ + function setValue(aID, aValue, aResult, aOldValue) + { + this.labelNode = getNode(aID); + + this.eventSeq = []; + + switch (aResult) { + case kRecreated: + this.eventSeq.push(new invokerChecker(EVENT_HIDE, this.labelNode)); + this.eventSeq.push(new invokerChecker(EVENT_SHOW, this.labelNode)); + break; + case kTextRemoved: + this.eventSeq.push( + new textChangeChecker(this.labelNode, 0, aOldValue.length, + aOldValue, false)); + break; + case kTextChanged: + this.eventSeq.push( + new textChangeChecker(this.labelNode, 0, aOldValue.length, + aOldValue, false)); + this.eventSeq.push( + new textChangeChecker(this.labelNode, 0, aValue.length, + aValue, true)); + break; + } + + this.invoke = function setValue_invoke() + { + if (aValue === kNoValue) + this.labelNode.removeAttribute("value"); + else + this.labelNode.setAttribute("value", aValue); + } + + this.finalCheck = function setValue_finalCheck() + { + let tree = + { LABEL: [] }; + + const expectChild = (() => { + if (aValue === kNoValue) { + return false; + } + if (aValue === "") { + return this.labelNode.nodeName == "label"; + } + return true; + })(); + + if (expectChild) { + tree.LABEL.push({ STATICTEXT: [ ] }); + } + testAccessibleTree(aID, tree); + } + + this.getID = function setValue_getID() + { + return "set @value='" + aValue + "' for label " + prettyName(aID); + } + } + + /** + * Change @crop attribute. + */ + function setCrop(aID, aCropValue, aRemovedText, aInsertedText) + { + this.labelNode = getNode(aID); + this.width = this.labelNode.getBoundingClientRect().width; + this.charWidth = this.width / this.labelNode.value.length; + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.labelNode), + new invokerChecker(EVENT_SHOW, this.labelNode), + ]; + + this.invoke = function setCrop_invoke() + { + if (!this.labelNode.hasAttribute("crop")) + this.labelNode.style.width = Math.floor(this.width - 2 * this.charWidth) + "px"; + + this.labelNode.setAttribute("crop", aCropValue); + } + + this.getID = function setCrop_finalCheck() + { + return "set crop " + aCropValue; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new setValue("label", "shiroka strana", kRecreated)); + gQueue.push(new setValue("label", "?<>!+_", kTextChanged, "shiroka strana")); + gQueue.push(new setValue("label", "", kRecreated)); + gQueue.push(new setValue("label", kNoValue, kRecreated)); + + gQueue.push(new setValue("descr", "hello world", kRecreated)); + gQueue.push(new setValue("descr", "si_ya", kTextChanged, "hello world")); + gQueue.push(new setValue("descr", "", kTextRemoved, "si_ya")); + gQueue.push(new setValue("descr", kNoValue, kRecreated)); + + gQueue.push(new setCrop("croplabel", "center")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=396166" + title="xul:label@value accessible should implement nsIAccessibleText"> + Bug 396166 + </a> + <br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <label id="label"/> + <description id="descr"/> + + <hbox> + <label id="croplabel" value="valuetocro" + style="font-family: monospace;"/> + </hbox> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/events/test_menu.xhtml b/accessible/tests/mochitest/events/test_menu.xhtml new file mode 100644 index 0000000000..271831ba83 --- /dev/null +++ b/accessible/tests/mochitest/events/test_menu.xhtml @@ -0,0 +1,200 @@ +<?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="Accessible menu events testing for XUL menu"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script><![CDATA[ + function openFileMenu() + { + this.eventSeq = [ + new invokerChecker(EVENT_MENU_START, "menubar"), + new invokerChecker("popupshown", "menupopup-file") + // new invokerChecker(EVENT_FOCUS, getNode("menuitem-newtab")) intermitent failure + ]; + + this.invoke = function openFileMenu_invoke() + { + synthesizeKey("F", {altKey: true, shiftKey: true}); + } + + this.getID = function openFileMenu_getID() + { + return "open file menu by alt+F press"; + } + } + + function openEditMenu() + { + this.eventSeq = [ + new invokerChecker("popuphidden", "menupopup-file"), + new invokerChecker("popupshown", "menupopup-edit"), + // new invokerChecker(EVENT_FOCUS, getNode("menuitem-undo")) intermitent failure + ]; + + this.invoke = function openEditMenu_invoke() + { + synthesizeKey("KEY_ArrowRight"); + } + + this.getID = function openEditMenu_getID() + { + return "open edit menu by lef arrow press"; + } + } + + function closeEditMenu() + { + this.eventSeq = [ + //new invokerChecker(EVENT_FOCUS, document), intermitent failure + new invokerChecker("popuphidden", "menupopup-edit"), + ]; + + this.invoke = function closeEditMenu_invoke() + { + synthesizeKey("KEY_Escape"); + } + + this.getID = function closeEditMenu_getID() + { + return "close edit menu"; + } + } + + function focusFileMenu() + { + this.eventSeq = [ + new invokerChecker(EVENT_MENU_START, getNode("menubar")) + // new invokerChecker(EVENT_FOCUS, getNode("menuitem-file")) //intermitent failure + ]; + + this.invoke = function focusFileMenu_invoke() + { + synthesizeKey("KEY_Alt"); + } + + this.getID = function focusFileMenu_getID() + { + return "activate menubar, focus file menu (atl press)"; + } + } + + function focusEditMenu() + { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getNode("menuitem-edit")) + ]; + + this.invoke = function focusEditMenu_invoke() + { + synthesizeKey("KEY_ArrowRight"); + } + + this.getID = function focusEditMenu_getID() + { + return "focus edit menu"; + } + } + + function leaveMenubar() + { + this.eventSeq = [ + //new invokerChecker(EVENT_FOCUS, document), intermitent failure + new invokerChecker(EVENT_MENU_END, "menubar") + ]; + + this.invoke = function leaveMenubar_invoke() + { + synthesizeKey("KEY_Escape"); + } + + this.getID = function leaveMenubar_getID() + { + return "leave menubar"; + } + } + + /** + * Do tests. + */ + + //gA11yEventDumpID = "eventdump"; + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTests() + { + if (!WIN && !LINUX) { + todo(false, "Enable this test on other platforms."); + SimpleTest.finish(); + return; + } + + todo(false, + "Fix intermitent failures. Focus may randomly occur before or after menupopup events!"); + + gQueue = new eventQueue(); + + gQueue.push(new openFileMenu()); + gQueue.push(new openEditMenu()); + gQueue.push(new closeEditMenu()); + gQueue.push(new leaveMenubar()); + + // Alt key is used to active menubar and focus menu item on Windows, + // other platforms requires setting a ui.key.menuAccessKeyFocuses + // preference. + if (WIN || LINUX) { + gQueue.push(new focusFileMenu()); + gQueue.push(new focusEditMenu()); + gQueue.push(new leaveMenubar()); + } + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + ]]></script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=615189" + title="Clean up FireAccessibleFocusEvent"> + Mozilla Bug 615189 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <menubar id="menubar"> + <menu id="menuitem-file" label="File" accesskey="F"> + <menupopup id="menupopup-file"> + <menuitem id="menuitem-newtab" label="New Tab"/> + </menupopup> + </menu> + <menu id="menuitem-edit" label="Edit" accesskey="E"> + <menupopup id="menupopup-edit"> + <menuitem id="menuitem-undo" label="Undo"/> + </menupopup> + </menu> + </menubar> + + <vbox id="eventdump" role="log"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_mutation.html b/accessible/tests/mochitest/events/test_mutation.html new file mode 100644 index 0000000000..7ee876570b --- /dev/null +++ b/accessible/tests/mochitest/events/test_mutation.html @@ -0,0 +1,580 @@ +<html> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style> + div.displayNone a { display:none; } + div.visibilityHidden a { visibility:hidden; } +</style> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + /** + * Invokers. + */ + var kNoEvents = 0; + + var kShowEvent = 1; + var kHideEvent = 2; + var kReorderEvent = 4; + var kShowEvents = kShowEvent | kReorderEvent; + var kHideEvents = kHideEvent | kReorderEvent; + var kHideAndShowEvents = kHideEvents | kShowEvent; + + /** + * Base class to test mutation a11y events. + * + * @param aNodeOrID [in] node invoker's action is executed for + * @param aEventTypes [in] events to register (see constants above) + * @param aDoNotExpectEvents [in] boolean indicates if events are expected + */ + function mutateA11yTree(aNodeOrID, aEventTypes, aDoNotExpectEvents) { + // Interface + this.DOMNode = getNode(aNodeOrID); + this.doNotExpectEvents = aDoNotExpectEvents; + this.eventSeq = []; + this.unexpectedEventSeq = []; + + /** + * Change default target (aNodeOrID) registered for the given event type. + */ + this.setTarget = function mutateA11yTree_setTarget(aEventType, aTarget) { + var type = this.getA11yEventType(aEventType); + for (var idx = 0; idx < this.getEventSeq().length; idx++) { + if (this.getEventSeq()[idx].type == type) { + this.getEventSeq()[idx].target = aTarget; + return idx; + } + } + return -1; + }; + + /** + * Replace the default target currently registered for a given event type + * with the nodes in the targets array. + */ + this.setTargets = function mutateA11yTree_setTargets(aEventType, aTargets) { + var targetIdx = this.setTarget(aEventType, aTargets[0]); + + var type = this.getA11yEventType(aEventType); + for (var i = 1; i < aTargets.length; i++) { + let checker = new invokerChecker(type, aTargets[i]); + this.getEventSeq().splice(++targetIdx, 0, checker); + } + }; + + // Implementation + this.getA11yEventType = function mutateA11yTree_getA11yEventType(aEventType) { + if (aEventType == kReorderEvent) + return nsIAccessibleEvent.EVENT_REORDER; + + if (aEventType == kHideEvent) + return nsIAccessibleEvent.EVENT_HIDE; + + if (aEventType == kShowEvent) + return nsIAccessibleEvent.EVENT_SHOW; + + return 0; + }; + + this.getEventSeq = function mutateA11yTree_getEventSeq() { + return this.doNotExpectEvents ? this.unexpectedEventSeq : this.eventSeq; + }; + + if (aEventTypes & kHideEvent) { + let checker = new invokerChecker(this.getA11yEventType(kHideEvent), + this.DOMNode); + this.getEventSeq().push(checker); + } + + if (aEventTypes & kShowEvent) { + let checker = new invokerChecker(this.getA11yEventType(kShowEvent), + this.DOMNode); + this.getEventSeq().push(checker); + } + + if (aEventTypes & kReorderEvent) { + let checker = new invokerChecker(this.getA11yEventType(kReorderEvent), + this.DOMNode.parentNode); + this.getEventSeq().push(checker); + } + } + + /** + * Change CSS style for the given node. + */ + function changeStyle(aNodeOrID, aProp, aValue, aEventTypes) { + this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false); + + this.invoke = function changeStyle_invoke() { + this.DOMNode.style[aProp] = aValue; + }; + + this.getID = function changeStyle_getID() { + return aNodeOrID + " change style " + aProp + " on value " + aValue; + }; + } + + /** + * Change class name for the given node. + */ + function changeClass(aParentNodeOrID, aNodeOrID, aClassName, aEventTypes) { + this.__proto__ = new mutateA11yTree(aNodeOrID, aEventTypes, false); + + this.invoke = function changeClass_invoke() { + this.parentDOMNode.className = aClassName; + }; + + this.getID = function changeClass_getID() { + return aNodeOrID + " change class " + aClassName; + }; + + this.parentDOMNode = getNode(aParentNodeOrID); + } + + /** + * Clone the node and append it to its parent. + */ + function cloneAndAppendToDOM(aNodeOrID, aEventTypes, + aTargetsFunc, aReorderTargetFunc) { + var eventTypes = aEventTypes || kShowEvents; + var doNotExpectEvents = (aEventTypes == kNoEvents); + + this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes, + doNotExpectEvents); + + this.invoke = function cloneAndAppendToDOM_invoke() { + var newElm = this.DOMNode.cloneNode(true); + newElm.removeAttribute("id"); + + var targets = aTargetsFunc ? + aTargetsFunc(newElm) : [newElm]; + this.setTargets(kShowEvent, targets); + + if (aReorderTargetFunc) { + var reorderTarget = aReorderTargetFunc(this.DOMNode); + this.setTarget(kReorderEvent, reorderTarget); + } + + this.DOMNode.parentNode.appendChild(newElm); + }; + + this.getID = function cloneAndAppendToDOM_getID() { + return aNodeOrID + " clone and append to DOM."; + }; + } + + /** + * Removes the node from DOM. + */ + function removeFromDOM(aNodeOrID, aEventTypes, + aTargetsFunc, aReorderTargetFunc) { + var eventTypes = aEventTypes || kHideEvents; + var doNotExpectEvents = (aEventTypes == kNoEvents); + + this.__proto__ = new mutateA11yTree(aNodeOrID, eventTypes, + doNotExpectEvents); + + this.invoke = function removeFromDOM_invoke() { + this.DOMNode.remove(); + }; + + this.getID = function removeFromDOM_getID() { + return prettyName(aNodeOrID) + " remove from DOM."; + }; + + if (aTargetsFunc && (eventTypes & kHideEvent)) + this.setTargets(kHideEvent, aTargetsFunc(this.DOMNode)); + + if (aReorderTargetFunc && (eventTypes & kReorderEvent)) + this.setTarget(kReorderEvent, aReorderTargetFunc(this.DOMNode)); + } + + /** + * Clone the node and replace the original node by cloned one. + */ + function cloneAndReplaceInDOM(aNodeOrID) { + this.__proto__ = new mutateA11yTree(aNodeOrID, kHideAndShowEvents, + false); + + this.invoke = function cloneAndReplaceInDOM_invoke() { + this.DOMNode.parentNode.replaceChild(this.newElm, this.DOMNode); + }; + + this.getID = function cloneAndReplaceInDOM_getID() { + return aNodeOrID + " clone and replace in DOM."; + }; + + this.newElm = this.DOMNode.cloneNode(true); + this.newElm.removeAttribute("id"); + this.setTarget(kShowEvent, this.newElm); + } + + /** + * Trigger content insertion (flush layout), removal and insertion of + * the same element for the same parent. + */ + function test1(aContainerID) { + this.divNode = document.createElement("div"); + this.divNode.setAttribute("id", "div-test1"); + this.containerNode = getNode(aContainerID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.divNode), + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function test1_invoke() { + this.containerNode.appendChild(this.divNode); + getComputedStyle(this.divNode, "").color; + this.containerNode.removeChild(this.divNode); + this.containerNode.appendChild(this.divNode); + }; + + this.getID = function test1_getID() { + return "fuzzy test #1: content insertion (flush layout), removal and" + + "reinsertion"; + }; + } + + /** + * Trigger content insertion (flush layout), removal and insertion of + * the same element for the different parents. + */ + function test2(aContainerID, aTmpContainerID) { + this.divNode = document.createElement("div"); + this.divNode.setAttribute("id", "div-test2"); + this.containerNode = getNode(aContainerID); + this.tmpContainerNode = getNode(aTmpContainerID); + this.container = getAccessible(this.containerNode); + this.tmpContainer = getAccessible(this.tmpContainerNode); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.divNode), + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_REORDER, this.tmpContainerNode), + ]; + + this.invoke = function test2_invoke() { + this.tmpContainerNode.appendChild(this.divNode); + getComputedStyle(this.divNode, "").color; + this.tmpContainerNode.removeChild(this.divNode); + this.containerNode.appendChild(this.divNode); + }; + + this.getID = function test2_getID() { + return "fuzzy test #2: content insertion (flush layout), removal and" + + "reinsertion under another container"; + }; + } + + /** + * Content insertion (flush layout) and then removal (nothing was changed). + */ + function test3(aContainerID) { + this.divNode = document.createElement("div"); + this.divNode.setAttribute("id", "div-test3"); + this.containerNode = getNode(aContainerID); + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_SHOW, this.divNode), + new invokerChecker(EVENT_HIDE, this.divNode), + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function test3_invoke() { + this.containerNode.appendChild(this.divNode); + getComputedStyle(this.divNode, "").color; + this.containerNode.removeChild(this.divNode); + }; + + this.getID = function test3_getID() { + return "fuzzy test #3: content insertion (flush layout) and removal"; + }; + } + + function insertReferredElm(aContainerID) { + this.containerNode = getNode(aContainerID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.firstChild; }, this.containerNode), + new invokerChecker(EVENT_SHOW, function(aNode) { return aNode.lastChild; }, this.containerNode), + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function insertReferredElm_invoke() { + let span = document.createElement("span"); + span.setAttribute("id", "insertReferredElms_span"); + let input = document.createElement("input"); + input.setAttribute("aria-labelledby", "insertReferredElms_span"); + this.containerNode.appendChild(span); + this.containerNode.appendChild(input); + }; + + this.getID = function insertReferredElm_getID() { + return "insert inaccessible element and then insert referring element to make it accessible"; + }; + } + + function showHiddenParentOfVisibleChild() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("c4_child")), + new invokerChecker(EVENT_SHOW, getNode("c4_middle")), + new invokerChecker(EVENT_REORDER, getNode("c4")), + ]; + + this.invoke = function showHiddenParentOfVisibleChild_invoke() { + getNode("c4_middle").style.visibility = "visible"; + }; + + this.getID = function showHiddenParentOfVisibleChild_getID() { + return "show hidden parent of visible child"; + }; + } + + function hideNDestroyDoc() { + this.txt = null; + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, () => { return this.txt; }), + ]; + + this.invoke = function hideNDestroyDoc_invoke() { + this.txt = getAccessible("c5").firstChild.firstChild; + this.txt.DOMNode.remove(); + }; + + this.check = function hideNDestroyDoc_check() { + getNode("c5").remove(); + }; + + this.getID = function hideNDestroyDoc_getID() { + return "remove text node and destroy a document on hide event"; + }; + } + + function hideHideNDestroyDoc() { + this.target = null; + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, () => { return this.target; }), + ]; + + this.invoke = function hideHideNDestroyDoc_invoke() { + var doc = getAccessible("c6").firstChild; + var l1 = doc.firstChild; + this.target = l1.firstChild; + var l2 = doc.lastChild; + l1.DOMNode.firstChild.remove(); + l2.DOMNode.firstChild.remove(); + }; + + this.check = function hideHideNDestroyDoc_check() { + getNode("c6").remove(); + }; + + this.getID = function hideHideNDestroyDoc_getID() { + return "remove text nodes (2 events in the queue) and destroy a document on first hide event"; + }; + } + + /** + * Target getters. + */ + function getFirstChild(aNode) { + return [aNode.firstChild]; + } + function getLastChild(aNode) { + return [aNode.lastChild]; + } + + function getNEnsureFirstChild(aNode) { + var node = aNode.firstChild; + getAccessible(node); + return [node]; + } + + function getNEnsureChildren(aNode) { + var children = []; + var node = aNode.firstChild; + do { + children.push(node); + getAccessible(node); + node = node.nextSibling; + } while (node); + + return children; + } + + function getParent(aNode) { + return aNode.parentNode; + } + + // gA11yEventDumpToConsole = true; // debug stuff + // enableLogging("events,verbose"); + + /** + * Do tests. + */ + var gQueue = null; + + function doTests() { + gQueue = new eventQueue(); + + // Show/hide events by changing of display style of accessible DOM node + // from 'inline' to 'none', 'none' to 'inline'. + let id = "link1"; + getAccessible(id); // ensure accessible is created + gQueue.push(new changeStyle(id, "display", "none", kHideEvents)); + gQueue.push(new changeStyle(id, "display", "inline", kShowEvents)); + + // Show/hide events by changing of visibility style of accessible DOM node + // from 'visible' to 'hidden', 'hidden' to 'visible'. + id = "link2"; + getAccessible(id); + gQueue.push(new changeStyle(id, "visibility", "hidden", kHideEvents)); + gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents)); + + // Show/hide events by changing of visibility style of accessible DOM node + // from 'collapse' to 'visible', 'visible' to 'collapse'. + id = "link4"; + gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents)); + gQueue.push(new changeStyle(id, "visibility", "collapse", kHideEvents)); + + // Show/hide events by adding new accessible DOM node and removing old one. + id = "link5"; + gQueue.push(new cloneAndAppendToDOM(id)); + gQueue.push(new removeFromDOM(id)); + + // No show/hide events by adding new not accessible DOM node and removing + // old one, no reorder event for their parent. + id = "child1"; + gQueue.push(new cloneAndAppendToDOM(id, kNoEvents)); + gQueue.push(new removeFromDOM(id, kNoEvents)); + + // Show/hide events by adding new accessible DOM node and removing + // old one, there is reorder event for their parent. + id = "child2"; + gQueue.push(new cloneAndAppendToDOM(id)); + gQueue.push(new removeFromDOM(id)); + + // Show/hide events by adding new DOM node containing accessible DOM and + // removing old one, there is reorder event for their parent. + id = "child3"; + gQueue.push(new cloneAndAppendToDOM(id, kShowEvents, getFirstChild, + getParent)); + + // Hide event for accessible child of unaccessible removed DOM node and + // reorder event for its parent. + gQueue.push(new removeFromDOM(id, kHideEvents, + getNEnsureFirstChild, getParent)); + + // Hide events for accessible children of unaccessible removed DOM node + // and reorder event for its parent. + gQueue.push(new removeFromDOM("child4", kHideEvents, + getNEnsureChildren, getParent)); + + // Show/hide events by creating new accessible DOM node and replacing + // old one. + getAccessible("link6"); // ensure accessible is created + gQueue.push(new cloneAndReplaceInDOM("link6")); + + // Show/hide events by changing class name on the parent node. + gQueue.push(new changeClass("container2", "link7", "", kShowEvents)); + gQueue.push(new changeClass("container2", "link7", "displayNone", + kHideEvents)); + + gQueue.push(new changeClass("container3", "link8", "", kShowEvents)); + gQueue.push(new changeClass("container3", "link8", "visibilityHidden", + kHideEvents)); + + gQueue.push(new test1("testContainer")); + gQueue.push(new test2("testContainer", "testContainer2")); + gQueue.push(new test2("testContainer", "testNestedContainer")); + gQueue.push(new test3("testContainer")); + gQueue.push(new insertReferredElm("testContainer3")); + gQueue.push(new showHiddenParentOfVisibleChild()); + + gQueue.push(new hideNDestroyDoc()); + gQueue.push(new hideHideNDestroyDoc()); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=469985" + title=" turn the test from bug 354745 into mochitest"> + Mozilla Bug 469985</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=472662" + title="no reorder event when html:link display property is changed from 'none' to 'inline'"> + Mozilla Bug 472662</a> + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275</a> + <a target="_blank" + title="Develop a way to handle visibility style" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125"> + Mozilla Bug 606125</a> + <a target="_blank" + title="Update accessible tree on content insertion after layout" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=498015"> + Mozilla Bug 498015</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <div id="testContainer"> + <a id="link1" href="http://www.google.com">Link #1</a> + <a id="link2" href="http://www.google.com">Link #2</a> + <a id="link3" href="http://www.google.com">Link #3</a> + <a id="link4" href="http://www.google.com" style="visibility:collapse">Link #4</a> + <a id="link5" href="http://www.google.com">Link #5</a> + + <div id="container" role="list"> + <span id="child1"></span> + <span id="child2" role="listitem"></span> + <span id="child3"><span role="listitem"></span></span> + <span id="child4"><span id="child4_1" role="listitem"></span><span id="child4_2" role="listitem"></span></span> + </div> + + <a id="link6" href="http://www.google.com">Link #6</a> + + <div id="container2" class="displayNone"><a id="link7">Link #7</a></div> + <div id="container3" class="visibilityHidden"><a id="link8">Link #8</a></div> + <div id="testNestedContainer"></div> + </div> + <div id="testContainer2"></div> + <div id="testContainer3"></div> + + <div id="c4"> + <div style="visibility:hidden" id="c4_middle"> + <div style="visibility:visible" id="c4_child"></div> + </div> + + <iframe id="c5" src="data:text/html,hey"></iframe> + <iframe id="c6" src="data:text/html,<label>l</label><label>l</label>"></iframe> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_namechange.html b/accessible/tests/mochitest/events/test_namechange.html new file mode 100644 index 0000000000..840e2dfb4f --- /dev/null +++ b/accessible/tests/mochitest/events/test_namechange.html @@ -0,0 +1,185 @@ +<html> + +<head> + <title>Accessible name change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + let PromEvents = {}; + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js", + PromEvents); + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function setAttr(aID, aAttr, aValue, aChecker) { + this.eventSeq = [ aChecker ]; + this.invoke = function setAttr_invoke() { + getNode(aID).setAttribute(aAttr, aValue); + }; + + this.getID = function setAttr_getID() { + return "set attr '" + aAttr + "', value '" + aValue + "'"; + }; + } + + /** + * No name change on an accessible, because the accessible is recreated. + */ + function setAttr_recreate(aID, aAttr, aValue) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getAccessible(aID)), + new invokerChecker(EVENT_SHOW, aID), + ]; + this.invoke = function setAttr_recreate_invoke() { + todo(false, "No accessible recreation should happen, just name change event"); + getNode(aID).setAttribute(aAttr, aValue); + }; + + this.getID = function setAttr_recreate_getID() { + return "set attr '" + aAttr + "', value '" + aValue + "'"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + // gA11yEventDumpToConsole = true; // debuggin + + var gQueue = null; + async function doTests() { + gQueue = new eventQueue(); + // Later tests use await. + let queueFinished = new Promise(resolve => { + gQueue.onFinish = function() { + resolve(); + return DO_NOT_FINISH_TEST; + }; + }); + + gQueue.push(new setAttr("tst1", "aria-label", "hi", + new invokerChecker(EVENT_NAME_CHANGE, "tst1"))); + gQueue.push(new setAttr("tst1", "alt", "alt", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1"))); + gQueue.push(new setAttr("tst1", "title", "title", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst1"))); + gQueue.push(new setAttr("tst1", "aria-labelledby", "display", + new invokerChecker(EVENT_NAME_CHANGE, "tst1"))); + + gQueue.push(new setAttr("tst2", "aria-labelledby", "display", + new invokerChecker(EVENT_NAME_CHANGE, "tst2"))); + gQueue.push(new setAttr("tst2", "alt", "alt", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2"))); + gQueue.push(new setAttr("tst2", "title", "title", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2"))); + gQueue.push(new setAttr("tst2", "aria-label", "hi", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst2"))); + + // When `alt` attribute is added or removed from a broken img, + // the accessible is recreated. + gQueue.push(new setAttr_recreate("tst3", "alt", "one")); + // When an `alt` attribute is changed, there is a name change event. + gQueue.push(new setAttr("tst3", "alt", "two", + new invokerChecker(EVENT_NAME_CHANGE, "tst3"))); + gQueue.push(new setAttr("tst3", "title", "title", + new unexpectedInvokerChecker(EVENT_NAME_CHANGE, "tst3"))); + + gQueue.push(new setAttr("tst4", "title", "title", + new invokerChecker(EVENT_NAME_CHANGE, "tst4"))); + + gQueue.invoke(); + await queueFinished; + // Tests beyond this point use await rather than eventQueue. + + const labelledBy = getNode("labelledBy"); + const label = getNode("label"); + let nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, labelledBy); + info("Changing text of aria-labelledby target"); + label.textContent = "l2"; + await nameChanged; + nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, labelledBy); + info("Adding node to aria-labelledby target"); + label.innerHTML = '<p id="labelChild">l3</p>'; + await nameChanged; + nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, labelledBy); + info("Changing text of aria-labelledby target's child"); + getNode("labelChild").textContent = "l4"; + await nameChanged; + + const lateLabelledBy = getNode("lateLabelledBy"); + nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, lateLabelledBy); + info("Setting aria-labelledby"); + lateLabelledBy.setAttribute("aria-labelledby", "lateLabel"); + await nameChanged; + nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, lateLabelledBy); + info("Changing text of late aria-labelledby target's child"); + getNode("lateLabelChild").textContent = "l2"; + await nameChanged; + + nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, "listitem"); + info("Changing textContent of listitem child"); + // Changing textContent replaces the text leaf with a new one. + getNode("listitem").textContent = "world"; + await nameChanged; + + nameChanged = PromEvents.waitForEvent(EVENT_NAME_CHANGE, "button"); + info("Changing text of button's text leaf"); + // Changing the text node's data changes the text without replacing the + // leaf. + getNode("button").firstChild.data = "after"; + await nameChanged; + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=991969" + title="Event not fired when description changes"> + Bug 991969 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <img id="tst1" alt="initial" src="../moz.png"> + <img id="tst2" src="../moz.png"> + <img id="tst3"> + <img id="tst4" src="../moz.png"> + + <div id="labelledBy" aria-labelledby="label"></div> + <div id="label">l1</div> + + <div id="lateLabelledBy"></div> + <div id="lateLabel"><p id="lateLabelChild">l1</p></div> + + <ul><li id="listitem">hello</li></ul> + + <button id="button">before</button> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_namechange.xhtml b/accessible/tests/mochitest/events/test_namechange.xhtml new file mode 100644 index 0000000000..a6dd8cb218 --- /dev/null +++ b/accessible/tests/mochitest/events/test_namechange.xhtml @@ -0,0 +1,90 @@ +<?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"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/chrome-harness.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + + /** + * Check name changed a11y event. + */ + function nameChangeChecker(aMsg, aID) + { + this.type = EVENT_NAME_CHANGE; + + function targetGetter() + { + return getAccessible(aID); + } + Object.defineProperty(this, "target", { get: targetGetter }); + + this.getID = function getID() + { + return aMsg + " name changed"; + } + } + + function changeRichListItemChild() + { + this.invoke = function changeRichListItemChild_invoke() + { + getNode('childcontent').setAttribute('value', 'Changed.'); + } + + this.eventSeq = + [ + new nameChangeChecker("changeRichListItemChild: ", "listitem") + ]; + + this.getID = function changeRichListItemChild_getID() + { + return "changeRichListItemChild"; + } + } + + function doTest() + { + var queue = new eventQueue(); + queue.push(new changeRichListItemChild()); + queue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <vbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=986054" + title="Propagate name change events"> + Mozilla Bug 986054 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <richlistbox> + <richlistitem id="listitem"> + <description id="childcontent" value="This will be changed."/> + </richlistitem> + </richlistbox> + </vbox> +</window> diff --git a/accessible/tests/mochitest/events/test_scroll.xhtml b/accessible/tests/mochitest/events/test_scroll.xhtml new file mode 100644 index 0000000000..d3cc2a7bda --- /dev/null +++ b/accessible/tests/mochitest/events/test_scroll.xhtml @@ -0,0 +1,107 @@ +<?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"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/chrome-harness.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../promisified-events.js" /> + <script type="application/javascript" + src="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Tests + + function matchesAnchorJumpInTabDocument(aTabIdx) { + return evt => { + let tabDoc = aTabIdx ? tabDocumentAt(aTabIdx) : currentTabDocument(); + let anchorAcc = getAccessible(tabDoc.querySelector("a[name='link1']")); + return anchorAcc == evt.accessible; + } + } + + function matchesTabDocumentAt(aTabIdx) { + return evt => { + let tabDoc = aTabIdx ? tabDocumentAt(aTabIdx) : currentTabDocument(); + return getAccessible(tabDoc) == evt.accessible; + } + } + + async function doTest() { + const kURL = "http://mochi.test:8888/a11y/accessible/tests/mochitest/events/scroll.html#link1"; + let evtProm = waitForEvents([ + [EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument], + [EVENT_SCROLLING_START, matchesAnchorJumpInTabDocument()], + ], "Load foreground tab then scroll to anchor"); + + tabBrowser().loadURI(Services.io.newURI(kURL), { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + // Flush layout, so as to guarantee that the a11y tree is constructed. + browserDocument().documentElement.getBoundingClientRect(); + await evtProm; + + + evtProm = waitForEvents({ + expected: [[EVENT_DOCUMENT_LOAD_COMPLETE, matchesTabDocumentAt(1)]], + unexpected: [[EVENT_SCROLLING_START, matchesAnchorJumpInTabDocument(1)]], + }, "Load background tab, don't dispatch scroll to anchor event"); + + tabBrowser().addTab(kURL, { + referrerURI: null, + charset: "", + postData: null, + inBackground: true, + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + // Flush layout, so as to guarantee that the a11y tree is constructed. + browserDocument().documentElement.getBoundingClientRect(); + await evtProm; + + evtProm = waitForEvent(EVENT_SCROLLING_START, + matchesAnchorJumpInTabDocument(), + "Scroll to anchor event dispatched when switching to loaded doc."); + tabBrowser().selectTabAtIndex(1); + await evtProm; + + closeBrowserWindow(); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTest); + ]]> + </script> + + <vbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=691734" + title="Make sure scrolling start event is fired when document receive focus"> + Mozilla Bug 691734 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="eventdump"></vbox> + </vbox> +</window> diff --git a/accessible/tests/mochitest/events/test_scroll_caret.xhtml b/accessible/tests/mochitest/events/test_scroll_caret.xhtml new file mode 100644 index 0000000000..f0f0fccfb2 --- /dev/null +++ b/accessible/tests/mochitest/events/test_scroll_caret.xhtml @@ -0,0 +1,91 @@ +<?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"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/chrome-harness.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Tests + + function getAnchorJumpInTabDocument(aTabIdx) + { + var tabDoc = aTabIdx ? tabDocumentAt(aTabIdx) : currentTabDocument(); + return tabDoc.querySelector("h1[id='heading_1']"); + } + + function loadTab(aURL) + { + this.eventSeq = [ + new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new asyncCaretMoveChecker(0, getAnchorJumpInTabDocument) + ]; + + this.invoke = function loadTab_invoke() + { + tabBrowser().loadURI(Services.io.newURI(aURL), { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + } + + this.getID = function loadTab_getID() + { + return "load tab: " + aURL; + } + } + + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + + var url = "http://mochi.test:8888/a11y/accessible/tests/mochitest/events/scroll.html#heading_1"; + gQueue.push(new loadTab(url)); + gQueue.onFinish = function() { closeBrowserWindow(); } + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTest); + ]]> + </script> + + <vbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1056459" + title="Make sure caret move event is fired when document receive focus"> + Mozilla Bug 1056459 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="eventdump"></vbox> + </vbox> +</window> diff --git a/accessible/tests/mochitest/events/test_selection.html b/accessible/tests/mochitest/events/test_selection.html new file mode 100644 index 0000000000..a749dd9c4c --- /dev/null +++ b/accessible/tests/mochitest/events/test_selection.html @@ -0,0 +1,109 @@ +<html> + +<head> + <title>Accessible selection event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + // gA11yEventDumpToConsole = true; // debuggin + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(); + + + // closed combobox + gQueue.push(new synthFocus("combobox")); + gQueue.push(new synthDownKey("cb1_item1", + selChangeSeq("cb1_item1", "cb1_item2"))); + + // listbox + gQueue.push(new synthClick("lb1_item1", + new invokerChecker(EVENT_SELECTION, "lb1_item1"))); + gQueue.push(new synthDownKey("lb1_item1", + selChangeSeq("lb1_item1", "lb1_item2"))); + + // multiselectable listbox + gQueue.push(new synthClick("lb2_item1", + selChangeSeq(null, "lb2_item1"))); + gQueue.push(new synthDownKey("lb2_item1", + selAddSeq("lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthUpKey("lb2_item2", + selRemoveSeq("lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true }, + selRemoveSeq("lb2_item1"))); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302" + title="Incorrect selection events in HTML, XUL and ARIA"> + Bug 414302 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=810268" + title="There's no way to know unselected item when selection in single selection was changed"> + Bug 810268 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select id="combobox"> + <option id="cb1_item1" value="mushrooms">mushrooms + <option id="cb1_item2" value="greenpeppers">green peppers + <option id="cb1_item3" value="onions" id="onions">onions + <option id="cb1_item4" value="tomatoes">tomatoes + <option id="cb1_item5" value="olives">olives + </select> + + <select id="listbox" size=5> + <option id="lb1_item1" value="mushrooms">mushrooms + <option id="lb1_item2" value="greenpeppers">green peppers + <option id="lb1_item3" value="onions" id="onions">onions + <option id="lb1_item4" value="tomatoes">tomatoes + <option id="lb1_item5" value="olives">olives + </select> + + <p>Pizza</p> + <select id="listbox2" multiple size=5> + <option id="lb2_item1" value="mushrooms">mushrooms + <option id="lb2_item2" value="greenpeppers">green peppers + <option id="lb2_item3" value="onions" id="onions">onions + <option id="lb2_item4" value="tomatoes">tomatoes + <option id="lb2_item5" value="olives">olives + </select> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_selection.xhtml b/accessible/tests/mochitest/events/test_selection.xhtml new file mode 100644 index 0000000000..9c34ddf286 --- /dev/null +++ b/accessible/tests/mochitest/events/test_selection.xhtml @@ -0,0 +1,254 @@ +<?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="Selection event tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + function advanceTab(aTabsID, aDirection, aNextTabID) + { + var eventSeq1 = [ + new invokerChecker(EVENT_SELECTION, aNextTabID) + ] + defineScenario(this, eventSeq1); + + var eventSeq2 = [ + new invokerChecker(EVENT_HIDE, getAccessible(aNextTabID)), + new invokerChecker(EVENT_SHOW, aNextTabID) + ]; + defineScenario(this, eventSeq2); + + this.invoke = function advanceTab_invoke() + { + todo(false, "No accessible recreation should happen, just selection event"); + getNode(aTabsID).advanceSelectedTab(aDirection, true); + } + + this.getID = function synthFocus_getID() + { + return "advanceTab on " + prettyName(aTabsID) + " to " + prettyName(aNextTabID); + } + } + + function select4FirstItems(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(1)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(2)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(3)) + ]; + + this.invoke = function select4FirstItems_invoke() + { + synthesizeKey("VK_DOWN", { shiftKey: true }); // selects two items + synthesizeKey("VK_DOWN", { shiftKey: true }); + synthesizeKey("VK_DOWN", { shiftKey: true }); + } + + this.getID = function select4FirstItems_getID() + { + return "select 4 first items for " + prettyName(aID); + } + } + + function unselect4FirstItems(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(3)), + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(2)), + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(1)), + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0)) + ]; + + this.invoke = function unselect4FirstItems_invoke() + { + synthesizeKey("VK_UP", { shiftKey: true }); + synthesizeKey("VK_UP", { shiftKey: true }); + synthesizeKey("VK_UP", { shiftKey: true }); + synthesizeKey(" ", { ctrlKey: true }); // unselect first item + } + + this.getID = function unselect4FirstItems_getID() + { + return "unselect 4 first items for " + prettyName(aID); + } + } + + function selectAllItems(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode)) + ]; + + this.invoke = function selectAllItems_invoke() + { + synthesizeKey("VK_END", { shiftKey: true }); + } + + this.getID = function selectAllItems_getID() + { + return "select all items for " + prettyName(aID); + } + } + + function unselectAllItemsButFirst(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode)) + ]; + + this.invoke = function unselectAllItemsButFirst_invoke() + { + synthesizeKey("VK_HOME", { shiftKey: true }); + } + + this.getID = function unselectAllItemsButFirst_getID() + { + return "unselect all items for " + prettyName(aID); + } + } + + function unselectSelectItem(aID) + { + this.listboxNode = getNode(aID); + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0)), + new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0)) + ]; + + this.invoke = function unselectSelectItem_invoke() + { + synthesizeKey(" ", { ctrlKey: true }); // select item + synthesizeKey(" ", { ctrlKey: true }); // unselect item + } + + this.getID = function unselectSelectItem_getID() + { + return "unselect and then select first item for " + prettyName(aID); + } + } + + /** + * Do tests. + */ + var gQueue = null; + + //enableLogging("events"); + //gA11yEventDumpToConsole = true; // debuggin + + function doTests() + { + gQueue = new eventQueue(); + + ////////////////////////////////////////////////////////////////////////// + // tabbox + gQueue.push(new advanceTab("tabs", 1, "tab3")); + + ////////////////////////////////////////////////////////////////////////// + // single selection listbox, the first item is selected by default + + gQueue.push(new synthClick("lb1_item2", + new invokerChecker(EVENT_SELECTION, "lb1_item2"))); + gQueue.push(new synthUpKey("lb1_item2", + new invokerChecker(EVENT_SELECTION, "lb1_item1"))); + gQueue.push(new synthDownKey("lb1_item1", + new invokerChecker(EVENT_SELECTION, "lb1_item2"))); + + ////////////////////////////////////////////////////////////////////////// + // multiselectable listbox + gQueue.push(new synthClick("lb2_item1", + new invokerChecker(EVENT_SELECTION, "lb2_item1"))); + gQueue.push(new synthDownKey("lb2_item1", + new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthUpKey("lb2_item2", + new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"), + { shiftKey: true })); + gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true }, + new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1"))); + + ////////////////////////////////////////////////////////////////////////// + // selection event coalescence + + // fire 4 selection_add events + gQueue.push(new select4FirstItems("listbox2")); + // fire 4 selection_remove events + gQueue.push(new unselect4FirstItems("listbox2")); + // fire selection_within event + gQueue.push(new selectAllItems("listbox2")); + // fire selection_within event + gQueue.push(new unselectAllItemsButFirst("listbox2")); + // fire selection_remove/add events + gQueue.push(new unselectSelectItem("listbox2")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302" + title="Incorrect selection events in HTML, XUL and ARIA"> + Mozilla Bug 414302 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <tabbox id="tabbox" selectedIndex="1"> + <tabs id="tabs"> + <tab id="tab1" label="tab1"/> + <tab id="tab2" label="tab2"/> + <tab id="tab3" label="tab3"/> + <tab id="tab4" label="tab4"/> + </tabs> + <tabpanels> + <tabpanel><!-- tabpanel First elements go here --></tabpanel> + <tabpanel><button id="b1" label="b1"/></tabpanel> + <tabpanel><button id="b2" label="b2"/></tabpanel> + <tabpanel></tabpanel> + </tabpanels> + </tabbox> + + <richlistbox id="listbox"> + <richlistitem id="lb1_item1"><label value="item1"/></richlistitem> + <richlistitem id="lb1_item2"><label value="item2"/></richlistitem> + </richlistbox> + + <richlistbox id="listbox2" seltype="multiple"> + <richlistitem id="lb2_item1"><label value="item1"/></richlistitem> + <richlistitem id="lb2_item2"><label value="item2"/></richlistitem> + <richlistitem id="lb2_item3"><label value="item3"/></richlistitem> + <richlistitem id="lb2_item4"><label value="item4"/></richlistitem> + <richlistitem id="lb2_item5"><label value="item5"/></richlistitem> + <richlistitem id="lb2_item6"><label value="item6"/></richlistitem> + <richlistitem id="lb2_item7"><label value="item7"/></richlistitem> + </richlistbox> + + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_selection_aria.html b/accessible/tests/mochitest/events/test_selection_aria.html new file mode 100644 index 0000000000..c479868e03 --- /dev/null +++ b/accessible/tests/mochitest/events/test_selection_aria.html @@ -0,0 +1,122 @@ +<html> + +<head> + <title>ARIA selection event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function selectItem(aSelectID, aItemID) { + this.selectNode = getNode(aSelectID); + this.itemNode = getNode(aItemID); + + this.eventSeq = [ + new invokerChecker(EVENT_SELECTION, aItemID), + ]; + + this.invoke = function selectItem_invoke() { + var itemNode = this.selectNode.querySelector("*[aria-selected='true']"); + if (itemNode) + itemNode.removeAttribute("aria-selected"); + + this.itemNode.setAttribute("aria-selected", "true"); + }; + + this.getID = function selectItem_getID() { + return "select item " + prettyName(aItemID); + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + + // gA11yEventDumpToConsole = true; // debug stuff + + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new selectItem("tablist", "tab1")); + gQueue.push(new selectItem("tablist", "tab2")); + + gQueue.push(new selectItem("tree", "treeitem1")); + gQueue.push(new selectItem("tree", "treeitem1a")); + gQueue.push(new selectItem("tree", "treeitem1a1")); + + gQueue.push(new selectItem("tree2", "tree2item1")); + gQueue.push(new selectItem("tree2", "tree2item1a")); + gQueue.push(new selectItem("tree2", "tree2item1a1")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=569653" + title="Make selection events async"> + Mozilla Bug 569653 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=804040" + title="Selection event not fired when selection of ARIA tab changes"> + Mozilla Bug 804040 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div role="tablist" id="tablist"> + <div role="tab" id="tab1">tab1</div> + <div role="tab" id="tab2">tab2</div> + </div> + + <div id="tree" role="tree"> + <div id="treeitem1" role="treeitem">Canada + <div id="treeitem1a" role="treeitem">- Ontario + <div id="treeitem1a1" role="treeitem">-- Toronto</div> + </div> + <div id="treeitem1b" role="treeitem">- Manitoba</div> + </div> + <div id="treeitem2" role="treeitem">Germany</div> + <div id="treeitem3" role="treeitem">Russia</div> + </div> + + <div id="tree2" role="tree" aria-multiselectable="true"> + <div id="tree2item1" role="treeitem">Canada + <div id="tree2item1a" role="treeitem">- Ontario + <div id="tree2item1a1" role="treeitem">-- Toronto</div> + </div> + <div id="tree2item1b" role="treeitem">- Manitoba</div> + </div> + <div id="tree2item2" role="treeitem">Germany</div> + <div id="tree2item3" role="treeitem">Russia</div> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_statechange.html b/accessible/tests/mochitest/events/test_statechange.html new file mode 100644 index 0000000000..e8ec08a4b3 --- /dev/null +++ b/accessible/tests/mochitest/events/test_statechange.html @@ -0,0 +1,582 @@ +<html> + +<head> + <title>Accessible state change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + async function openNode(aIDDetails, aIDSummary, aIsOpen) { + let p = waitForStateChange(aIDSummary, STATE_EXPANDED, aIsOpen, false); + if (aIsOpen) { + getNode(aIDDetails).setAttribute("open", ""); + } else { + getNode(aIDDetails).removeAttribute("open"); + } + await p; + } + + async function makeEditableDoc(aDocNode, aIsEnabled) { + let p = waitForStateChange(aDocNode, EXT_STATE_EDITABLE, true, true); + aDocNode.designMode = "on"; + await p; + } + + async function invalidInput(aNodeOrID) { + let p = waitForStateChange(aNodeOrID, STATE_INVALID, true, false); + getNode(aNodeOrID).value = "I am not an email"; + await p; + } + + async function changeCheckInput(aID, aIsChecked) { + let p = waitForStateChange(aID, STATE_CHECKED, aIsChecked, false); + getNode(aID).checked = aIsChecked; + await p; + } + + async function changeRequiredState(aID, aIsRequired) { + let p = waitForStateChange(aID, STATE_REQUIRED, aIsRequired, false); + getNode(aID).required = aIsRequired; + await p; + } + + async function stateChangeOnFileInput(aID, aAttr, aValue, + aState, aIsExtraState, aIsEnabled) { + let fileControlNode = getNode(aID); + let fileControl = getAccessible(fileControlNode); + let browseButton = fileControl.firstChild; + let p = waitForEvents([ + stateChangeEventArgs(fileControl, aState, aIsEnabled, aIsExtraState), + stateChangeEventArgs(browseButton, aState, aIsEnabled, aIsExtraState)]) + fileControlNode.setAttribute(aAttr, aValue); + await p; + } + + function toggleSentinel() { + let sentinel = getNode("sentinel"); + if (sentinel.hasAttribute("aria-busy")) { + sentinel.removeAttribute("aria-busy"); + } else { + sentinel.setAttribute("aria-busy", "true"); + } + } + + async function toggleStateChange(aID, aAttr, aState, aIsExtraState) { + let p = waitForEvents([ + stateChangeEventArgs(aID, aState, true, aIsExtraState), + [EVENT_STATE_CHANGE, "sentinel"] + ]); + getNode(aID).setAttribute(aAttr, "true"); + toggleSentinel(); + await p; + p = waitForEvents([ + stateChangeEventArgs(aID, aState, false, aIsExtraState), + [EVENT_STATE_CHANGE, "sentinel"] + ]); + getNode(aID).setAttribute(aAttr, "false"); + toggleSentinel(); + await p; + } + + async function dupeStateChange(aID, aAttr, aValue, + aState, aIsExtraState, aIsEnabled) { + let p = waitForEvents([ + stateChangeEventArgs(aID, aState, aIsEnabled, aIsExtraState), + [EVENT_STATE_CHANGE, "sentinel"] + ]); + getNode(aID).setAttribute(aAttr, aValue); + getNode(aID).setAttribute(aAttr, aValue); + toggleSentinel(); + await p; + } + + async function oppositeStateChange(aID, aAttr, aState, aIsExtraState) { + let p = waitForEvents({ + expected: [[EVENT_STATE_CHANGE, "sentinel"]], + unexpected: [ + stateChangeEventArgs(aID, aState, false, aIsExtraState), + stateChangeEventArgs(aID, aState, true, aIsExtraState) + ] + }); + getNode(aID).setAttribute(aAttr, "false"); + getNode(aID).setAttribute(aAttr, "true"); + toggleSentinel(); + await p; + } + + /** + * Change concomitant ARIA and native attribute at once. + */ + async function echoingStateChange(aID, aARIAAttr, aAttr, aValue, + aState, aIsExtraState, aIsEnabled) { + let p = waitForStateChange(aID, aState, aIsEnabled, aIsExtraState); + if (aValue == null) { + getNode(aID).removeAttribute(aARIAAttr); + getNode(aID).removeAttribute(aAttr); + } else { + getNode(aID).setAttribute(aARIAAttr, aValue); + getNode(aID).setAttribute(aAttr, aValue); + } + await p; + } + + async function testLinked() { + let p = waitForStateChange("link1", STATE_LINKED, false, false); + getNode("link1").removeAttribute("href"); + await p; + + p = waitForStateChange("link2", STATE_LINKED, false, false); + getNode("link2").removeAttribute("onclick"); + await p; + + p = waitForStateChange("link3", STATE_LINKED, true, false); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + getNode("link3").setAttribute("href", "http://example.com"); + await p; + } + + async function testHasPopup() { + let p = waitForStateChange("popupButton", STATE_HASPOPUP, true, false); + getNode("popupButton").setAttribute("aria-haspopup", "true"); + await p; + + p = waitForStateChange("popupButton", STATE_HASPOPUP, false, false); + getNode("popupButton").setAttribute("aria-haspopup", "false"); + await p; + + p = waitForStateChange("popupButton", STATE_HASPOPUP, true, false); + getNode("popupButton").setAttribute("aria-haspopup", "true"); + await p; + + p = waitForStateChange("popupButton", STATE_HASPOPUP, false, false); + getNode("popupButton").removeAttribute("aria-haspopup"); + await p; + } + + async function testDefaultSubmitChange() { + testStates("default-button", + STATE_DEFAULT, 0, + 0, 0, + "button should have DEFAULT state"); + let button = document.createElement("button"); + button.textContent = "new default"; + let p = waitForStateChange("default-button", STATE_DEFAULT, false, false); + getNode("default-button").before(button); + await p; + testStates("default-button", + 0, 0, + STATE_DEFAULT, 0, + "button should not have DEFAULT state"); + p = waitForStateChange("default-button", STATE_DEFAULT, true, false); + button.remove(); + await p; + testStates("default-button", + STATE_DEFAULT, 0, + 0, 0, + "button should have DEFAULT state"); + } + + async function testReadOnly() { + let p = waitForStateChange("email", STATE_READONLY, true, false); + getNode("email").setAttribute("readonly", "true"); + await p; + p = waitForStateChange("email", STATE_READONLY, false, false); + getNode("email").removeAttribute("readonly"); + await p; + } + + async function testReadonlyUntilEditable() { + testStates("article", + STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE, + "article is READONLY and not EDITABLE"); + let p = waitForEvents([ + stateChangeEventArgs("article", STATE_READONLY, false, false), + stateChangeEventArgs("article", EXT_STATE_EDITABLE, true, true)]); + getNode("article").contentEditable = "true"; + await p; + testStates("article", + 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0, + "article is EDITABLE and not READONLY"); + p = waitForEvents([ + stateChangeEventArgs("article", STATE_READONLY, true, false), + stateChangeEventArgs("article", EXT_STATE_EDITABLE, false, true)]); + getNode("article").contentEditable = "false"; + await p; + testStates("article", + STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE, + "article is READONLY and not EDITABLE"); + } + + async function testAnimatedImage() { + testStates("animated-image", + STATE_ANIMATED, 0, + 0, 0, + "image should be animated 1"); + let p = waitForStateChange("animated-image", STATE_ANIMATED, false, false); + getNode("animated-image").src = "../animated-gif-finalframe.gif"; + await p; + testStates("animated-image", + 0, 0, + STATE_ANIMATED, 0, + "image should not be animated 2"); + p = waitForStateChange("animated-image", STATE_ANIMATED, true, false); + getNode("animated-image").src = "../animated-gif.gif"; + await p; + testStates("animated-image", + STATE_ANIMATED, 0, + 0, 0, + "image should be animated 3"); + } + + async function testImageLoad() { + let img = document.createElement("img"); + img.id = "image"; + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + img.src = "http://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs"; + let p = waitForEvent(EVENT_SHOW, "image"); + getNode("eventdump").before(img); + await p; + testStates("image", + STATE_INVISIBLE, 0, + 0, 0, + "image should be invisible"); + p = waitForStateChange("image", STATE_INVISIBLE, false, false); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + await fetch("http://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs?complete"); + await p; + testStates("image", + 0, 0, + STATE_INVISIBLE, 0, + "image should be invisible"); + } + + async function testMultiSelectable(aID, aAttribute) { + testStates(aID, + 0, 0, + STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0, + `${aID} should not be multiselectable`); + let p = waitForEvents([ + stateChangeEventArgs(aID, STATE_MULTISELECTABLE, true, false), + stateChangeEventArgs(aID, STATE_EXTSELECTABLE, true, false), + ]); + getNode(aID).setAttribute(aAttribute, true); + await p; + testStates(aID, + STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0, + 0, 0, + `${aID} should not be multiselectable`); + p = waitForEvents([ + stateChangeEventArgs(aID, STATE_MULTISELECTABLE, false, false), + stateChangeEventArgs(aID, STATE_EXTSELECTABLE, false, false), + ]); + getNode(aID).removeAttribute(aAttribute); + await p; + testStates(aID, + 0, 0, + STATE_MULTISELECTABLE | STATE_EXTSELECTABLE, 0, + `${aID} should not be multiselectable`); + } + + async function testAutocomplete() { + // A text input will have autocomplete via browser's form autofill... + testStates("input", + 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION, + 0, 0, + "input supports autocompletion"); + // unless it is explicitly turned off. + testStates("input-autocomplete-off", + 0, 0, + 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION, + "input-autocomplete-off does not support autocompletion"); + // An input with a datalist will always have autocomplete. + testStates("input-list", + 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION, + 0, 0, + "input-list supports autocompletion"); + // password fields don't get autocomplete. + testStates("input-password", + 0, 0, + 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION, + "input-autocomplete-off does not support autocompletion"); + + let p = waitForEvents({ + expected: [ + // Setting the form's autocomplete attribute to "off" will cause + // "input" to lost its autocomplete state. + stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, false, true) + ], + unexpected: [ + // "input-list" should preserve its autocomplete state regardless of + // forms "autocomplete" attribute + [EVENT_STATE_CHANGE, "input-list"], + // "input-autocomplete-off" already has its autocomplte off, so no state + // change here. + [EVENT_STATE_CHANGE, "input-autocomplete-off"], + // passwords never get autocomplete + [EVENT_STATE_CHANGE, "input-password"], + ] + }); + + getNode("form").setAttribute("autocomplete", "off"); + + await p; + + // Same when we remove the form's autocomplete attribute. + p = waitForEvents({ + expected: [stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, true, true)], + unexpected: [ + [EVENT_STATE_CHANGE, "input-list"], + [EVENT_STATE_CHANGE, "input-autocomplete-off"], + [EVENT_STATE_CHANGE, "input-password"], + ] + }); + + getNode("form").removeAttribute("autocomplete"); + + await p; + + p = waitForEvents({ + expected: [ + // Forcing autocomplete off on an input will cause a state change + stateChangeEventArgs("input", EXT_STATE_SUPPORTS_AUTOCOMPLETION, false, true), + // Associating a datalist with an autocomplete=off input + // will give it an autocomplete state, regardless. + stateChangeEventArgs("input-autocomplete-off", EXT_STATE_SUPPORTS_AUTOCOMPLETION, true, true), + // XXX: datalist inputs also get a HASPOPUP state, the inconsistent + // use of that state is inexplicable, but lets make sure we fire state + // change events for it anyway. + stateChangeEventArgs("input-autocomplete-off", STATE_HASPOPUP, true, false), + ], + unexpected: [ + // Forcing autocomplete off with a dataset input does nothing. + [EVENT_STATE_CHANGE, "input-list"], + // passwords never get autocomplete + [EVENT_STATE_CHANGE, "input-password"], + ] + }); + + getNode("input").setAttribute("autocomplete", "off"); + getNode("input-list").setAttribute("autocomplete", "off"); + getNode("input-autocomplete-off").setAttribute("list", "browsers"); + getNode("input-password").setAttribute("autocomplete", "off"); + + await p; + } + + async function doTests() { + // Test opening details objects + await openNode("detailsOpen", "summaryOpen", true); + await openNode("detailsOpen", "summaryOpen", false); + await openNode("detailsOpen1", "summaryOpen1", true); + await openNode("detailsOpen2", "summaryOpen2", true); + await openNode("detailsOpen3", "summaryOpen3", true); + await openNode("detailsOpen4", "summaryOpen4", true); + await openNode("detailsOpen5", "summaryOpen5", true); + await openNode("detailsOpen6", "summaryOpen6", true); + + // Test delayed editable state change + var doc = document.getElementById("iframe").contentDocument; + await makeEditableDoc(doc); + + // invalid state change + await invalidInput("email"); + + // checked state change + await changeCheckInput("checkbox", true); + await changeCheckInput("checkbox", false); + await changeCheckInput("radio", true); + await changeCheckInput("radio", false); + + // required state change + await changeRequiredState("checkbox", true); + + // file input inherited state changes + await stateChangeOnFileInput("file", "aria-busy", "true", + STATE_BUSY, false, true); + await stateChangeOnFileInput("file", "aria-required", "true", + STATE_REQUIRED, false, true); + await stateChangeOnFileInput("file", "aria-invalid", "true", + STATE_INVALID, false, true); + + await dupeStateChange("div", "aria-busy", "true", + STATE_BUSY, false, true); + await oppositeStateChange("div", "aria-busy", + STATE_BUSY, false); + + await echoingStateChange("text1", "aria-disabled", "disabled", "true", + EXT_STATE_ENABLED, true, false); + await echoingStateChange("text1", "aria-disabled", "disabled", null, + EXT_STATE_ENABLED, true, true); + + await testReadOnly(); + + await testReadonlyUntilEditable(); + + await testLinked(); + + await testHasPopup(); + + await toggleStateChange("textbox", "aria-multiline", EXT_STATE_MULTI_LINE, true); + + await testDefaultSubmitChange(); + + await testAnimatedImage(); + + await testImageLoad(); + + await testMultiSelectable("listbox", "aria-multiselectable"); + + await testMultiSelectable("select", "multiple"); + + await testAutocomplete(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> +<style> + details.openBefore::before{ + content: "before detail content: "; + background: blue; + } + summary.openBefore::before{ + content: "before summary content: "; + background: green; + } + details.openAfter::after{ + content: " :after detail content"; + background: blue; + } + summary.openAfter::after{ + content: " :after summary content"; + background: green; + } +</style> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=564471" + title="Make state change events async"> + Bug 564471 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=555728" + title="Fire a11y event based on HTML5 constraint validation"> + Bug 555728 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017" + title="File input control should be propogate states to descendants"> + Bug 699017 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=788389" + title="Fire statechange event whenever checked state is changed not depending on focused state"> + Bug 788389 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=926812" + title="State change event not fired when both disabled and aria-disabled are toggled"> + Bug 926812 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- open --> + <details id="detailsOpen"><summary id="summaryOpen">open</summary>details can be opened</details> + <details id="detailsOpen1">order doesn't matter<summary id="summaryOpen1">open</summary></details> + <details id="detailsOpen2"><div>additional elements don't matter</div><summary id="summaryOpen2">open</summary></details> + <details id="detailsOpen3" class="openBefore"><summary id="summaryOpen3">summary</summary>content</details> + <details id="detailsOpen4" class="openAfter"><summary id="summaryOpen4">summary</summary>content</details> + <details id="detailsOpen5"><summary id="summaryOpen5" class="openBefore">summary</summary>content</details> + <details id="detailsOpen6"><summary id="summaryOpen6" class="openAfter">summary</summary>content</details> + + + <div id="testContainer"> + <iframe id="iframe"></iframe> + </div> + + <input id="email" type='email'> + + <input id="checkbox" type="checkbox"> + <input id="radio" type="radio"> + + <input id="file" type="file"> + + <div id="div"></div> + + <!-- A sentinal guards from events of interest being fired after it emits a state change --> + <div id="sentinel"></div> + + <input id="text1"> + + <a id="link1" href="#">I am a link link</a> + <a id="link2" onclick="console.log('hi')">I am a link-ish link</a> + <a id="link3">I am a non-link link</a> + + <div id="textbox" role="textbox" aria-multiline="false">hello</div> + + <form id="form"> + <button id="default-button">hello</button> + <button>world</button> + <input id="input"> + <input id="input-autocomplete-off" autocomplete="off"> + <input id="input-list" list="browsers"> + <input id="input-password" type="password"> + <datalist id="browsers"> + <option value="Internet Explorer"> + <option value="Firefox"> + <option value="Google Chrome"> + <option value="Opera"> + <option value="Safari"> + </datalist> + </form> + + <div id="article" role="article">hello</div> + + <img id="animated-image" src="../animated-gif.gif"> + + <ul id="listbox" role="listbox"> + <li role="option">one</li> + <li role="option">two</li> + <li role="option">three</li> + <li role="option">four</li> + <li role="option">five</li> + </ul> + + <select id="select" size="2"> + <option>one</option> + <option>two</option> + <option>three</option> + <option>four</option> + <option>five</option> + <option>size</option> + </select> + + <div id="eventdump"></div> + + <div id="eventdump"></div> + <button id="popupButton">action</button> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_statechange.xhtml b/accessible/tests/mochitest/events/test_statechange.xhtml new file mode 100644 index 0000000000..4d63c664f1 --- /dev/null +++ b/accessible/tests/mochitest/events/test_statechange.xhtml @@ -0,0 +1,117 @@ +<?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="XUL state change event tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../promisified-events.js" /> + + <script type="application/javascript"> + <![CDATA[ + function offscreenChangeEvent(acc, enabled) { + return [ + EVENT_STATE_CHANGE, + event => { + const scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent); + return event.accessible == acc && + scEvent.state == STATE_OFFSCREEN && + scEvent.isEnabled == enabled; + } + ]; + } + + async function testTabpanels() { + const tabs = getNode("tabs"); + is(tabs.selectedIndex, 0, "tab1 initially selected"); + const panel1 = getAccessible("panel1"); + testStates(panel1, 0, 0, STATE_OFFSCREEN); + const panel2 = getAccessible("panel2"); + testStates(panel2, STATE_OFFSCREEN); + const panel3 = getAccessible("panel3"); + testStates(panel3, STATE_OFFSCREEN); + + let events = waitForEvents([ + offscreenChangeEvent(panel1, true), + offscreenChangeEvent(panel2, false) + ]); + info("Selecting tab2"); + tabs.selectedIndex = 1; + await events; + + events = waitForEvents([ + offscreenChangeEvent(panel2, true), + offscreenChangeEvent(panel3, false) + ]); + info("Selecting tab3"); + tabs.selectedIndex = 2; + await events; + + events = waitForEvents([ + offscreenChangeEvent(panel3, true), + offscreenChangeEvent(panel1, false) + ]); + info("Selecting tab1"); + tabs.selectedIndex = 0; + await events; + } + + async function testPressed() { + const toolbarbuttonCheckbox = getNode("toolbarbuttonCheckbox"); + testStates(toolbarbuttonCheckbox, 0, 0, STATE_PRESSED); + info("Checking toolbarbuttonCheckbox"); + let changed = waitForStateChange(toolbarbuttonCheckbox, STATE_PRESSED, true); + toolbarbuttonCheckbox.setAttribute("checked", true); + await changed; + info("Unchecking toolbarbuttonCheckbox"); + changed = waitForStateChange(toolbarbuttonCheckbox, STATE_PRESSED, false); + toolbarbuttonCheckbox.removeAttribute("checked"); + await changed; + } + + async function doTests() { + await testTabpanels(); + await testPressed(); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <tabbox id="tabbox" selectedIndex="0"> + <tabs id="tabs"> + <tab id="tab1" label="tab1"/> + <tab id="tab2" label="tab2"/> + <tab id="tab3" label="tab3"/> + </tabs> + <tabpanels> + <hbox id="panel1"><button label="b1"/></hbox> + <hbox id="panel2"><button label="b2"/></hbox> + <hbox id="panel3"><button label="b3"/></hbox> + </tabpanels> + </tabbox> + + <toolbarbutton id="toolbarbuttonCheckbox" type="checkbox">toolbarbuttonCheckbox</toolbarbutton> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_text.html b/accessible/tests/mochitest/events/test_text.html new file mode 100644 index 0000000000..8a0bd7f9a4 --- /dev/null +++ b/accessible/tests/mochitest/events/test_text.html @@ -0,0 +1,310 @@ +<html> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + /** + * Base text remove invoker and checker. + */ + function textRemoveInvoker(aID, aStart, aEnd, aText) { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new textChangeChecker(aID, aStart, aEnd, aText, false), + ]; + } + + function textInsertInvoker(aID, aStart, aEnd, aText) { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new textChangeChecker(aID, aStart, aEnd, aText, true), + ]; + } + + /** + * Remove inaccessible child node containing accessibles. + */ + function removeChildSpan(aID) { + this.__proto__ = new textRemoveInvoker(aID, 0, 5, "33322"); + + this.invoke = function removeChildSpan_invoke() { + // remove HTML span, a first child of the node + this.DOMNode.firstChild.remove(); + }; + + this.getID = function removeChildSpan_getID() { + return "Remove inaccessible span containing accessible nodes" + prettyName(aID); + }; + } + + /** + * Insert inaccessible child node containing accessibles. + */ + function insertChildSpan(aID, aInsertAllTogether) { + this.__proto__ = new textInsertInvoker(aID, 0, 5, "33322"); + + this.invoke = function insertChildSpan_invoke() { + // <span><span>333</span><span>22</span></span> + if (aInsertAllTogether) { + let topSpan = document.createElement("span"); + let fSpan = document.createElement("span"); + fSpan.textContent = "333"; + topSpan.appendChild(fSpan); + let sSpan = document.createElement("span"); + sSpan.textContent = "22"; + topSpan.appendChild(sSpan); + + this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]); + } else { + let topSpan = document.createElement("span"); + this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]); + + let fSpan = document.createElement("span"); + fSpan.textContent = "333"; + topSpan.appendChild(fSpan); + + let sSpan = document.createElement("span"); + sSpan.textContent = "22"; + topSpan.appendChild(sSpan); + } + }; + + this.getID = function insertChildSpan_getID() { + return "Insert inaccessible span containing accessibles" + + prettyName(aID); + }; + } + + /** + * Remove child embedded accessible. + */ + function removeChildDiv(aID) { + this.__proto__ = new textRemoveInvoker(aID, 5, 6, kEmbedChar); + + this.invoke = function removeChildDiv_invoke() { + var childDiv = this.DOMNode.childNodes[1]; + + // Ensure accessible is created to get text remove event when it's + // removed. + getAccessible(childDiv); + + this.DOMNode.removeChild(childDiv); + }; + + this.getID = function removeChildDiv_getID() { + return "Remove accessible div from the middle of text accessible " + + prettyName(aID); + }; + } + + /** + * Insert child embedded accessible. + */ + function insertChildDiv(aID) { + this.__proto__ = new textInsertInvoker(aID, 5, 6, kEmbedChar); + + this.invoke = function insertChildDiv_invoke() { + var childDiv = document.createElement("div"); + // Note after bug 646216, a sole div without text won't be accessible + // and would not result in an embedded character. + // Therefore, add some text. + childDiv.textContent = "hello"; + this.DOMNode.insertBefore(childDiv, this.DOMNode.childNodes[1]); + }; + + this.getID = function insertChildDiv_getID() { + return "Insert accessible div into the middle of text accessible " + + prettyName(aID); + }; + } + + /** + * Remove children from text container from first to last child or vice + * versa. + */ + function removeChildren(aID, aLastToFirst, aStart, aEnd, aText) { + this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText); + + this.invoke = function removeChildren_invoke() { + if (aLastToFirst) { + while (this.DOMNode.firstChild) + this.DOMNode.removeChild(this.DOMNode.lastChild); + } else { + while (this.DOMNode.firstChild) + this.DOMNode.firstChild.remove(); + } + }; + + this.getID = function removeChildren_getID() { + return "remove children of " + prettyName(aID) + + (aLastToFirst ? " from last to first" : " from first to last"); + }; + } + + /** + * Remove text from HTML input. + */ + function removeTextFromInput(aID, aStart, aEnd, aText) { + this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText); + + this.eventSeq.push(new invokerChecker(EVENT_TEXT_VALUE_CHANGE, + this.DOMNode)); + + this.invoke = function removeTextFromInput_invoke() { + this.DOMNode.focus(); + this.DOMNode.setSelectionRange(aStart, aEnd); + + synthesizeKey("KEY_Delete"); + }; + + this.getID = function removeTextFromInput_getID() { + return "Remove text from " + aStart + " to " + aEnd + " for " + + prettyName(aID); + }; + } + + /** + * Add text into HTML input. + */ + function insertTextIntoInput(aID, aStart, aEnd, aText) { + this.__proto__ = new textInsertInvoker(aID, aStart, aEnd, aText); + + this.eventSeq.push(new invokerChecker(EVENT_TEXT_VALUE_CHANGE, + this.DOMNode)); + + this.invoke = function insertTextIntoInput_invoke() { + this.DOMNode.focus(); + sendString("a"); + }; + + this.getID = function insertTextIntoInput_getID() { + return "Insert text to " + aStart + " for " + prettyName(aID); + }; + } + + /** + * Remove text data from text node of editable area. + */ + function removeTextFromEditable(aID, aStart, aEnd, aText, aTextNode) { + this.__proto__ = new textRemoveInvoker(aID, aStart, aEnd, aText); + + this.invoke = function removeTextFromEditable_invoke() { + this.DOMNode.focus(); + + var selection = window.getSelection(); + var range = document.createRange(); + range.setStart(this.textNode, aStart); + range.setEnd(this.textNode, aEnd); + selection.addRange(range); + + synthesizeKey("KEY_Delete"); + }; + + this.getID = function removeTextFromEditable_getID() { + return "Remove text from " + aStart + " to " + aEnd + " for " + + prettyName(aID); + }; + + this.textNode = getNode(aTextNode); + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + gA11yEventDumpToConsole = true; // debugging + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(); + + // Text remove event on inaccessible child HTML span removal containing + // accessible text nodes. + gQueue.push(new removeChildSpan("p")); + gQueue.push(new insertChildSpan("p"), true); + gQueue.push(new insertChildSpan("p"), false); + + // Remove embedded character. + gQueue.push(new removeChildDiv("div")); + gQueue.push(new insertChildDiv("div")); + + // Remove all children. + var text = kEmbedChar + "txt" + kEmbedChar; + gQueue.push(new removeChildren("div2", true, 0, 5, text)); + gQueue.push(new removeChildren("div3", false, 0, 5, text)); + + // Text remove from text node within hypertext accessible. + gQueue.push(new removeTextFromInput("input", 1, 3, "al")); + gQueue.push(new insertTextIntoInput("input", 1, 2, "a")); + + // bug 570691 + todo(false, "Fix text change events from editable area, see bug 570691"); + // var textNode = getNode("editable").firstChild; + // gQueue.push(new removeTextFromEditable("editable", 1, 3, "al", textNode)); + // textNode = getNode("editable2").firstChild.firstChild; + // gQueue.push(new removeTextFromEditable("editable2", 1, 3, "al", textNode)); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=566293" + title=" wrong length of text remove event when inaccessible node containing accessible nodes is removed"> + Mozilla Bug 566293 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570710" + title="Avoid extra array traversal during text event creation"> + Mozilla Bug 570710 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=574003" + title="Coalesce text events on nodes removal"> + Mozilla Bug 574003 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=575052" + title="Cache text offsets within hypertext accessible"> + Mozilla Bug 575052 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275" + title="Rework accessible tree update code"> + Mozilla Bug 570275 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <p id="p"><span><span>333</span><span>22</span></span>1111</p> + <div id="div">hello<div>hello</div>hello</div> + <div id="div2"><div>txt</div>txt<div>txt</div></div> + <div id="div3"><div>txt</div>txt<div>txt</div></div> + <input id="input" value="value"> + <div contentEditable="true" id="editable">value</div> + <div contentEditable="true" id="editable2"><span>value</span></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_text_alg.html b/accessible/tests/mochitest/events/test_text_alg.html new file mode 100644 index 0000000000..f9b55c8b23 --- /dev/null +++ b/accessible/tests/mochitest/events/test_text_alg.html @@ -0,0 +1,249 @@ +<html> + +<head> + <title>Accessible text update algorithm testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + const kRemoval = false; + const kInsertion = true; + const kUnexpected = true; + + function changeText(aContainerID, aValue, aEventList) { + this.containerNode = getNode(aContainerID); + this.textNode = this.containerNode.firstChild; + this.textData = this.textNode.data; + + this.eventSeq = [ ]; + this.unexpectedEventSeq = [ ]; + + for (var i = 0; i < aEventList.length; i++) { + var event = aEventList[i]; + + var isInserted = event[0]; + var str = event[1]; + var offset = event[2]; + var checker = new textChangeChecker(this.containerNode, offset, + offset + str.length, str, + isInserted); + + if (event[3] == kUnexpected) + this.unexpectedEventSeq.push(checker); + else + this.eventSeq.push(checker); + } + + this.invoke = function changeText_invoke() { + this.textNode.data = aValue; + }; + + this.getID = function changeText_getID() { + return "change text '" + shortenString(this.textData) + "' -> '" + + shortenString(this.textNode.data) + "' for " + + prettyName(this.containerNode); + }; + } + + function expStr(x, doublings) { + for (var i = 0; i < doublings; ++i) + x = x + x; + return x; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(); + + // //////////////////////////////////////////////////////////////////////// + // wqrema -> tqb: substitution coalesced with removal + + var events = [ + [ kRemoval, "w", 0 ], // wqrema -> qrema + [ kInsertion, "t", 0], // qrema -> tqrema + [ kRemoval, "rema", 2 ], // tqrema -> tq + [ kInsertion, "b", 2], // tq -> tqb + ]; + gQueue.push(new changeText("p1", "tqb", events)); + + // //////////////////////////////////////////////////////////////////////// + // b -> insa: substitution coalesced with insertion (complex substitution) + + events = [ + [ kRemoval, "b", 0 ], // b -> + [ kInsertion, "insa", 0], // -> insa + ]; + gQueue.push(new changeText("p2", "insa", events)); + + // //////////////////////////////////////////////////////////////////////// + // abc -> def: coalesced substitutions + + events = [ + [ kRemoval, "abc", 0 ], // abc -> + [ kInsertion, "def", 0], // -> def + ]; + gQueue.push(new changeText("p3", "def", events)); + + // //////////////////////////////////////////////////////////////////////// + // abcabc -> abcDEFabc: coalesced insertions + + events = [ + [ kInsertion, "DEF", 3], // abcabc -> abcDEFabc + ]; + gQueue.push(new changeText("p4", "abcDEFabc", events)); + + // //////////////////////////////////////////////////////////////////////// + // abc -> defabc: insertion into begin + + events = [ + [ kInsertion, "def", 0], // abc -> defabc + ]; + gQueue.push(new changeText("p5", "defabc", events)); + + // //////////////////////////////////////////////////////////////////////// + // abc -> abcdef: insertion into end + + events = [ + [ kInsertion, "def", 3], // abc -> abcdef + ]; + gQueue.push(new changeText("p6", "abcdef", events)); + + // //////////////////////////////////////////////////////////////////////// + // defabc -> abc: removal from begin + + events = [ + [ kRemoval, "def", 0], // defabc -> abc + ]; + gQueue.push(new changeText("p7", "abc", events)); + + // //////////////////////////////////////////////////////////////////////// + // abcdef -> abc: removal from the end + + events = [ + [ kRemoval, "def", 3], // abcdef -> abc + ]; + gQueue.push(new changeText("p8", "abc", events)); + + // //////////////////////////////////////////////////////////////////////// + // abcDEFabc -> abcabc: coalesced removals + + events = [ + [ kRemoval, "DEF", 3], // abcDEFabc -> abcabc + ]; + gQueue.push(new changeText("p9", "abcabc", events)); + + // //////////////////////////////////////////////////////////////////////// + // !abcdef@ -> @axbcef!: insertion, deletion and substitutions + + events = [ + [ kRemoval, "!", 0 ], // !abcdef@ -> abcdef@ + [ kInsertion, "@", 0], // abcdef@ -> @abcdef@ + [ kInsertion, "x", 2 ], // @abcdef@ -> @axbcdef@ + [ kRemoval, "d", 5], // @axbcdef@ -> @axbcef@ + [ kRemoval, "@", 7 ], // @axbcef@ -> @axbcef + [ kInsertion, "!", 7 ], // @axbcef -> @axbcef! + ]; + gQueue.push(new changeText("p10", "@axbcef!", events)); + + // //////////////////////////////////////////////////////////////////////// + // meilenstein -> levenshtein: insertion, complex and simple substitutions + + events = [ + [ kRemoval, "m", 0 ], // meilenstein -> eilenstein + [ kInsertion, "l", 0], // eilenstein -> leilenstein + [ kRemoval, "il", 2 ], // leilenstein -> leenstein + [ kInsertion, "v", 2], // leenstein -> levenstein + [ kInsertion, "h", 6 ], // levenstein -> levenshtein + ]; + gQueue.push(new changeText("p11", "levenshtein", events)); + + // //////////////////////////////////////////////////////////////////////// + // long strings, remove/insert pair as the old string was replaced on + // new one + + var longStr1 = expStr("x", 16); + var longStr2 = expStr("X", 16); + + var newStr = "a" + longStr1 + "b", insStr = longStr1, rmStr = ""; + events = [ + [ kRemoval, rmStr, 1, kUnexpected ], + [ kInsertion, insStr, 1 ], + ]; + gQueue.push(new changeText("p12", newStr, events)); + + newStr = "a" + longStr2 + "b"; + insStr = longStr2; + rmStr = longStr1; + events = [ + [ kRemoval, rmStr, 1 ], + [ kInsertion, insStr, 1], + ]; + gQueue.push(new changeText("p12", newStr, events)); + + newStr = "ab"; + insStr = ""; + rmStr = longStr2; + events = [ + [ kRemoval, rmStr, 1 ], + [ kInsertion, insStr, 1, kUnexpected ], + ]; + gQueue.push(new changeText("p12", newStr, events)); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660" + title="Cache rendered text on a11y side"> + Mozilla Bug 626660 + </a> + <br> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <!-- Note: only editable text gets diffed this way. --> + <div contenteditable="true"> + <p id="p1">wqrema</p> + <p id="p2">b</p> + <p id="p3">abc</p> + <p id="p4">abcabc</p> + <p id="p5">abc</p> + <p id="p6">abc</p> + <p id="p7">defabc</p> + <p id="p8">abcdef</p> + <p id="p9">abcDEFabc</p> + <p id="p10">!abcdef@</p> + <p id="p11">meilenstein</p> + <p id="p12">ab</p> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_textattrchange.html b/accessible/tests/mochitest/events/test_textattrchange.html new file mode 100644 index 0000000000..6282ceb5c2 --- /dev/null +++ b/accessible/tests/mochitest/events/test_textattrchange.html @@ -0,0 +1,105 @@ +<html> + +<head> + <title>Text attribute changed event for misspelled text</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + + const {InlineSpellChecker} = ChromeUtils.import("resource://gre/modules/InlineSpellChecker.jsm"); + + function spelledTextInvoker(aID) { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_TEXT_ATTRIBUTE_CHANGED, this.DOMNode), + ]; + + this.invoke = function spelledTextInvoker_invoke() { + var editor = this.DOMNode.editor; + var spellChecker = new InlineSpellChecker(editor); + spellChecker.enabled = true; + + // var spellchecker = editor.getInlineSpellChecker(true); + // spellchecker.enableRealTimeSpell = true; + + this.DOMNode.value = "valid text inalid tixt"; + }; + + this.finalCheck = function spelledTextInvoker_finalCheck() { + var defAttrs = buildDefaultTextAttrs(this.DOMNode, kInputFontSize, + kNormalFontWeight, + kInputFontFamily); + testDefaultTextAttrs(aID, defAttrs); + + var attrs = { }; + var misspelledAttrs = { + "invalid": "spelling", + }; + + testTextAttrs(aID, 0, attrs, defAttrs, 0, 11); + testTextAttrs(aID, 11, misspelledAttrs, defAttrs, 11, 17); + testTextAttrs(aID, 17, attrs, defAttrs, 17, 18); + testTextAttrs(aID, 18, misspelledAttrs, defAttrs, 18, 22); + }; + + this.getID = function spelledTextInvoker_getID() { + return "text attribute change for misspelled text"; + }; + } + + /** + * Do tests. + */ + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTests() { + // Synth focus before spellchecking turning on to make sure editor + // gets a time for initialization. + + gQueue = new eventQueue(); + gQueue.push(new synthFocus("input")); + gQueue.push(new spelledTextInvoker("input")); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=345759" + title="Implement text attributes"> + Mozilla Bug 345759 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="input"/> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_textselchange.html b/accessible/tests/mochitest/events/test_textselchange.html new file mode 100644 index 0000000000..3dce0760eb --- /dev/null +++ b/accessible/tests/mochitest/events/test_textselchange.html @@ -0,0 +1,82 @@ +<html> + +<head> + <title>Accessible text selection change events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + var gQueue = null; + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + function getOnclickSeq(aID) { + return [ + new caretMoveChecker(0, true, aID), + new unexpectedInvokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID), + ]; + } + + function doTests() { + // test caret move events and caret offsets + gQueue = new eventQueue(); + + gQueue.push(new synthClick("c1_p1", getOnclickSeq("c1_p1"))); + gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 1, "c1_p1", 0, "c1_p2", 0), { shiftKey: true })); + gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 2, "c1_p1", 0, "c1_p2", 9), { shiftKey: true })); + + gQueue.push(new synthClick("ta1", getOnclickSeq("ta1"))); + gQueue.push(new synthRightKey("ta1", + new textSelectionChecker("ta1", 0, 1, "ta1", 0, "ta1", 1), + { shiftKey: true })); + gQueue.push(new synthLeftKey("ta1", + [new textSelectionChecker("ta1", 0, 0, "ta1", 0, "ta1", 0), + new caretMoveChecker(0, true, "ta1")])); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=762934" + title="Text selection change event has a wrong target when selection is spanned through several objects"> + Bug 762934 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=956032" + title="Text selection change event missed when selected text becomes unselected"> + Bug 956032 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="c1" contentEditable="true"> + <p id="c1_p1">paragraph</p> + <p id="c1_p2">paragraph</p> + </div> + + <textarea id="ta1">Hello world</textarea> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_tree.xhtml b/accessible/tests/mochitest/events/test_tree.xhtml new file mode 100644 index 0000000000..af7feafde8 --- /dev/null +++ b/accessible/tests/mochitest/events/test_tree.xhtml @@ -0,0 +1,358 @@ +<?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="DOM TreeRowCountChanged and a11y name change events."> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + var gView; + + //////////////////////////////////////////////////////////////////////////// + // Invoker's checkers + + /** + * Check TreeRowCountChanged event. + */ + function rowCountChangedChecker(aMsg, aIdx, aCount) + { + this.type = "TreeRowCountChanged"; + this.target = gTree; + this.check = function check(aEvent) + { + var propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2); + var index = propBag.getPropertyAsInt32("index"); + is(index, aIdx, "Wrong 'index' data of 'treeRowCountChanged' event."); + + var count = propBag.getPropertyAsInt32("count"); + is(count, aCount, "Wrong 'count' data of 'treeRowCountChanged' event."); + } + this.getID = function getID() + { + return aMsg + "TreeRowCountChanged"; + } + } + + /** + * Check TreeInvalidated event. + */ + function treeInvalidatedChecker(aMsg, aStartRow, aEndRow, aStartCol, aEndCol) + { + this.type = "TreeInvalidated"; + this.target = gTree; + this.check = function check(aEvent) + { + var propBag = aEvent.detail.QueryInterface(Ci.nsIPropertyBag2); + try { + var startRow = propBag.getPropertyAsInt32("startrow"); + } catch (e) { + if (e.name != 'NS_ERROR_NOT_AVAILABLE') { + throw e; + } + startRow = null; + } + is(startRow, aStartRow, + "Wrong 'startrow' of 'treeInvalidated' event on " + aMsg); + + try { + var endRow = propBag.getPropertyAsInt32("endrow"); + } catch (e) { + if (e.name != 'NS_ERROR_NOT_AVAILABLE') { + throw e; + } + endRow = null; + } + is(endRow, aEndRow, + "Wrong 'endrow' of 'treeInvalidated' event on " + aMsg); + + try { + var startCol = propBag.getPropertyAsInt32("startcolumn"); + } catch (e) { + if (e.name != 'NS_ERROR_NOT_AVAILABLE') { + throw e; + } + startCol = null; + } + is(startCol, aStartCol, + "Wrong 'startcolumn' of 'treeInvalidated' event on " + aMsg); + + try { + var endCol = propBag.getPropertyAsInt32("endcolumn"); + } catch (e) { + if (e.name != 'NS_ERROR_NOT_AVAILABLE') { + throw e; + } + endCol = null; + } + is(endCol, aEndCol, + "Wrong 'endcolumn' of 'treeInvalidated' event on " + aMsg); + } + this.getID = function getID() + { + return "TreeInvalidated on " + aMsg; + } + } + + /** + * Check name changed a11y event. + */ + function nameChangeChecker(aMsg, aRow, aCol) + { + this.type = EVENT_NAME_CHANGE; + + function targetGetter() + { + var acc = getAccessible(gTree); + + var tableAcc = getAccessible(acc, [nsIAccessibleTable]); + return tableAcc.getCellAt(aRow, aCol); + } + Object.defineProperty(this, "target", { get: targetGetter }); + + this.getID = function getID() + { + return aMsg + "name changed"; + } + } + + /** + * Check name changed a11y event for a row. + */ + function rowNameChangeChecker(aMsg, aRow) + { + this.type = EVENT_NAME_CHANGE; + + function targetGetter() + { + var acc = getAccessible(gTree); + return acc.getChildAt(aRow + 1); + } + Object.defineProperty(this, "target", { get: targetGetter }); + + this.getID = function getID() + { + return aMsg + "name changed"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + /** + * Set tree view. + */ + function setTreeView() + { + this.invoke = function setTreeView_invoke() + { + gTree.view = gView; + } + + this.getID = function setTreeView_getID() { return "set tree view"; } + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, gTree) + ]; + }; + + /** + * Insert row at 0 index and checks TreeRowCountChanged and TreeInvalidated + * event. + */ + function insertRow() + { + this.invoke = function insertRow_invoke() + { + gView.appendItem("last"); + gTree.rowCountChanged(0, 1); + } + + this.eventSeq = + [ + new rowCountChangedChecker("insertRow: ", 0, 1), + new treeInvalidatedChecker("insertRow", 0, 5, null, null) + ]; + + this.getID = function insertRow_getID() + { + return "insert row"; + } + } + + /** + * Invalidates first column and checks six name changed events for each + * treeitem plus TreeInvalidated event. + */ + function invalidateColumn() + { + this.invoke = function invalidateColumn_invoke() + { + // Make sure accessible subtree of XUL tree is created otherwise no + // name change events for cell accessibles are emitted. + var tree = getAccessible(gTree); + var child = tree.firstChild; + var walkDown = true; + while (child != tree) { + if (walkDown) { + var grandChild = child.firstChild; + if (grandChild) { + child = grandChild; + continue; + } + } + + var sibling = child.nextSibling; + if (sibling) { + child = sibling; + walkDown = true; + continue; + } + + child = child.parent; + walkDown = false; + } + + // Fire 'TreeInvalidated' event by InvalidateColumn() + var firstCol = gTree.columns.getFirstColumn(); + for (var i = 0; i < gView.rowCount; i++) + gView.setCellText(i, firstCol, "hey " + String(i) + "x0"); + + gTree.invalidateColumn(firstCol); + } + + this.eventSeq = + [ + new nameChangeChecker("invalidateColumn: ", 0, 0), + new nameChangeChecker("invalidateColumn: ", 1, 0), + new nameChangeChecker("invalidateColumn: ", 2, 0), + new nameChangeChecker("invalidateColumn: ", 3, 0), + new nameChangeChecker("invalidateColumn: ", 4, 0), + new nameChangeChecker("invalidateColumn: ", 5, 0), + new treeInvalidatedChecker("invalidateColumn", null, null, 0, 0) + ]; + + this.getID = function invalidateColumn_getID() + { + return "invalidate column"; + } + } + + /** + * Invalidates second row and checks name changed event for first treeitem + * (note, there are two name changed events on linux due to different + * accessible tree for xul:tree element) plus TreeInvalidated event. + */ + function invalidateRow() + { + this.invoke = function invalidateRow_invoke() + { + // Fire 'TreeInvalidated' event by InvalidateRow() + // eslint-disable-next-line no-unused-vars + var colCount = gTree.columns.count; + var column = gTree.columns.getFirstColumn(); + while (column) { + gView.setCellText(1, column, "aloha 1x" + String(column.index)); + column = column.getNext(); + } + + gTree.invalidateRow(1); + } + + this.eventSeq = + [ + new nameChangeChecker("invalidateRow: ", 1, 0), + new nameChangeChecker("invalidateRow: ", 1, 1), + new rowNameChangeChecker("invalidateRow: ", 1), + new treeInvalidatedChecker("invalidateRow", 1, 1, null, null) + ]; + + this.getID = function invalidateRow_getID() + { + return "invalidate row"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + var gTree = null; + var gTreeView = null; + var gQueue = null; + + // gA11yEventDumpID = "debug"; + gA11yEventDumpToConsole = true; // debuggin + + function doTest() + { + // Initialize the tree + gTree = document.getElementById("tree"); + gView = new nsTableTreeView(5); + + // Perform actions + gQueue = new eventQueue(); + + gQueue.push(new setTreeView()); + gQueue.push(new insertRow()); + gQueue.push(new invalidateColumn()); + gQueue.push(new invalidateRow()); + + gQueue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=368835" + title="Fire TreeViewChanged/TreeRowCountChanged events."> + Mozilla Bug 368835 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=308564" + title="No accessibility events when data in a tree row changes."> + Mozilla Bug 308564 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=739524" + title="replace TreeViewChanged DOM event on direct call from XUL tree."> + Mozilla Bug 739524 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=743568" + title="Thunderbird message list tree emitting incorrect focus signals after message deleted."> + Mozilla Bug 743568 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="debug"/> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col1" flex="1" primary="true" label="column"/> + <treecol id="col2" flex="1" label="column 2"/> + </treecols> + <treechildren id="treechildren"/> + </tree> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/events/test_valuechange.html b/accessible/tests/mochitest/events/test_valuechange.html new file mode 100644 index 0000000000..1ad3c0359d --- /dev/null +++ b/accessible/tests/mochitest/events/test_valuechange.html @@ -0,0 +1,315 @@ +<html> + +<head> + <title>Accessible value change events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript" + src="../value.js"></script> + + <script type="application/javascript"> + /** + * Do tests. + */ + var gQueue = null; + + // Value change invoker + function changeARIAValue(aNodeOrID, aValuenow, aValuetext) { + this.DOMNode = getNode(aNodeOrID); + this.eventSeq = [ new invokerChecker(aValuetext ? + EVENT_TEXT_VALUE_CHANGE : + EVENT_VALUE_CHANGE, this.DOMNode), + ]; + + this.invoke = function changeARIAValue_invoke() { + // Note: this should not fire an EVENT_VALUE_CHANGE when aria-valuetext + // is not empty + if (aValuenow != undefined) + this.DOMNode.setAttribute("aria-valuenow", aValuenow); + + // Note: this should always fire an EVENT_VALUE_CHANGE + if (aValuetext != undefined) + this.DOMNode.setAttribute("aria-valuetext", aValuetext); + }; + + this.check = function changeARIAValue_check() { + var acc = getAccessible(aNodeOrID, [nsIAccessibleValue]); + if (!acc) + return; + + // Note: always test against valuetext first because the existence of + // aria-valuetext takes precedence over aria-valuenow in gecko. + is(acc.value, (aValuetext != undefined) ? aValuetext : aValuenow, + "Wrong value of " + prettyName(aNodeOrID)); + }; + + this.getID = function changeARIAValue_getID() { + return prettyName(aNodeOrID) + " value changed"; + }; + } + + function changeValue(aID, aValue) { + this.DOMNode = getNode(aID); + this.eventSeq = [new invokerChecker(EVENT_TEXT_VALUE_CHANGE, + this.DOMNode), + ]; + + this.invoke = function changeValue_invoke() { + this.DOMNode.value = aValue; + }; + + this.check = function changeValue_check() { + var acc = getAccessible(this.DOMNode); + is(acc.value, aValue, "Wrong value for " + prettyName(aID)); + }; + + this.getID = function changeValue_getID() { + return prettyName(aID) + " value changed"; + }; + } + + function changeProgressValue(aID, aValue) { + this.DOMNode = getNode(aID); + this.eventSeq = [new invokerChecker(EVENT_VALUE_CHANGE, this.DOMNode)]; + + this.invoke = function changeProgressValue_invoke() { + this.DOMNode.value = aValue; + }; + + this.check = function changeProgressValue_check() { + var acc = getAccessible(this.DOMNode); + is(acc.value, aValue + "%", "Wrong value for " + prettyName(aID)); + }; + + this.getID = function changeProgressValue_getID() { + return prettyName(aID) + " value changed"; + }; + } + + function changeRangeValueWithMouse(aID) { + this.DOMNode = getNode(aID); + this.eventSeq = [new invokerChecker(EVENT_VALUE_CHANGE, this.DOMNode)]; + + this.invoke = function changeRangeValue_invoke() { + synthesizeMouse(getNode(aID), 5, 5, { }); + }; + + this.finalCheck = function changeRangeValue_finalCheck() { + var acc = getAccessible(this.DOMNode); + is(acc.value, "0", "Wrong value for " + prettyName(aID)); + }; + + this.getID = function changeRangeValue_getID() { + return prettyName(aID) + " range value changed"; + }; + } + + function changeRangeValueWithA11yAPI(aID) { + this.DOMNode = getNode(aID); + let inputChecker = new invokerChecker("input", this.DOMNode); + inputChecker.eventTarget = "element"; + + let changeChecker = new invokerChecker("change", this.DOMNode); + changeChecker.eventTarget = "element"; + + this.eventSeq = [ + inputChecker, + changeChecker, + new invokerChecker(EVENT_VALUE_CHANGE, this.DOMNode), + ]; + + this.invoke = function changeRangeValue_invoke() { + this.DOMNode.focus(); + let acc = getAccessible(this.DOMNode, [nsIAccessibleValue]); + acc.currentValue = 0; + this.DOMNode.blur(); + }; + + this.finalCheck = function changeRangeValue_finalCheck() { + var acc = getAccessible(this.DOMNode); + is(acc.value, "0", "Wrong value for " + prettyName(aID)); + }; + + this.getID = function changeRangeValue_getID() { + return prettyName(aID) + " range value changed"; + }; + } + + function changeSelectValue(aID, aKey, aValue) { + this.eventSeq = + [ new invokerChecker(EVENT_TEXT_VALUE_CHANGE, getAccessible(aID)) ]; + + this.invoke = function changeSelectValue_invoke() { + getAccessible(aID).takeFocus(); + synthesizeKey(aKey, {}, window); + }; + + this.finalCheck = function changeSelectValue_finalCheck() { + is(getAccessible(aID).value, aValue, "Wrong value for " + prettyName(aID)); + }; + + this.getID = function changeSelectValue_getID() { + return `${prettyName(aID)} closed select value change on '${aKey}'' key press`; + }; + } + + // enableLogging("DOMEvents"); + // gA11yEventDumpToConsole = true; + function doTests() { + + // Test initial values + testValue("slider_vn", "5", 5, 0, 1000, 0); + testValue("slider_vnvt", "plain", 0, 0, 5, 0); + testValue("slider_vt", "hi", 1.5, 0, 3, 0); + testValue("scrollbar", "5", 5, 0, 1000, 0); + testValue("splitter", "5", 5, 0, 1000, 0); + testValue("progress", "22%", 22, 0, 100, 0); + testValue("range", "6", 6, 0, 10, 1); + testValue("range2", "6", 6, 0, 10, 1); + + // Test that elements which should not expose values do not + let separatorVal = getAccessible("separator", [nsIAccessibleValue], null, DONOTFAIL_IF_NO_INTERFACE); + ok(!separatorVal, "value interface is not exposed for separator"); + let separatorAcc = getAccessible("separator"); + ok(!separatorAcc.value, "Value text is not exposed for separator"); + + // Test value change events + gQueue = new eventQueue(); + + gQueue.push(new changeARIAValue("slider_vn", "6", undefined)); + gQueue.push(new changeARIAValue("slider_vt", undefined, "hey!")); + gQueue.push(new changeARIAValue("slider_vnvt", "3", "sweet")); + gQueue.push(new changeARIAValue("scrollbar", "6", undefined)); + gQueue.push(new changeARIAValue("splitter", "6", undefined)); + + gQueue.push(new changeValue("combobox", "hello")); + + gQueue.push(new changeProgressValue("progress", "50")); + gQueue.push(new changeRangeValueWithMouse("range")); + gQueue.push(new changeRangeValueWithA11yAPI("range2")); + + gQueue.push(new changeSelectValue("select", "VK_DOWN", "2nd")); + gQueue.push(new changeSelectValue("select", "3", "3rd")); + + let iframeSelect = getAccessible("selectIframe").firstChild.firstChild; + gQueue.push(new changeSelectValue(iframeSelect, "VK_DOWN", "2")); + + let shadowSelect = getAccessible("selectShadow").firstChild; + gQueue.push(new changeSelectValue(shadowSelect, "VK_DOWN", "2")); + gQueue.push(new changeValue("number", "2")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=478032" + title=" Fire delayed value changed event for aria-valuetext changes"> + Mozilla Bug 478032 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=529289" + title="We dont expose new aria role 'scrollbar' and property aria-orientation"> + Mozilla Bug 529289 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764" + title="Make HTML5 input@type=range element accessible"> + Mozilla Bug 559764 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=703202" + title="ARIA comboboxes don't fire value change events"> + Mozilla Bug 703202 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=761901" + title=" HTML5 progress accessible should fire value change event"> + Mozilla Bug 761901 + </a> + + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <!-- ARIA sliders --> + <div id="slider_vn" role="slider" aria-valuenow="5" + aria-valuemin="0" aria-valuemax="1000">slider</div> + + <div id="slider_vt" role="slider" aria-valuetext="hi" + aria-valuemin="0" aria-valuemax="3">greeting slider</div> + + <div id="slider_vnvt" role="slider" aria-valuenow="0" aria-valuetext="plain" + aria-valuemin="0" aria-valuemax="5">sweetness slider</div> + + <!-- ARIA scrollbar --> + <div id="scrollbar" role="scrollbar" aria-valuenow="5" + aria-valuemin="0" aria-valuemax="1000">slider</div> + + <!-- ARIA separator which is focusable (i.e. a splitter) --> + <div id="splitter" role="separator" tabindex="0" aria-valuenow="5" + aria-valuemin="0" aria-valuemax="1000">splitter</div> + + <!-- ARIA separator which is not focusable and should not expose values --> + <div id="separator" role="separator" aria-valuenow="5" + aria-valuemin="0" aria-valuemax="1000">splitter</div> + + <!-- ARIA combobox --> + <input id="combobox" role="combobox" aria-autocomplete="inline"> + + <!-- progress bar --> + <progress id="progress" value="22" max="100"></progress> + + <!-- input@type="range" --> + <input type="range" id="range" min="0" max="10" value="6"> + + <!-- input@type="range" --> + <input type="range" id="range2" min="0" max="10" value="6"> + + <select id="select"> + <option>1st</option> + <option>2nd</option> + <option>3rd</option> + </select> + + <iframe id="selectIframe" + src="data:text/html,<select id='iframeSelect'><option>1</option><option>2</option></select>"> + </iframe> + + <div id="selectShadow"></div> + <script> + let host = document.getElementById("selectShadow"); + let shadow = host.attachShadow({mode: "open"}); + let select = document.createElement("select"); + select.id = "shadowSelect"; + let option = document.createElement("option"); + option.textContent = "1"; + select.appendChild(option); + option = document.createElement("option"); + option.textContent = "2"; + select.appendChild(option); + shadow.appendChild(select); + </script> + + <input type="number" id="number" value="1"> +</body> +</html> diff --git a/accessible/tests/mochitest/focus/a11y.ini b/accessible/tests/mochitest/focus/a11y.ini new file mode 100644 index 0000000000..905d38882c --- /dev/null +++ b/accessible/tests/mochitest/focus/a11y.ini @@ -0,0 +1,9 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_focus_radio.xhtml] +[test_focusedChild.html] +skip-if = (os == 'win' && os_version != '6.1') # bug 845134 +[test_takeFocus.html] +[test_takeFocus.xhtml] diff --git a/accessible/tests/mochitest/focus/test_focus_radio.xhtml b/accessible/tests/mochitest/focus/test_focus_radio.xhtml new file mode 100644 index 0000000000..717d9976b6 --- /dev/null +++ b/accessible/tests/mochitest/focus/test_focus_radio.xhtml @@ -0,0 +1,84 @@ +<?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="Tests for Accessible TakeFocus on Radio Elements"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../promisified-events.js" /> + + <script type="application/javascript"> + <![CDATA[ + async function doTests() { + let radio1 = getAccessible("radio1"); + let focused = waitForEvent(EVENT_FOCUS, radio1); + radio1.takeFocus(); + await focused; + // radio1 wasn't selected. Ensure that takeFocus didn't change that. + testStates(radio1, STATE_FOCUSED, 0, STATE_CHECKED); + + // Test focusing another radio in the group while the group is still + // focused. + let radio2 = getAccessible("radio2"); + focused = waitForEvent(EVENT_FOCUS, radio2); + radio2.takeFocus(); + await focused; + testStates(radio2, STATE_FOCUSED | STATE_CHECKED); + + let groupEl = document.getElementById("radiogroup"); + // Selecting an item also focuses it. + focused = waitForEvent(EVENT_FOCUS, radio1); + groupEl.value = "1"; + await focused; + testStates(radio1, STATE_FOCUSED | STATE_CHECKED); + + // If an item is already selected but not focused, selecting it again + // focuses it. + focused = waitForEvent(EVENT_FOCUS, radio2); + radio2.takeFocus(); + await focused; + testStates(radio2, STATE_FOCUSED, 0, STATE_CHECKED); + // radio1 is selected but not focused. + // Select radio1 again, which should focus it. + focused = waitForEvent(EVENT_FOCUS, radio1); + groupEl.value = "1"; + await focused; + testStates(radio1, STATE_FOCUSED | STATE_CHECKED); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <radiogroup id="radiogroup" value="2"> + <radio id="radio1" value="1"/> + <radio id="radio2" value="2"/> + </radiogroup> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/focus/test_focusedChild.html b/accessible/tests/mochitest/focus/test_focusedChild.html new file mode 100644 index 0000000000..d12e229b48 --- /dev/null +++ b/accessible/tests/mochitest/focus/test_focusedChild.html @@ -0,0 +1,81 @@ +<html> + +<head> + <title>nsIAccessible::focusedChild testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function openWnd() { + this.eventSeq = [ new invokerChecker(EVENT_FOCUS, + getDialogAccessible, + this) ]; + + this.invoke = function openWnd_invoke() { + this.dialog = window.browsingContext.topChromeWindow + .openDialog("about:mozilla", + "AboutMozilla", + "chrome,width=600,height=600"); + }; + + this.finalCheck = function openWnd_finalCheck() { + var app = getApplicationAccessible(); + is(app.focusedChild, getDialogAccessible(this), + "Wrong focused child"); + + this.dialog.close(); + }; + + this.getID = function openWnd_getID() { + return "focusedChild for application accessible"; + }; + + function getDialogAccessible(aInvoker) { + return getAccessible(aInvoker.dialog.document); + } + } + + gA11yEventDumpToConsole = true; + var gQueue = null; + + function doTest() { + enableLogging("focus,doclifecycle"); + gQueue = new eventQueue(); + + gQueue.push(new openWnd()); + + gQueue.onFinish = function() { disableLogging(); }; + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=677467" + title="focusedChild crashes on application accessible"> + Mozilla Bug 677467 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + +</body> +</html> diff --git a/accessible/tests/mochitest/focus/test_takeFocus.html b/accessible/tests/mochitest/focus/test_takeFocus.html new file mode 100644 index 0000000000..752ed66b36 --- /dev/null +++ b/accessible/tests/mochitest/focus/test_takeFocus.html @@ -0,0 +1,109 @@ +<html> + +<head> + <title>nsIAccessible::takeFocus testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function takeFocusInvoker(aID) { + this.accessible = getAccessible(aID); + + this.eventSeq = [ new focusChecker(this.accessible) ]; + + this.invoke = function takeFocusInvoker_invoke() { + this.accessible.takeFocus(); + }; + + this.getID = function takeFocusInvoker_getID() { + return "takeFocus for " + prettyName(aID); + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTest() { + disableLogging(); // from test_focusedChild + gQueue = new eventQueue(); + + gQueue.push(new takeFocusInvoker("aria-link")); + gQueue.push(new takeFocusInvoker("aria-link2")); + gQueue.push(new takeFocusInvoker("link")); + gQueue.push(new takeFocusInvoker("item2")); + gQueue.push(new takeFocusInvoker(document)); + gQueue.push(new takeFocusInvoker("lb_item2")); + gQueue.push(new takeFocusInvoker(document)); + gQueue.push(new takeFocusInvoker("lb_item3.2")); + gQueue.push(new takeFocusInvoker(document)); + gQueue.push(new takeFocusInvoker("lb_item3.1")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=429547" + title="Support aria-activedescendant usage in nsIAccesible::TakeFocus()"> + Mozilla Bug 429547 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=452710" + title="nsIAccessible::takeFocus testing"> + Mozilla Bug 452710 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=706067" + title="Make takeFocus work on widget items"> + Mozilla Bug 706067 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <span id="aria-link" role="link" tabindex="0">link</span> + <span id="aria-link2" role="link" tabindex="0">link</span> + + <a id="link" href="">link</a> + + <div role="listbox" aria-activedescendant="item1" id="container" tabindex="1"> + <div role="option" id="item1">item1</div> + <div role="option" id="item2">item2</div> + <div role="option" id="item3">item3</div> + </div> + + <select id="listbox" size="5"> + <option id="lb_item1">item1</option> + <option id="lb_item2">item2</option> + <optgroup> + <option id="lb_item3.1">item 3.1</option> + <option id="lb_item3.2">item 3.2</option> + </optgroup> + </select> +</body> +</html> diff --git a/accessible/tests/mochitest/focus/test_takeFocus.xhtml b/accessible/tests/mochitest/focus/test_takeFocus.xhtml new file mode 100644 index 0000000000..127f47d067 --- /dev/null +++ b/accessible/tests/mochitest/focus/test_takeFocus.xhtml @@ -0,0 +1,104 @@ +<?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="Accessible focus testing"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function takeFocusInvoker(aID, aArgConverterFunc) + { + this.targetFunc = aArgConverterFunc ? aArgConverterFunc : getAccessible; + + this.eventSeq = [ new focusChecker(this.targetFunc, aID) ]; + + this.invoke = function takeFocusInvoker_invoke() + { + this.targetFunc.call(null, aID).takeFocus(); + } + + this.getID = function takeFocusInvoker_getID() + { + return "takeFocus for " + prettyName(aID); + } + } + + function getLastChild(aID) + { + return getAccessible(aID).lastChild; + } + + //////////////////////////////////////////////////////////////////////////// + // Tests + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTests() + { + // Test focus events. + gQueue = new eventQueue(); + + gQueue.push(new takeFocusInvoker("tree", getLastChild)); + gQueue.push(new takeFocusInvoker("listitem2")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yXULTreeLoadEvent(doTests, "tree", new nsTableTreeView(5)); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=706067" + title="Make takeFocus work on widget items"> + Mozilla Bug 706067 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col1" flex="1" primary="true" label="column"/> + <treecol id="col2" flex="1" label="column 2"/> + </treecols> + <treechildren id="treechildren"/> + </tree> + + <richlistbox id="listbox"> + <richlistitem id="listitem1"><label value="item1"/></richlistitem> + <richlistitem id="listitem2"><label value="item2"/></richlistitem> + </richlistbox> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/formimage.png b/accessible/tests/mochitest/formimage.png Binary files differnew file mode 100644 index 0000000000..10e44bf920 --- /dev/null +++ b/accessible/tests/mochitest/formimage.png diff --git a/accessible/tests/mochitest/grid.js b/accessible/tests/mochitest/grid.js new file mode 100644 index 0000000000..4a2e01ee9b --- /dev/null +++ b/accessible/tests/mochitest/grid.js @@ -0,0 +1,142 @@ +/* import-globals-from common.js */ + +/** + * Create grid object based on HTML table. + */ +function grid(aTableIdentifier) { + this.getRowCount = function getRowCount() { + return this.table.rows.length - (this.table.tHead ? 1 : 0); + }; + this.getColsCount = function getColsCount() { + return this.table.rows[0].cells.length; + }; + + this.getRowAtIndex = function getRowAtIndex(aIndex) { + return this.table.rows[this.table.tHead ? aIndex + 1 : aIndex]; + }; + + this.getMaxIndex = function getMaxIndex() { + return this.getRowCount() * this.getColsCount() - 1; + }; + + this.getCellAtIndex = function getCellAtIndex(aIndex) { + var colsCount = this.getColsCount(); + + var rowIdx = Math.floor(aIndex / colsCount); + var colIdx = aIndex % colsCount; + + var row = this.getRowAtIndex(rowIdx); + return row.cells[colIdx]; + }; + + this.getIndexByCell = function getIndexByCell(aCell) { + var colIdx = aCell.cellIndex; + + var rowIdx = aCell.parentNode.rowIndex; + if (this.table.tHead) { + rowIdx -= 1; + } + + var colsCount = this.getColsCount(); + return rowIdx * colsCount + colIdx; + }; + + this.getCurrentCell = function getCurrentCell() { + var rowCount = this.table.rows.length; + var colsCount = this.getColsCount(); + for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (var colIdx = 0; colIdx < colsCount; colIdx++) { + var cell = this.table.rows[rowIdx].cells[colIdx]; + if (cell.hasAttribute("tabindex")) { + return cell; + } + } + } + return null; + }; + + this.initGrid = function initGrid() { + this.table.addEventListener("keypress", this); + this.table.addEventListener("click", this); + }; + + this.handleEvent = function handleEvent(aEvent) { + if (aEvent instanceof KeyboardEvent) { + this.handleKeyEvent(aEvent); + } else { + this.handleClickEvent(aEvent); + } + }; + + this.handleKeyEvent = function handleKeyEvent(aEvent) { + if (aEvent.target.localName != "td") { + return; + } + + var cell = aEvent.target; + switch (aEvent.keyCode) { + case KeyboardEvent.DOM_VK_UP: { + let colsCount = this.getColsCount(); + let idx = this.getIndexByCell(cell); + var upidx = idx - colsCount; + if (upidx >= 0) { + cell.removeAttribute("tabindex"); + var upcell = this.getCellAtIndex(upidx); + upcell.setAttribute("tabindex", "0"); + upcell.focus(); + } + break; + } + case KeyboardEvent.DOM_VK_DOWN: { + let colsCount = this.getColsCount(); + let idx = this.getIndexByCell(cell); + var downidx = idx + colsCount; + if (downidx <= this.getMaxIndex()) { + cell.removeAttribute("tabindex"); + var downcell = this.getCellAtIndex(downidx); + downcell.setAttribute("tabindex", "0"); + downcell.focus(); + } + break; + } + case KeyboardEvent.DOM_VK_LEFT: { + let idx = this.getIndexByCell(cell); + if (idx > 0) { + cell.removeAttribute("tabindex"); + var prevcell = this.getCellAtIndex(idx - 1); + prevcell.setAttribute("tabindex", "0"); + prevcell.focus(); + } + break; + } + case KeyboardEvent.DOM_VK_RIGHT: { + let idx = this.getIndexByCell(cell); + if (idx < this.getMaxIndex()) { + cell.removeAttribute("tabindex"); + var nextcell = this.getCellAtIndex(idx + 1); + nextcell.setAttribute("tabindex", "0"); + nextcell.focus(); + } + break; + } + } + }; + + this.handleClickEvent = function handleClickEvent(aEvent) { + if (aEvent.target.localName != "td") { + return; + } + + var curCell = this.getCurrentCell(); + var cell = aEvent.target; + + if (cell != curCell) { + curCell.removeAttribute("tabindex"); + cell.setAttribute("tabindex", "0"); + cell.focus(); + } + }; + + this.table = getNode(aTableIdentifier); + this.initGrid(); +} diff --git a/accessible/tests/mochitest/hittest/a11y.ini b/accessible/tests/mochitest/hittest/a11y.ini new file mode 100644 index 0000000000..6dc059bd48 --- /dev/null +++ b/accessible/tests/mochitest/hittest/a11y.ini @@ -0,0 +1,13 @@ +[DEFAULT] +support-files = zoom_tree.xhtml + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + +[test_browser.html] +[test_general.html] +[test_menu.xhtml] +[test_shadowroot.html] +support-files = test_shadowroot_subframe.html +[test_zoom.html] +[test_zoom_text.html] +[test_zoom_tree.xhtml] diff --git a/accessible/tests/mochitest/hittest/test_browser.html b/accessible/tests/mochitest/hittest/test_browser.html new file mode 100644 index 0000000000..c14df7d736 --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_browser.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<html> +<head> + <title>nsIAccessible::childAtPoint() from browser tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + + <script type="application/javascript"> + function doTest() { + // Hit testing. See bug #726097 + getNode("hittest").scrollIntoView(true); + + var hititem = getAccessible("hititem"); + var hittest = getAccessible("hittest"); + + var [hitX, hitY, hitWidth, hitHeight] = getBounds(hititem); + var tgtX = hitX + hitWidth / 2; + var tgtY = hitY + hitHeight / 2; + + var rootAcc = getRootAccessible(); + var docAcc = getAccessible(document); + var outerDocAcc = docAcc.parent; + + var hitAcc = rootAcc.getDeepestChildAtPoint(tgtX, tgtY); + is(hitAcc, hititem, "Hit match at " + tgtX + "," + tgtY + + ". Found: " + prettyName(hitAcc)); + var hitAcc2 = docAcc.getDeepestChildAtPoint(tgtX, tgtY); + is(hitAcc, hitAcc2, "Hit match at " + tgtX + "," + tgtY + + ". Found: " + prettyName(hitAcc2)); + + hitAcc = outerDocAcc.getChildAtPoint(tgtX, tgtY); + is(hitAcc, docAcc, "Hit match at " + tgtX + "," + tgtY + + ". Found: " + prettyName(hitAcc)); + hitAcc = docAcc.getChildAtPoint(tgtX, tgtY); + is(hitAcc, hittest, "Hit match at " + tgtX + "," + tgtY + + ". Found: " + prettyName(hitAcc)); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=726097" + title="nsIAccessible::childAtPoint() from browser tests">Mozilla Bug 726097</a> + + <div id="hittest"> + <div id="hititem"><span role="image">img</span>item</div> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/hittest/test_general.html b/accessible/tests/mochitest/hittest/test_general.html new file mode 100644 index 0000000000..f5afd18446 --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_general.html @@ -0,0 +1,110 @@ +<!DOCTYPE html> +<html> +<head> + <title>nsIAccessible::childAtPoint() tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function doPreTest() { + waitForImageMap("imgmap", doTest); + } + + function doTest() { + // Not specific case, child and deepchild testing. + var list = getAccessible("list"); + var listitem = getAccessible("listitem"); + var image = getAccessible("image"); +if (!MAC) { + testChildAtPoint(list, 1, 1, listitem, image.firstChild); +} else { + todo(false, "Bug 746974 - children must match on all platforms, disable failing test on Mac"); +} + + // ::MustPrune case (in this case childAtPoint doesn't look inside a + // textbox), point is inside of textbox. + var txt = getAccessible("txt"); + testChildAtPoint(txt, 1, 1, txt, txt); + + // ::MustPrune case, point is outside of textbox accessible but is in + // document. + testChildAtPoint(txt, -1, 1, null, null); + + // ::MustPrune case, point is outside of root accessible. + testChildAtPoint(txt, -10000, 10000, null, null); + + // Not specific case, point is inside of btn accessible. + var btn = getAccessible("btn"); + testChildAtPoint(btn, 1, 1, btn, btn); + + // Not specific case, point is outside of btn accessible. + testChildAtPoint(btn, -1, 1, null, null); + + // Out of flow accessible testing, do not return out of flow accessible + // because it's not a child of the accessible even visually it is. + var rectArea = getNode("area").getBoundingClientRect(); + var outOfFlow = getNode("outofflow"); + outOfFlow.style.left = rectArea.left + "px"; + outOfFlow.style.top = rectArea.top + "px"; + + testChildAtPoint("area", 1, 1, "area", "area"); + + // Test image maps. Their children are not in the layout tree. + var theLetterA = getAccessible("imgmap").firstChild; + hitTest("imgmap", theLetterA, theLetterA); + hitTest("container", "imgmap", theLetterA); + + // hit testing for element contained by zero-width element + hitTest("container2", "container2_input", "container2_input"); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doPreTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=491657" + title="nsIAccessible::childAtPoint() tests">Mozilla Bug 491657</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div role="list" id="list"> + <div role="listitem" id="listitem"><span role="image" id="image">img</span>item</div> + </div> + + <span role="button">button1</span><span role="button" id="btn">button2</span> + + <span role="textbox">textbox1</span><span role="textbox" id="txt">textbox2</span> + + <div id="outofflow" style="width: 10px; height: 10px; position: absolute; left: 0px; top: 0px; background-color: yellow;"> + </div> + <div id="area" style="width: 100px; height: 100px; background-color: blue;"></div> + + <map name="atoz_map"> + <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a" + coords="0,0,15,15" alt="thelettera" shape="rect"/> + </map> + + <div id="container"> + <img id="imgmap" width="447" height="15" usemap="#atoz_map" src="../letters.gif"/> + </div> + + <div id="container2" style="width: 0px"> + <input id="container2_input"> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/hittest/test_menu.xhtml b/accessible/tests/mochitest/hittest/test_menu.xhtml new file mode 100644 index 0000000000..d80b31305d --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_menu.xhtml @@ -0,0 +1,133 @@ +<?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="Hit testing for XUL menus"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../layout.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + function openMenu(aMenuID, aMenuPopupID, aMenuItemID) + { + this.menuNode = getNode(aMenuID); + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, this.menuNode) + ]; + + this.invoke = function openMenu_invoke() + { + this.menuNode.open = true; + } + + this.finalCheck = function openMenu_finalCheck() + { + hitTest(aMenuPopupID, aMenuItemID, aMenuItemID); + } + + this.getID = function openMenu_invoke() + { + return "open menu '" + aMenuID + "' and do hit testing"; + } + } + + function closeMenu(aID, aSubID, aSub2ID) + { + this.menuNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, document) + ]; + + this.invoke = function openMenu_invoke() + { + this.menuNode.open = false; + } + + this.finalCheck = function openMenu_finalCheck() + { + testStates(aID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN); + testStates(aSubID, STATE_INVISIBLE, 0, STATE_OFFSCREEN); + testStates(aSub2ID, STATE_INVISIBLE, 0, STATE_OFFSCREEN); + } + + this.getID = function openMenu_invoke() + { + return "open menu and test states"; + } + } + + var gQueue = null; + function doTest() + { + if (LINUX) { + ok(true, "No tests is running on Linux"); + SimpleTest.finish(); + return; + } + + getNode("mi_file1").scrollIntoView(true); + + gQueue = new eventQueue(); + gQueue.push(new openMenu("mi_file1", "mp_file1", "mi_file1.1")); + gQueue.push(new openMenu("mi_file1.2", "mp_file1.2", "mi_file1.2.1")); + gQueue.push(new closeMenu("mi_file1", "mi_file1.1", "mi_file1.2.1")); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=670087" + title="AccessibleObjectFromPoint returns incorrect accessible for popup menus"> + Bug 670087 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <menubar> + <menu label="File" id="mi_file1"> + <menupopup id="mp_file1"> + <menuitem label="SubFile" id="mi_file1.1"/> + <menu label="SubFile2" id="mi_file1.2"> + <menupopup style="max-height: 5em;" id="mp_file1.2"> + <menuitem label="SubSubFile" id="mi_file1.2.1"/> + <menuitem label="SubSubFile2" id="mi_file1.2.2"/> + <menuitem label="SubSubFile3" id="mi_file1.2.3"/> + <menuitem label="SubSubFile4" id="mi_file1.2.4"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menubar> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/hittest/test_shadowroot.html b/accessible/tests/mochitest/hittest/test_shadowroot.html new file mode 100644 index 0000000000..6acdc47987 --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_shadowroot.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<head> + <title>ShadowRoot hit tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +</head> +<body> + + <a target="_blank" + title="Test getChildAtPoint works for shadow DOM content" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1027315"> + Mozilla Bug 1027315 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <script> + SimpleTest.waitForExplicitFinish(); + + window.onload = () => { + var iframe = document.createElement("iframe"); + iframe.src = "test_shadowroot_subframe.html"; + document.body.appendChild(iframe); + }; + + </script> + +</body> +</html> diff --git a/accessible/tests/mochitest/hittest/test_shadowroot_subframe.html b/accessible/tests/mochitest/hittest/test_shadowroot_subframe.html new file mode 100644 index 0000000000..7a365e7b93 --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_shadowroot_subframe.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>ShadowRoot hit tests</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="application/javascript" src="../common.js"></script> + <script type="application/javascript" src="../layout.js"></script> + + <script type="application/javascript"> + let SimpleTest = window.parent.SimpleTest; + let ok = window.parent.ok; + let is = window.parent.is; + + function doTest() { + var componentAcc = getAccessible("component1"); + testChildAtPoint(componentAcc, 1, 1, componentAcc.firstChild, + componentAcc.firstChild); + + componentAcc = getAccessible("component2"); + testChildAtPoint(componentAcc, 1, 1, componentAcc.firstChild, + componentAcc.firstChild); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> +<body> + <div role="group" class="components" id="component1" style="display: inline-block;"> + <!-- + <div role="button" id="component-child" + style="width: 100px; height: 100px; background-color: pink;"> + </div> + --> + </div> + <div role="group" class="components" id="component2" style="display: inline-block;"> + <!-- + <button>Hello world</button> + --> + </div> + <script> + // This routine adds the comment children of each 'component' to its + // shadow root. + var components = document.querySelectorAll(".components"); + for (var i = 0; i < components.length; i++) { + var component = components[i]; + var shadow = component.attachShadow({mode: "open"}); + for (var child = component.firstChild; child; child = child.nextSibling) { + if (child.nodeType === 8) + // eslint-disable-next-line no-unsanitized/property + shadow.innerHTML = child.data; + } + } + </script> +</body> +</html> diff --git a/accessible/tests/mochitest/hittest/test_zoom.html b/accessible/tests/mochitest/hittest/test_zoom.html new file mode 100644 index 0000000000..70e71f7a6d --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_zoom.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html> +<head> + <title>childAtPoint when page is zoomed</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + <script type="application/javascript" + src="../browser.js"></script> + + <script type="application/javascript"> + function doTest() { +if (!MAC) { + var tabDocument = currentTabDocument(); + var p1 = tabDocument.body.firstElementChild; + var p2 = tabDocument.body.lastElementChild; + + hitTest(tabDocument, p1, p1.firstChild); + hitTest(tabDocument, p2, p2.firstChild); + + zoomDocument(tabDocument, 2.0); + + hitTest(tabDocument, p1, p1.firstChild); + hitTest(tabDocument, p2, p2.firstChild); + + closeBrowserWindow(); +} else { + todo(false, "Bug 746974 - deepest child must be correct on all platforms, disabling on Mac!"); +} + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTest, + "data:text/html,<html><body><p>para 1</p><p>para 2</p></body></html>", + { left: 100, top: 100 }); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942" + title="childAtPoint may return incorrect accessibles when page zoomed"> + Mozilla Bug 727942 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/hittest/test_zoom_text.html b/accessible/tests/mochitest/hittest/test_zoom_text.html new file mode 100644 index 0000000000..4dc92b9639 --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_zoom_text.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html> +<head> + <title>getOffsetAtPoint when page is zoomed</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + + <script type="application/javascript"> + function doTest() { + var hyperText = getNode("paragraph"); + var textNode = hyperText.firstChild; + let [x, y, width, height] = getBounds(textNode); + testOffsetAtPoint(hyperText, x + width / 2, y + height / 2, + COORDTYPE_SCREEN_RELATIVE, + hyperText.textContent.length / 2); + + zoomDocument(document, 2.0); + + document.body.offsetTop; // getBounds doesn't flush layout on its own, looks like. + + [x, y, width, height] = getBounds(textNode); + testOffsetAtPoint(hyperText, x + width / 2, y + height / 2, + COORDTYPE_SCREEN_RELATIVE, + hyperText.textContent.length / 2); + + zoomDocument(document, 1.0); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942" + title="getOffsetAtPoint returns incorrect value when page is zoomed"> + Mozilla Bug 727942 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <p id="paragraph" style="font-family: monospace;">Болтали две сороки</p> +</body> +</html> diff --git a/accessible/tests/mochitest/hittest/test_zoom_tree.xhtml b/accessible/tests/mochitest/hittest/test_zoom_tree.xhtml new file mode 100644 index 0000000000..54cb37c871 --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_zoom_tree.xhtml @@ -0,0 +1,97 @@ +<?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="nsIAccessible::getChildAtPoint and getDeepestChildAtPoint"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/chrome-harness.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../layout.js" /> + <script type="application/javascript" + src="../browser.js" /> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + var tabDocument = currentTabDocument(); + var tabWindow = currentTabWindow(); + + var tree = tabDocument.getElementById("tree"); + var treecols = tabDocument.getElementById("treecols"); + var treecol1 = tabDocument.getElementById("treecol1"); + + // tree columns + hitTest(tree, treecols, treecol1); + + // tree rows and cells + var treeRect = tree.treeBody.getBoundingClientRect(); + var rect = tree.getCoordsForCellItem(1, tree.columns[0], "cell"); + + var treeAcc = getAccessible(tree, [nsIAccessibleTable]); + var cellAcc = treeAcc.getCellAt(1, 0); + var rowAcc = cellAcc.parent; + + var cssX = rect.x + treeRect.x; + var cssY = rect.y + treeRect.y; + var [x, y] = CSSToDevicePixels(tabWindow, cssX, cssY); + + testChildAtPoint(treeAcc, x, y, rowAcc, cellAcc); + testChildAtPoint(rowAcc, x, y, cellAcc, cellAcc); + + // do zoom + zoomDocument(tabDocument, 1.5); + + // tree columns + hitTest(tree, treecols, treecol1); + + // tree rows and cells + [x, y] = CSSToDevicePixels(tabWindow, cssX, cssY); + testChildAtPoint(treeAcc, x, y, rowAcc, cellAcc); + testChildAtPoint(rowAcc, x, y, cellAcc, cellAcc); + + closeBrowserWindow(); + SimpleTest.finish(); + } + + function prepareTest() + { + var tabDocument = currentTabDocument(); + var tree = tabDocument.getElementById("tree"); + loadXULTreeAndDoTest(doTest, tree, new nsTableTreeView(5)); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(prepareTest, + getRootDirectory(window.location.href) + "zoom_tree.xhtml", + { left: 100, top: 100 }); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=471493" + title=" crash [@ nsPropertyTable::GetPropertyInternal]"> + Mozilla Bug 471493 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/hittest/zoom_tree.xhtml b/accessible/tests/mochitest/hittest/zoom_tree.xhtml new file mode 100644 index 0000000000..52ec0932ab --- /dev/null +++ b/accessible/tests/mochitest/hittest/zoom_tree.xhtml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="nsIAccessible::getChildAtPoint and getDeepestChildAtPoint for XUL trees"> + + <tree id="tree" flex="1"> + <treecols id="treecols"> + <treecol id="treecol1" flex="1" primary="true" label="column"/> + <treecol id="treecol2" flex="1" label="column 2"/> + </treecols> + <treechildren id="treechildren"/> + </tree> + +</window> + diff --git a/accessible/tests/mochitest/hyperlink/a11y.ini b/accessible/tests/mochitest/hyperlink/a11y.ini new file mode 100644 index 0000000000..60804a70c8 --- /dev/null +++ b/accessible/tests/mochitest/hyperlink/a11y.ini @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = hyperlink.js + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + +[test_general.html] +[test_general.xhtml] diff --git a/accessible/tests/mochitest/hyperlink/hyperlink.js b/accessible/tests/mochitest/hyperlink/hyperlink.js new file mode 100644 index 0000000000..93caa9090c --- /dev/null +++ b/accessible/tests/mochitest/hyperlink/hyperlink.js @@ -0,0 +1,46 @@ +/* import-globals-from ../common.js */ +/* import-globals-from ../events.js */ +/* import-globals-from ../states.js */ + +/** + * Focus hyperlink invoker. + * + * @param aID [in] hyperlink identifier + * @param aSelectedAfter [in] specifies if hyperlink is selected/focused after + * the focus + */ +function focusLink(aID, aSelectedAfter) { + this.node = getNode(aID); + this.accessible = getAccessible(this.node); + + this.eventSeq = []; + this.unexpectedEventSeq = []; + + var checker = new invokerChecker(EVENT_FOCUS, this.accessible); + if (aSelectedAfter) { + this.eventSeq.push(checker); + } else { + this.unexpectedEventSeq.push(checker); + } + + this.invoke = function focusLink_invoke() { + var expectedStates = aSelectedAfter ? STATE_FOCUSABLE : 0; + var unexpectedStates = + (!aSelectedAfter ? STATE_FOCUSABLE : 0) | STATE_FOCUSED; + testStates(aID, expectedStates, 0, unexpectedStates, 0); + + this.node.focus(); + }; + + this.finalCheck = function focusLink_finalCheck() { + var expectedStates = aSelectedAfter ? STATE_FOCUSABLE | STATE_FOCUSED : 0; + var unexpectedStates = !aSelectedAfter + ? STATE_FOCUSABLE | STATE_FOCUSED + : 0; + testStates(aID, expectedStates, 0, unexpectedStates, 0); + }; + + this.getID = function focusLink_getID() { + return "focus hyperlink " + prettyName(aID); + }; +} diff --git a/accessible/tests/mochitest/hyperlink/test_general.html b/accessible/tests/mochitest/hyperlink/test_general.html new file mode 100644 index 0000000000..c652f1d962 --- /dev/null +++ b/accessible/tests/mochitest/hyperlink/test_general.html @@ -0,0 +1,279 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=418368 +--> +<head> + <title>nsIHyperLinkAccessible chrome tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript" + src="hyperlink.js"></script> + + <script type="application/javascript"> + function testThis(aID, aAcc, aRole, aAnchors, aName, aValid, aStartIndex, + aEndIndex) { + testRole(aAcc, aRole); + is(aAcc.anchorCount, aAnchors, "Wrong number of anchors for ID " + + aID + "!"); + is(aAcc.getAnchor(0).name, aName, "Wrong name for ID " + + aID + "!"); + is(aAcc.valid, aValid, "No correct valid state for ID " + + aID + "!"); + is(aAcc.startIndex, aStartIndex, "Wrong startIndex value for ID " + + aID + "!"); + is(aAcc.endIndex, aEndIndex, "Wrong endIndex value for ID " + + aID + "!"); + } + + function testAction(aId, aAcc, aActionName) { + var actionCount = aActionName ? 1 : 0; + is(aAcc.actionCount, actionCount, + "Wrong actions number for ID " + aId); + try { + is(aAcc.getActionName(0), aActionName, + "Wrong action name for ID " + aId); + } catch (e) { + if (actionCount) + ok(false, "Exception on action name getting for ID " + aId); + else + ok(true, "Correct action name for ID " + aId); + } + } + + // gA11yEventDumpToConsole = true; // debug stuff + function doPreTest() { + waitForImageMap("imgmap", doTest); + } + + var gQueue = null; + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // normal hyperlink + var normalHyperlinkAcc = getAccessible("NormalHyperlink", + [nsIAccessibleHyperLink]); + testThis("NormalHyperlink", normalHyperlinkAcc, ROLE_LINK, 1, + "Mozilla Foundation", true, 17, 18); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + is(normalHyperlinkAcc.getURI(0).spec, "http://www.mozilla.org/", + "URI wrong for normalHyperlinkElement!"); + testStates(normalHyperlinkAcc, STATE_LINKED, 0); + + // //////////////////////////////////////////////////////////////////////// + // ARIA hyperlink + var ariaHyperlinkAcc = getAccessible("AriaHyperlink", + [nsIAccessibleHyperLink]); + testThis("AriaHyperlink", ariaHyperlinkAcc, ROLE_LINK, 1, + "Mozilla Foundation Home", true, 30, 31); + testStates(ariaHyperlinkAcc, STATE_LINKED, 0); + testAction("AriaHyperlink", ariaHyperlinkAcc, "click"); + + // //////////////////////////////////////////////////////////////////////// + // ARIA hyperlink with status invalid + var invalidAriaHyperlinkAcc = getAccessible("InvalidAriaHyperlink", + [nsIAccessibleHyperLink]); + is(invalidAriaHyperlinkAcc.valid, false, "Should not be valid!"); + testStates(invalidAriaHyperlinkAcc, STATE_LINKED, 0); + + // //////////////////////////////////////////////////////////////////////// + // image map and its link children + + var imageMapHyperlinkAcc = getAccessible("imgmap", + [nsIAccessibleHyperLink]); + testThis("imgmap", imageMapHyperlinkAcc, ROLE_IMAGE_MAP, 2, "b", true, + 79, 80); + is(imageMapHyperlinkAcc.getURI(0).spec, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.bbc.co.uk/radio4/atoz/index.shtml#b", "URI wrong!"); + is(imageMapHyperlinkAcc.getURI(1).spec, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.bbc.co.uk/radio4/atoz/index.shtml#a", "URI wrong!"); + testStates(imageMapHyperlinkAcc, 0, 0); + + var area1 = getAccessible(imageMapHyperlinkAcc.firstChild, + [nsIAccessibleHyperLink]); + testThis("Area1", area1, ROLE_LINK, 1, "b", true, 0, 1); + is(area1.getURI(0).spec, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.bbc.co.uk/radio4/atoz/index.shtml#b", "URI wrong!"); + testStates(area1, (STATE_LINKED)); + + var area2 = getAccessible(area1.nextSibling, + [nsIAccessibleHyperLink]); + testThis("Area2", area2, ROLE_LINK, 1, "a", true, 1, 2); + is(area2.getURI(0).spec, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.bbc.co.uk/radio4/atoz/index.shtml#a", "URI wrong!"); + testStates(area2, (STATE_LINKED)); + + // //////////////////////////////////////////////////////////////////////// + // empty hyperlink + var EmptyHLAcc = getAccessible("emptyLink", + [nsIAccessibleHyperLink]); + testThis("emptyLink", EmptyHLAcc, ROLE_LINK, 1, null, true, 93, 94); + testStates(EmptyHLAcc, (STATE_FOCUSABLE | STATE_LINKED), 0); + testAction("emptyLink", EmptyHLAcc, "jump"); + + // //////////////////////////////////////////////////////////////////////// + // normal hyperlink with embedded span + var hyperlinkWithSpanAcc = getAccessible("LinkWithSpan", + [nsIAccessibleHyperLink]); + testThis("LinkWithSpan", hyperlinkWithSpanAcc, ROLE_LINK, 1, + "Heise Online", true, 119, 120); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + is(hyperlinkWithSpanAcc.getURI(0).spec, "http://www.heise.de/", + "URI wrong for hyperlinkElementWithSpan!"); + testStates(hyperlinkWithSpanAcc, STATE_LINKED, 0); + testAction("LinkWithSpan", hyperlinkWithSpanAcc, "jump"); + + // //////////////////////////////////////////////////////////////////////// + // Named anchor, should never have state_linked + var namedAnchorAcc = getAccessible("namedAnchor", + [nsIAccessibleHyperLink]); + testThis("namedAnchor", namedAnchorAcc, ROLE_LINK, 1, + "This should never be of state_linked", true, 196, 197); + testStates(namedAnchorAcc, STATE_SELECTABLE, + 0, (STATE_FOCUSABLE | STATE_LINKED)); + testAction("namedAnchor", namedAnchorAcc, ""); + + // //////////////////////////////////////////////////////////////////////// + // No link (hasn't any attribute), should never have state_linked + var noLinkAcc = getAccessible("noLink", + [nsIAccessibleHyperLink]); + testThis("noLink", noLinkAcc, ROLE_LINK, 1, + "This should never be of state_linked", true, 254, 255); + testStates(noLinkAcc, 0, 0, (STATE_FOCUSABLE | STATE_LINKED)); + testAction("noLink", noLinkAcc, ""); + + // //////////////////////////////////////////////////////////////////////// + // Link with registered 'click' event, should have state_linked + var linkWithClickAcc = getAccessible("linkWithClick", + [nsIAccessibleHyperLink]); + testThis("linkWithClick", linkWithClickAcc, ROLE_LINK, 1, + "This should have state_linked", true, 292, 293); + testStates(linkWithClickAcc, STATE_LINKED, 0); + testAction("linkWithClick", linkWithClickAcc, "click"); + + // //////////////////////////////////////////////////////////////////////// + // Maps to group links (bug 431615). + // var linksMapAcc = getAccessible("linksmap"); + + // //////////////////////////////////////////////////////////////////////// + // Link with title attribute, no name from the subtree (bug 438325). + var id = "linkWithTitleNoNameFromSubtree"; + var linkAcc = getAccessible(id, [nsIAccessibleHyperLink]); + testThis(id, linkAcc, ROLE_LINK, 1, "Link with title", true, 344, 345); + testStates(linkAcc, STATE_LINKED, 0); + testAction(id, linkAcc, "jump"); + + // //////////////////////////////////////////////////////////////////////// + // Link with title attribute, name from the subtree - onscreen name + // (bug 438325). + id = "linkWithTitleNameFromSubtree"; + linkAcc = getAccessible(id, [nsIAccessibleHyperLink]); + testThis(id, linkAcc, ROLE_LINK, 1, "the name from subtree", true, 393, + 394); + testStates(linkAcc, STATE_LINKED, 0); + testAction(id, linkAcc, "jump"); + + // //////////////////////////////////////////////////////////////////////// + // Link with title attribute, name from the nested html:img (bug 438325). + id = "linkWithTitleNameFromImg"; + linkAcc = getAccessible(id, [nsIAccessibleHyperLink]); + testThis(id, linkAcc, ROLE_LINK, 1, "The title for link", true, 447, + 448); + testStates(linkAcc, STATE_LINKED, 0); + testAction(id, linkAcc, "jump"); + + // //////////////////////////////////////////////////////////////////////// + // Text accessible shouldn't implement nsIAccessibleHyperLink + var res = isAccessible(getNode("namedAnchor").firstChild, + [nsIAccessibleHyperLink]); + ok(!res, "Text accessible shouldn't implement nsIAccessibleHyperLink"); + + // //////////////////////////////////////////////////////////////////////// + // Test focus + gQueue = new eventQueue(); + + gQueue.push(new focusLink("NormalHyperlink", true)); + gQueue.push(new focusLink("AriaHyperlink", true)); + gQueue.push(new focusLink("InvalidAriaHyperlink", false)); + gQueue.push(new focusLink("LinkWithSpan", true)); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doPreTest); + </script> + +</head> +<body><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=418368">Mozilla Bug 418368</a + ><p id="display"></p + ><div id="content" style="display: none"></div + ><pre id="test"> + </pre + ><br + >Simple link:<br + ><a id="NormalHyperlink" href="http://www.mozilla.org">Mozilla Foundation</a + ><br>ARIA link:<br + ><span id="AriaHyperlink" role="link" + onclick="window.open('http://www.mozilla.org/');" + tabindex="0">Mozilla Foundation Home</span + ><br + >Invalid, non-focusable hyperlink:<br + ><span id="InvalidAriaHyperlink" role="link" aria-invalid="true" + onclick="window.open('http:/www.mozilla.org/');">Invalid link</span + ><br>Image map:<br + ><map name="atoz_map" + ><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b" + coords="17,0,30,14" + alt="b" + shape="rect"></area + ><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a" + coords="0,0,13,14" + alt="a" + shape="rect"></area + ></map + ><img width="447" id="imgmap" + height="15" + usemap="#atoz_map" + src="../letters.gif"><br>Empty link:<br + ><a id="emptyLink" href=""><img src=""></a + ><br>Link with embedded span<br + ><a id="LinkWithSpan" href="http://www.heise.de/"><span lang="de">Heise Online</span></a + ><br>Named anchor, must not have "linked" state for it to be exposed correctly:<br + ><a id="namedAnchor" name="named_anchor">This should never be of state_linked</a + ><br>Link having no attributes, must not have "linked" state:<a id="noLink" + >This should never be of state_linked</a + ><br>Link with registered 'click' event: <a id="linkWithClick" onclick="var clicked = true;" + >This should have state_linked</a + ><br>Link with title attribute (no name from subtree): <a + id="linkWithTitleNoNameFromSubtree" href="http://www.heise.de/" + title="Link with title"><img src=""/></a + ><br>Link with title attribute (name from subtree): <a + id="linkWithTitleNameFromSubtree" href="http://www.heise.de/" + title="Link with title">the name from subtree</a + ><br>Link with title attribute (name from nested image): <a + id="linkWithTitleNameFromImg" href="http://www.heise.de/" + title="Link with title"><img src="" alt="The title for link"/></a + ><br><br>Map that is used to group links (www.w3.org/TR/WCAG10-HTML-TECHS/#group-bypass), also see the bug 431615:<br + ><map id="linksmap" title="Site navigation"><ul + ><li><a href="http://mozilla.org">About the project</a></li + ><li><a href="http://mozilla.org">Sites and sounds</a></li + ></ul + ></map +></body> +</html> diff --git a/accessible/tests/mochitest/hyperlink/test_general.xhtml b/accessible/tests/mochitest/hyperlink/test_general.xhtml new file mode 100644 index 0000000000..b006e58ef4 --- /dev/null +++ b/accessible/tests/mochitest/hyperlink/test_general.xhtml @@ -0,0 +1,98 @@ +<?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="test for nsIAccessibleHyperLink interface on XUL:label elements"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript" + src="hyperlink.js" /> + + <script type="application/javascript"> + <![CDATA[ + function testThis(aID, aAcc, aRole, aAnchorCount, aAnchorName, aURI, + aStartIndex, aEndIndex, aValid) + { + testRole(aID, aRole); + is(aAcc.anchorCount, aAnchorCount, "Wrong number of anchors for ID " + + aID + "!"); + is(aAcc.getAnchor(0).name, aAnchorName, "Wrong name for ID " + aID + "!"); + is(aAcc.getURI(0).spec, aURI, "URI wrong for ID " + aID + "!"); + is(aAcc.startIndex, aStartIndex, "Wrong startIndex value for ID " + aID + + "!"); + is(aAcc.endIndex, aEndIndex, "Wrong endIndex value for ID " + aID + "!"); + is(aAcc.valid, aValid, "Wrong valid state for ID " + aID + "!"); + } + + var gQueue = null; + function doTest() + { + var linkedLabelAcc = getAccessible("linkedLabel", + [nsIAccessibleHyperLink]); + testThis("linkedLabel", linkedLabelAcc, ROLE_LINK, 1, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "Mozilla Foundation home", "http://www.mozilla.org/", 1, 2, + true); + testStates(linkedLabelAcc, STATE_LINKED, 0); + + var labelWithValueAcc = getAccessible("linkLabelWithValue", + [nsIAccessibleHyperLink]); + testThis("linkLabelWithValue", labelWithValueAcc, ROLE_LINK, 1, + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "Mozilla Foundation", "http://www.mozilla.org/", 2, 3, true, + false, true); + testStates(labelWithValueAcc, STATE_LINKED, 0); + + var normalLabelAcc = getAccessible("normalLabel"); + testRole(normalLabelAcc, ROLE_LABEL); + is(normalLabelAcc.name, "This label should not be a link", + "Wrong name for normal label!"); + testStates(normalLabelAcc, 0, 0, (STATE_FOCUSABLE | STATE_LINKED)); + + ////////////////////////////////////////////////////////////////////////// + // Test focus + + gQueue = new eventQueue(); + + gQueue.push(new focusLink("linkedLabel", true)); + gQueue.push(new focusLink("linkLabelWithValue", true)); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=421066" + title="Implement Mochitests for the nsIAccessibleHyperLink interface on XUL:label elements"> + Mozilla Bug 421066 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <label id="linkedLabel" href="http://www.mozilla.org/" is="text-link"> + Mozilla Foundation home</label> + <label id="linkLabelWithValue" value="Mozilla Foundation" is="text-link" + href="http://www.mozilla.org/" /> + <label id="normalLabel" value="This label should not be a link" /> +</window> diff --git a/accessible/tests/mochitest/hypertext/a11y.ini b/accessible/tests/mochitest/hypertext/a11y.ini new file mode 100644 index 0000000000..27f878f743 --- /dev/null +++ b/accessible/tests/mochitest/hypertext/a11y.ini @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + +[test_general.html] +[test_update.html] diff --git a/accessible/tests/mochitest/hypertext/test_general.html b/accessible/tests/mochitest/hypertext/test_general.html new file mode 100644 index 0000000000..a89be54f95 --- /dev/null +++ b/accessible/tests/mochitest/hypertext/test_general.html @@ -0,0 +1,150 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=428248 +--> +<head> + <title>nsIHyper>TextAccessible chrome tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + var gParagraphAcc; + + function testLinkIndexAtOffset(aID, aOffset, aIndex) { + var htAcc = getAccessible(aID, [nsIAccessibleHyperText]); + is(htAcc.getLinkIndexAtOffset(aOffset), aIndex, + "Wrong link index at offset " + aOffset + " for ID " + aID + "!"); + } + + function testThis(aID, aCharIndex, aExpectedLinkIndex, aName) { + testLinkIndexAtOffset(gParagraphAcc, aCharIndex, aExpectedLinkIndex); + + var linkAcc = gParagraphAcc.getLinkAt(aExpectedLinkIndex); + ok(linkAcc, "No accessible for link " + aID + "!"); + + var linkIndex = gParagraphAcc.getLinkIndex(linkAcc); + is(linkIndex, aExpectedLinkIndex, "Wrong link index for " + aID + "!"); + + // Just test the link's name to make sure we get the right one. + is(linkAcc.getAnchor(0).name, aName, "Wrong name for " + aID + "!"); + } + + // gA11yEventDumpToConsole = true; + function doPreTest() { + waitForImageMap("imgmap", doTest); + } + + function doTest() { + // Test link count + gParagraphAcc = getAccessible("testParagraph", [nsIAccessibleHyperText]); + is(gParagraphAcc.linkCount, 7, "Wrong link count for paragraph!"); + + // normal hyperlink + testThis("NormalHyperlink", 14, 0, "Mozilla Foundation"); + + // ARIA hyperlink + testThis("AriaHyperlink", 27, 1, "Mozilla Foundation Home"); + + // ARIA hyperlink with status invalid + testThis("InvalidAriaHyperlink", 63, 2, "Invalid link"); + + // image map, but not its link children. They are not part of hypertext. + testThis("imgmap", 76, 3, "b"); + + // empty hyperlink + testThis("emptyLink", 90, 4, null); + + // normal hyperlink with embedded span + testThis("LinkWithSpan", 116, 5, "Heise Online"); + + // Named anchor + testThis("namedAnchor", 193, 6, "This should never be of state_linked"); + + // Paragraph with link + var p2 = getAccessible("p2", [nsIAccessibleHyperText]); + var link = p2.getLinkAt(0); + is(link, p2.getChildAt(0), "Wrong link for p2"); + is(p2.linkCount, 1, "Wrong link count for p2"); + + // getLinkIndexAtOffset, causes the offsets to be cached; + testLinkIndexAtOffset("p4", 0, 0); // 1st 'mozilla' link + testLinkIndexAtOffset("p4", 1, 1); // 2nd 'mozilla' link + testLinkIndexAtOffset("p4", 2, -1); // ' ' of ' te' text node + testLinkIndexAtOffset("p4", 3, -1); // 't' of ' te' text node + testLinkIndexAtOffset("p4", 5, -1); // 'x' of 'xt ' text node + testLinkIndexAtOffset("p4", 7, -1); // ' ' of 'xt ' text node + testLinkIndexAtOffset("p4", 8, 2); // 3d 'mozilla' link + testLinkIndexAtOffset("p4", 9, 2); // the end, latest link + + // the second pass to make sure link indexes are calculated propertly from + // cached offsets. + testLinkIndexAtOffset("p4", 0, 0); // 1st 'mozilla' link + testLinkIndexAtOffset("p4", 1, 1); // 2nd 'mozilla' link + testLinkIndexAtOffset("p4", 2, -1); // ' ' of ' te' text node + testLinkIndexAtOffset("p4", 3, -1); // 't' of ' te' text node + testLinkIndexAtOffset("p4", 5, -1); // 'x' of 'xt ' text node + testLinkIndexAtOffset("p4", 7, -1); // ' ' of 'xt ' text node + testLinkIndexAtOffset("p4", 8, 2); // 3d 'mozilla' link + testLinkIndexAtOffset("p4", 9, 2); // the end, latest link + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doPreTest); + </script> + +</head> +<body> + + <a target="_blank" + title="Create tests for NSIAccessibleHyperlink interface" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=418368"> + Mozilla Bug 418368 + </a><br> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <p id="testParagraph"><br + >Simple link:<br + ><a id="NormalHyperlink" href="http://www.mozilla.org">Mozilla Foundation</a><br + >ARIA link:<br + ><span id="AriaHyperlink" role="link" + onclick="window.open('http://www.mozilla.org/');" + tabindex="0">Mozilla Foundation Home</span><br + >Invalid, non-focusable hyperlink:<br + ><span id="InvalidAriaHyperlink" role="link" aria-invalid="true" + onclick="window.open('http:/www.mozilla.org/');">Invalid link</span><br + >Image map:<br + ><map name="atoz_map"><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b" + coords="17,0,30,14" + alt="b" + shape="rect"></area + ><area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a" + coords="0,0,13,14" + alt="a" + shape="rect"></area></map + ><img width="447" id="imgmap" + height="15" + usemap="#atoz_map" + src="../letters.gif"></img><br + >Empty link:<br + ><a id="emptyLink" href=""><img src=""></img></a><br + >Link with embedded span<br + ><a id="LinkWithSpan" href="http://www.heise.de/"><span lang="de">Heise Online</span></a><br + >Named anchor, must not have "linked" state for it to be exposed correctly:<br + ><a id="namedAnchor" name="named_anchor">This should never be of state_linked</a> + </p> + <p id="p2"><a href="http://mozilla.org">mozilla.org</a></p> + <p id="p4"><a href="www">mozilla</a><a href="www">mozilla</a><span> te</span><span>xt </span><a href="www">mozilla</a></p> +</body> +</html> diff --git a/accessible/tests/mochitest/hypertext/test_update.html b/accessible/tests/mochitest/hypertext/test_update.html new file mode 100644 index 0000000000..f3407bea64 --- /dev/null +++ b/accessible/tests/mochitest/hypertext/test_update.html @@ -0,0 +1,214 @@ +<!DOCTYPE html> +<html> +<head> + <title>nsIHyper>TextAccessible in dynamic tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + const kLinksCount = 128; + function addLinks(aContainerID) { + this.containerNode = getNode(aContainerID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function addLinks_invoke() { + for (var jdx = 0; jdx < kLinksCount; jdx++) { + var a = document.createElement("a"); + a.setAttribute("href", "mozilla.org"); + a.textContent = "mozilla"; + this.containerNode.appendChild(a); + + var span = document.createElement("span"); + span.textContent = " text "; + this.containerNode.appendChild(span); + } + }; + + this.finalCheck = function addLinks_finalCheck() { + // getLinkAt and getLinkIndex. + var htAcc = getAccessible(this.containerNode, [nsIAccessibleHyperText]); + for (var jdx = 0; jdx < kLinksCount; jdx++) { + var link = htAcc.getLinkAt(jdx); + ok(link, "No link at index " + jdx + " for '" + aContainerID + "'"); + + var linkIdx = htAcc.getLinkIndex(link); + is(linkIdx, jdx, "Wrong link index for '" + aContainerID + "'!"); + } + }; + + this.getID = function addLinks_getID() { + return "Add links for '" + aContainerID + "'"; + }; + } + + function updateText(aContainerID) { + this.containerNode = getNode(aContainerID); + this.container = getAccessible(this.containerNode, nsIAccessibleHyperText); + this.text = this.container.firstChild; + this.textNode = this.text.DOMNode; + this.textLen = this.textNode.data.length; + + this.eventSeq = [ + new invokerChecker(EVENT_TEXT_INSERTED, this.containerNode), + ]; + + this.invoke = function updateText_invoke() { + is(this.container.getLinkIndexAtOffset(this.textLen), 0, + "Wrong intial text offsets!"); + + this.text.DOMNode.appendData(" my"); + }; + + this.finalCheck = function updateText_finalCheck() { + is(this.container.getLinkIndexAtOffset(this.textLen), -1, + "Text offsets weren't updated!"); + }; + + this.getID = function updateText_getID() { + return "update text for '" + aContainerID + "'"; + }; + } + + /** + * Text offsets must be updated when hypertext child is removed. + */ + function removeChild(aContainerID, aChildID, aInitialText, aFinalText) { + this.containerNode = getNode(aContainerID); + this.container = getAccessible(this.containerNode, nsIAccessibleText); + this.childNode = getNode(aChildID); + + // Call first to getText so offsets are cached + is(this.container.getText(0, -1), aInitialText, + "Wrong text before child removal"); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function removeChild_invoke() { + this.containerNode.removeChild(this.childNode); + }; + + this.finalCheck = function removeChild_finalCheck() { + is(this.container.getText(0, -1), aFinalText, + "Wrong text after child removal"); + is(this.container.characterCount, aFinalText.length, + "Wrong text after child removal"); + }; + + this.getID = function removeChild_getID() { + return "check text after removing child from '" + aContainerID + "'"; + }; + } + + function removeFirstChild(aContainer) { + this.ht = getAccessible(aContainer, [ nsIAccessibleHyperText ]); + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aContainer), + ]; + + this.invoke = function removeFirstChild_invoke() { + is(this.ht.linkCount, 2, "Wrong embedded objects count before removal"); + + getNode(aContainer).removeChild(getNode(aContainer).firstElementChild); + }; + + this.finalCheck = function removeFirstChild_finalCheck() { + // check list index before link count + is(this.ht.getLinkIndex(this.ht.firstChild), 0, "Wrong child index"); + is(this.ht.linkCount, 1, "Wrong embedded objects count after removal"); + }; + + this.getID = function removeFirstChild_getID() { + return "Remove first child and check embedded object indeces"; + }; + } + + function removeLastChild(aContainer) { + this.ht = getAccessible(aContainer, [ nsIAccessibleHyperText ]); + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aContainer), + ]; + + this.invoke = function removeLastChild_invoke() { + is(this.ht.linkCount, 1, "Wrong embedded objects count before removal"); + + getNode(aContainer).removeChild(getNode(aContainer).lastElementChild); + }; + + this.finalCheck = function removeLastChild_finalCheck() { + is(this.ht.linkCount, 0, "Wrong embedded objects count after removal"); + + var link = null; + try { + link = this.ht.getLinkAt(0); + } catch (e) { } + ok(!link, "No embedded object is expected"); + }; + + this.getID = function removeLastChild_getID() { + return "Remove last child and try its embedded object"; + }; + } + + // gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + gQueue.push(new addLinks("p1")); + gQueue.push(new updateText("p2")); + gQueue.push(new removeChild("div1", "div2", + "hello my good friend", "hello friend")); + gQueue.push(new removeFirstChild("c4")); + gQueue.push(new removeLastChild("c5")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Cache links within hypertext accessible" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=572394"> + Mozilla Bug 572394 + </a> + <a target="_blank" + title="Text offsets don't get updated when text of first child text accessible is changed" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=625009"> + Mozilla Bug 625009 + </a> + <a target="_blank" + title="Crash in nsHyperTextAccessible::GetText()" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=630841"> + Mozilla Bug 630841 + </a><br> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <p id="p1"></p> + <p id="p2"><b>hello</b><a>friend</a></p> + <div id="div1">hello<span id="div2"> my<span id="div3"> good</span></span> friend</span></div> + <form id="c4"> + <label for="c4_input">label</label> + <input id="c4_input"> + </form> + <div id="c5">TextLeaf<input id="c5_input"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/layout.js b/accessible/tests/mochitest/layout.js new file mode 100644 index 0000000000..1467d5fe7f --- /dev/null +++ b/accessible/tests/mochitest/layout.js @@ -0,0 +1,390 @@ +/* import-globals-from common.js */ + +/** + * Tests if the given child and grand child accessibles at the given point are + * expected. + * + * @param aID [in] accessible identifier + * @param aX [in] x coordinate of the point relative accessible + * @param aY [in] y coordinate of the point relative accessible + * @param aChildID [in] expected child accessible + * @param aGrandChildID [in] expected child accessible + */ +function testChildAtPoint(aID, aX, aY, aChildID, aGrandChildID) { + var child = getChildAtPoint(aID, aX, aY, false); + var expectedChild = getAccessible(aChildID); + + var msg = + "Wrong direct child accessible at the point (" + + aX + + ", " + + aY + + ") of " + + prettyName(aID); + isObject(child, expectedChild, msg); + + var grandChild = getChildAtPoint(aID, aX, aY, true); + var expectedGrandChild = getAccessible(aGrandChildID); + + msg = + "Wrong deepest child accessible at the point (" + + aX + + ", " + + aY + + ") of " + + prettyName(aID); + isObject(grandChild, expectedGrandChild, msg); +} + +/** + * Test if getChildAtPoint returns the given child and grand child accessibles + * at coordinates of child accessible (direct and deep hit test). + */ +function hitTest(aContainerID, aChildID, aGrandChildID) { + var container = getAccessible(aContainerID); + var child = getAccessible(aChildID); + var grandChild = getAccessible(aGrandChildID); + + var [x, y] = getBoundsForDOMElm(child); + + var actualChild = container.getChildAtPoint(x + 1, y + 1); + isObject( + actualChild, + child, + "Wrong direct child of " + prettyName(aContainerID) + ); + + var actualGrandChild = container.getDeepestChildAtPoint(x + 1, y + 1); + isObject( + actualGrandChild, + grandChild, + "Wrong deepest child of " + prettyName(aContainerID) + ); +} + +/** + * Test if getOffsetAtPoint returns the given text offset at given coordinates. + */ +function testOffsetAtPoint(aHyperTextID, aX, aY, aCoordType, aExpectedOffset) { + var hyperText = getAccessible(aHyperTextID, [nsIAccessibleText]); + var offset = hyperText.getOffsetAtPoint(aX, aY, aCoordType); + is( + offset, + aExpectedOffset, + "Wrong offset at given point (" + + aX + + ", " + + aY + + ") for " + + prettyName(aHyperTextID) + ); +} + +/** + * Zoom the given document. + */ +function zoomDocument(aDocument, aZoom) { + SpecialPowers.setFullZoom(aDocument.defaultView, aZoom); +} + +/** + * Set the relative resolution of this document. This is what apz does. + * On non-mobile platforms you won't see a visible change. + */ +function setResolution(aDocument, aZoom) { + var windowUtils = aDocument.defaultView.windowUtils; + + windowUtils.setResolutionAndScaleTo(aZoom); +} + +/** + * Return child accessible at the given point. + * + * @param aIdentifier [in] accessible identifier + * @param aX [in] x coordinate of the point relative accessible + * @param aY [in] y coordinate of the point relative accessible + * @param aFindDeepestChild [in] points whether deepest or nearest child should + * be returned + * @return the child accessible at the given point + */ +function getChildAtPoint(aIdentifier, aX, aY, aFindDeepestChild) { + var acc = getAccessible(aIdentifier); + if (!acc) { + return null; + } + + var [screenX, screenY] = getBoundsForDOMElm(acc.DOMNode); + + var x = screenX + aX; + var y = screenY + aY; + + try { + if (aFindDeepestChild) { + return acc.getDeepestChildAtPoint(x, y); + } + return acc.getChildAtPoint(x, y); + } catch (e) {} + + return null; +} + +/** + * Test the accessible position. + */ +function testPos(aID, aPoint) { + var [expectedX, expectedY] = + aPoint != undefined ? aPoint : getBoundsForDOMElm(aID); + + var [x, y] = getBounds(aID); + is(x, expectedX, "Wrong x coordinate of " + prettyName(aID)); + is(y, expectedY, "Wrong y coordinate of " + prettyName(aID)); +} + +/** + * Test the accessible boundaries. + */ +function testBounds(aID, aRect) { + var [expectedX, expectedY, expectedWidth, expectedHeight] = + aRect != undefined ? aRect : getBoundsForDOMElm(aID); + + var [x, y, width, height] = getBounds(aID); + is(x, expectedX, "Wrong x coordinate of " + prettyName(aID)); + is(y, expectedY, "Wrong y coordinate of " + prettyName(aID)); + is(width, expectedWidth, "Wrong width of " + prettyName(aID)); + is(height, expectedHeight, "Wrong height of " + prettyName(aID)); +} + +/** + * Test text position at the given offset. + */ +function testTextPos(aID, aOffset, aPoint, aCoordOrigin) { + var [expectedX, expectedY] = aPoint; + + var xObj = {}, + yObj = {}; + var hyperText = getAccessible(aID, [nsIAccessibleText]); + hyperText.getCharacterExtents(aOffset, xObj, yObj, {}, {}, aCoordOrigin); + is( + xObj.value, + expectedX, + "Wrong x coordinate at offset " + aOffset + " for " + prettyName(aID) + ); + ok( + yObj.value - expectedY <= 2 && expectedY - yObj.value <= 2, + "Wrong y coordinate at offset " + + aOffset + + " for " + + prettyName(aID) + + " - got " + + yObj.value + + ", expected " + + expectedY + + "The difference doesn't exceed 1." + ); +} + +/** + * Test text bounds that is enclosed betwene the given offsets. + */ +function testTextBounds(aID, aStartOffset, aEndOffset, aRect, aCoordOrigin) { + var [expectedX, expectedY, expectedWidth, expectedHeight] = aRect; + + var xObj = {}, + yObj = {}, + widthObj = {}, + heightObj = {}; + var hyperText = getAccessible(aID, [nsIAccessibleText]); + hyperText.getRangeExtents( + aStartOffset, + aEndOffset, + xObj, + yObj, + widthObj, + heightObj, + aCoordOrigin + ); + + // x + isWithin( + expectedX, + xObj.value, + 1, + "Wrong x coordinate of text between offsets (" + + aStartOffset + + ", " + + aEndOffset + + ") for " + + prettyName(aID) + ); + + // y + isWithin( + expectedY, + yObj.value, + 1, + `y coord of text between offsets (${aStartOffset}, ${aEndOffset}) ` + + `for ${prettyName(aID)}` + ); + + // Width + var msg = + "Wrong width of text between offsets (" + + aStartOffset + + ", " + + aEndOffset + + ") for " + + prettyName(aID) + + " - Got " + + widthObj.value + + " Expected " + + expectedWidth; + if (!WIN) { + isWithin(expectedWidth, widthObj.value, 1, msg); + } else { + // fails on some windows machines + todo(false, msg); + } + + // Height + isWithin( + expectedHeight, + heightObj.value, + 1, + `height of text between offsets (${aStartOffset}, ${aEndOffset}) ` + + `for ${prettyName(aID)}` + ); +} + +/** + * Return the accessible coordinates relative to the screen in device pixels. + */ +function getPos(aID) { + var accessible = getAccessible(aID); + var x = {}, + y = {}; + accessible.getBounds(x, y, {}, {}); + return [x.value, y.value]; +} + +/** + * Return the accessible coordinates and size relative to the screen in device + * pixels. This methods also retrieves coordinates in CSS pixels and ensures that they + * match Dev pixels with a given device pixel ratio. + */ +function getBounds(aID, aDPR = window.devicePixelRatio) { + const accessible = getAccessible(aID); + let x = {}, + y = {}, + width = {}, + height = {}; + let xInCSS = {}, + yInCSS = {}, + widthInCSS = {}, + heightInCSS = {}; + accessible.getBounds(x, y, width, height); + accessible.getBoundsInCSSPixels(xInCSS, yInCSS, widthInCSS, heightInCSS); + + info(`DPR is: ${aDPR}`); + isWithin( + xInCSS.value, + x.value / aDPR, + 1, + "X in CSS pixels is calculated correctly" + ); + isWithin( + yInCSS.value, + y.value / aDPR, + 1, + "Y in CSS pixels is calculated correctly" + ); + isWithin( + widthInCSS.value, + width.value / aDPR, + 1, + "Width in CSS pixels is calculated correctly" + ); + isWithin( + heightInCSS.value, + height.value / aDPR, + 1, + "Height in CSS pixels is calculated correctly" + ); + + return [x.value, y.value, width.value, height.value]; +} + +function getRangeExtents(aID, aStartOffset, aEndOffset, aCoordOrigin) { + var hyperText = getAccessible(aID, [nsIAccessibleText]); + var x = {}, + y = {}, + width = {}, + height = {}; + hyperText.getRangeExtents( + aStartOffset, + aEndOffset, + x, + y, + width, + height, + aCoordOrigin + ); + return [x.value, y.value, width.value, height.value]; +} + +/** + * Return DOM node coordinates relative the screen and its size in device + * pixels. + */ +function getBoundsForDOMElm(aID) { + var x = 0, + y = 0, + width = 0, + height = 0; + + var elm = getNode(aID); + if (elm.localName == "area") { + var mapName = elm.parentNode.getAttribute("name"); + var selector = "[usemap='#" + mapName + "']"; + var img = elm.ownerDocument.querySelector(selector); + + var areaCoords = elm.coords.split(","); + var areaX = parseInt(areaCoords[0]); + var areaY = parseInt(areaCoords[1]); + var areaWidth = parseInt(areaCoords[2]) - areaX; + var areaHeight = parseInt(areaCoords[3]) - areaY; + + let rect = img.getBoundingClientRect(); + x = rect.left + areaX; + y = rect.top + areaY; + width = areaWidth; + height = areaHeight; + } else { + let rect = elm.getBoundingClientRect(); + x = rect.left; + y = rect.top; + width = rect.width; + height = rect.height; + } + + var elmWindow = elm.ownerGlobal; + return CSSToDevicePixels( + elmWindow, + x + elmWindow.mozInnerScreenX, + y + elmWindow.mozInnerScreenY, + width, + height + ); +} + +function CSSToDevicePixels(aWindow, aX, aY, aWidth, aHeight) { + var ratio = aWindow.devicePixelRatio; + + // CSS pixels and ratio can be not integer. Device pixels are always integer. + // Do our best and hope it works. + return [ + Math.round(aX * ratio), + Math.round(aY * ratio), + Math.round(aWidth * ratio), + Math.round(aHeight * ratio), + ]; +} diff --git a/accessible/tests/mochitest/letters.gif b/accessible/tests/mochitest/letters.gif Binary files differnew file mode 100644 index 0000000000..299b91784a --- /dev/null +++ b/accessible/tests/mochitest/letters.gif diff --git a/accessible/tests/mochitest/longdesc_src.html b/accessible/tests/mochitest/longdesc_src.html new file mode 100644 index 0000000000..37248795dd --- /dev/null +++ b/accessible/tests/mochitest/longdesc_src.html @@ -0,0 +1,8 @@ +<html> +<head> +<title>Mozilla logo</title> +</head> +<body> +<p>This file would contain a longer description of the Mozilla logo, if I knew what it looked like.</p> +</body> +</html> diff --git a/accessible/tests/mochitest/moz.build b/accessible/tests/mochitest/moz.build new file mode 100644 index 0000000000..6276f6ced9 --- /dev/null +++ b/accessible/tests/mochitest/moz.build @@ -0,0 +1,36 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +A11Y_MANIFESTS += [ + "a11y.ini", + "actions/a11y.ini", + "aom/a11y.ini", + "attributes/a11y.ini", + "bounds/a11y.ini", + "elm/a11y.ini", + "events/a11y.ini", + "events/docload/a11y.ini", + "focus/a11y.ini", + "hittest/a11y.ini", + "hyperlink/a11y.ini", + "hypertext/a11y.ini", + "name/a11y.ini", + "pivot/a11y.ini", + "relations/a11y.ini", + "role/a11y.ini", + "scroll/a11y.ini", + "selectable/a11y.ini", + "states/a11y.ini", + "table/a11y.ini", + "text/a11y.ini", + "textattrs/a11y.ini", + "textcaret/a11y.ini", + "textrange/a11y.ini", + "textselection/a11y.ini", + "tree/a11y.ini", + "treeupdate/a11y.ini", + "value/a11y.ini", +] diff --git a/accessible/tests/mochitest/moz.png b/accessible/tests/mochitest/moz.png Binary files differnew file mode 100644 index 0000000000..743292dc6f --- /dev/null +++ b/accessible/tests/mochitest/moz.png diff --git a/accessible/tests/mochitest/name.js b/accessible/tests/mochitest/name.js new file mode 100644 index 0000000000..48bf2d7038 --- /dev/null +++ b/accessible/tests/mochitest/name.js @@ -0,0 +1,38 @@ +/* import-globals-from common.js */ + +/** + * Test accessible name for the given accessible identifier. + */ +function testName(aAccOrElmOrID, aName, aMsg, aTodo) { + var msg = aMsg ? aMsg : ""; + + var acc = getAccessible(aAccOrElmOrID); + if (!acc) { + return ""; + } + + var func = aTodo ? todo_is : is; + var txtID = prettyName(aAccOrElmOrID); + try { + func(acc.name, aName, msg + "Wrong name of the accessible for " + txtID); + } catch (e) { + ok(false, msg + "Can't get name of the accessible for " + txtID); + } + return acc; +} + +/** + * Test accessible description for the given accessible. + */ +function testDescr(aAccOrElmOrID, aDescr) { + var acc = getAccessible(aAccOrElmOrID); + if (!acc) { + return; + } + + is( + acc.description, + aDescr, + "Wrong description for " + prettyName(aAccOrElmOrID) + ); +} diff --git a/accessible/tests/mochitest/name/a11y.ini b/accessible/tests/mochitest/name/a11y.ini new file mode 100644 index 0000000000..cfcb49d816 --- /dev/null +++ b/accessible/tests/mochitest/name/a11y.ini @@ -0,0 +1,18 @@ +[DEFAULT] +support-files = + markup.js + markuprules.xml + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/moz.png + +[test_ARIACore_examples.html] +[test_browserui.xhtml] +[test_counterstyle.html] +[test_general.html] +[test_general.xhtml] +[test_link.html] +[test_list.html] +[test_markup.html] +skip-if = (debug && os == 'win') # Bug 1296784 +[test_svg.html] +[test_tree.xhtml] diff --git a/accessible/tests/mochitest/name/markup.js b/accessible/tests/mochitest/name/markup.js new file mode 100644 index 0000000000..a267bd4f7b --- /dev/null +++ b/accessible/tests/mochitest/name/markup.js @@ -0,0 +1,425 @@ +/* import-globals-from ../attributes.js */ +/* import-globals-from ../common.js */ +/* import-globals-from ../events.js */ +/* import-globals-from ../name.js */ + +// ////////////////////////////////////////////////////////////////////////////// +// Name tests described by "markuprules.xml" file. + +var gNameRulesFileURL = "markuprules.xml"; + +var gRuleDoc = null; + +// Debuggin stuff. +var gDumpToConsole = false; + +/** + * Start name tests. Run through markup elements and test names for test + * element (see namerules.xml for details). + */ +function testNames() { + // enableLogging("tree,stack"); // debugging + + var request = new XMLHttpRequest(); + request.open("get", gNameRulesFileURL, false); + request.send(); + + gRuleDoc = request.responseXML; + + var markupElms = evaluateXPath(gRuleDoc, "//rules/rulesample/markup"); + gTestIterator.iterateMarkups(markupElms); +} + +// ////////////////////////////////////////////////////////////////////////////// +// Private section. + +/** + * Helper class to interate through name tests. + */ +var gTestIterator = { + iterateMarkups: function gTestIterator_iterateMarkups(aMarkupElms) { + this.markupElms = aMarkupElms; + + this.iterateNext(); + }, + + iterateRules: function gTestIterator_iterateRules( + aElm, + aContainer, + aRuleSetElm, + aRuleElms, + aTestID + ) { + this.ruleSetElm = aRuleSetElm; + this.ruleElms = aRuleElms; + this.elm = aElm; + this.container = aContainer; + this.testID = aTestID; + + this.iterateNext(); + }, + + iterateNext: function gTestIterator_iterateNext() { + if (this.markupIdx == -1) { + this.markupIdx++; + testNamesForMarkup(this.markupElms[this.markupIdx]); + return; + } + + this.ruleIdx++; + if (this.ruleIdx == this.ruleElms.length) { + // When test is finished then name is empty and no explict-name. + var defaultName = this.ruleSetElm.hasAttribute("defaultName") + ? this.ruleSetElm.getAttribute("defaultName") + : null; + testName( + this.elm, + defaultName, + "Default name test (" + gTestIterator.testID + "). " + ); + testAbsentAttrs(this.elm, { "explicit-name": "true" }); + + this.markupIdx++; + if (this.markupIdx == this.markupElms.length) { + // disableLogging("tree"); // debugging + SimpleTest.finish(); + return; + } + + this.ruleIdx = -1; + + if (gDumpToConsole) { + dump( + "\nPend next markup processing. Wait for reorder event on " + + prettyName(document) + + "'\n" + ); + } + waitForEvent( + EVENT_REORDER, + document, + testNamesForMarkup, + null, + this.markupElms[this.markupIdx] + ); + + document.body.removeChild(this.container); + return; + } + + testNameForRule(this.elm, this.ruleElms[this.ruleIdx]); + }, + + markupElms: null, + markupIdx: -1, + rulesetElm: null, + ruleElms: null, + ruleIdx: -1, + elm: null, + container: null, + testID: "", +}; + +/** + * Process every 'markup' element and test names for it. Used by testNames + * function. + */ +function testNamesForMarkup(aMarkupElm) { + if (gDumpToConsole) { + dump("\nProcessing markup '" + aMarkupElm.getAttribute("id") + "'\n"); + } + + var div = document.createElement("div"); + div.setAttribute("id", "test"); + + var child = aMarkupElm.firstChild; + while (child) { + var newChild = document.importNode(child, true); + div.appendChild(newChild); + child = child.nextSibling; + } + + if (gDumpToConsole) { + dump( + "\nProcessing markup. Wait for reorder event on " + + prettyName(document) + + "'\n" + ); + } + waitForEvent( + EVENT_REORDER, + document, + testNamesForMarkupRules, + null, + aMarkupElm, + div + ); + + document.body.appendChild(div); +} + +function testNamesForMarkupRules(aMarkupElm, aContainer) { + var testID = aMarkupElm.getAttribute("id"); + if (gDumpToConsole) { + dump("\nProcessing markup rules '" + testID + "'\n"); + } + + var expr = "//html/body/div[@id='test']/" + aMarkupElm.getAttribute("ref"); + var elm = evaluateXPath(document, expr, htmlDocResolver)[0]; + + var ruleId = aMarkupElm.getAttribute("ruleset"); + var ruleElm = gRuleDoc.querySelector("[id='" + ruleId + "']"); + var ruleElms = getRuleElmsByRulesetId(ruleId); + + var processMarkupRules = gTestIterator.iterateRules.bind( + gTestIterator, + elm, + aContainer, + ruleElm, + ruleElms, + testID + ); + + // Images may be recreated after we append them into subtree. We need to wait + // in this case. If we are on profiling enabled build then stack tracing + // works and thus let's log instead. Note, that works if you enabled logging + // (refer to testNames() function). + if (isAccessible(elm) || isLogged("stack")) { + processMarkupRules(); + } else { + waitForEvent(EVENT_SHOW, elm, processMarkupRules); + } +} + +/** + * Test name for current rule and current 'markup' element. Used by + * testNamesForMarkup function. + */ +function testNameForRule(aElm, aRuleElm) { + if (aRuleElm.hasAttribute("attr")) { + if (gDumpToConsole) { + dump( + "\nProcessing rule { attr: " + aRuleElm.getAttribute("attr") + " }\n" + ); + } + + testNameForAttrRule(aElm, aRuleElm); + } else if (aRuleElm.hasAttribute("elm")) { + if (gDumpToConsole) { + dump( + "\nProcessing rule { elm: " + + aRuleElm.getAttribute("elm") + + ", elmattr: " + + aRuleElm.getAttribute("elmattr") + + " }\n" + ); + } + + testNameForElmRule(aElm, aRuleElm); + } else if (aRuleElm.getAttribute("fromsubtree") == "true") { + if (gDumpToConsole) { + dump( + "\nProcessing rule { fromsubtree: " + + aRuleElm.getAttribute("fromsubtree") + + " }\n" + ); + } + + testNameForSubtreeRule(aElm, aRuleElm); + } +} + +function testNameForAttrRule(aElm, aRule) { + var name = ""; + + var attr = aRule.getAttribute("attr"); + var attrValue = aElm.getAttribute(attr); + + var type = aRule.getAttribute("type"); + if (type == "string") { + name = attrValue; + } else if (type == "ref" && attrValue) { + var ids = attrValue.split(/\s+/); + for (var idx = 0; idx < ids.length; idx++) { + var labelElm = getNode(ids[idx]); + if (name != "") { + name += " "; + } + + name += labelElm.getAttribute("textequiv"); + } + } + + var msg = "Attribute '" + attr + "' test (" + gTestIterator.testID + "). "; + testName(aElm, name, msg); + + if (aRule.getAttribute("explict-name") != "false") { + testAttrs(aElm, { "explicit-name": "true" }, true); + } else { + testAbsentAttrs(aElm, { "explicit-name": "true" }); + } + + waitForEvent( + EVENT_NAME_CHANGE, + aElm, + gTestIterator.iterateNext, + gTestIterator + ); + + aElm.removeAttribute(attr); +} + +function testNameForElmRule(aElm, aRule) { + var labelElm; + + var tagname = aRule.getAttribute("elm"); + var attrname = aRule.getAttribute("elmattr"); + if (attrname) { + var filter = { + acceptNode: function filter_acceptNode(aNode) { + if ( + aNode.localName == this.mLocalName && + aNode.getAttribute(this.mAttrName) == this.mAttrValue + ) { + return NodeFilter.FILTER_ACCEPT; + } + + return NodeFilter.FILTER_SKIP; + }, + + mLocalName: tagname, + mAttrName: attrname, + mAttrValue: aElm.getAttribute("id"), + }; + + var treeWalker = document.createTreeWalker( + document.body, + NodeFilter.SHOW_ELEMENT, + filter + ); + labelElm = treeWalker.nextNode(); + } else { + // if attrname is empty then look for the element in subtree. + labelElm = aElm.getElementsByTagName(tagname)[0]; + if (!labelElm) { + labelElm = aElm.getElementsByTagName("html:" + tagname)[0]; + } + } + + if (!labelElm) { + ok(false, msg + " Failed to find '" + tagname + "' element."); + gTestIterator.iterateNext(); + return; + } + + var msg = "Element '" + tagname + "' test (" + gTestIterator.testID + ")."; + testName(aElm, labelElm.getAttribute("textequiv"), msg); + testAttrs(aElm, { "explicit-name": "true" }, true); + + var parentNode = labelElm.parentNode; + + if (gDumpToConsole) { + dump( + "\nProcessed elm rule. Wait for name change event on " + + prettyName(aElm) + + "\n" + ); + } + waitForEvent( + EVENT_NAME_CHANGE, + aElm, + gTestIterator.iterateNext, + gTestIterator + ); + + parentNode.removeChild(labelElm); +} + +function testNameForSubtreeRule(aElm, aRule) { + var msg = "From subtree test (" + gTestIterator.testID + ")."; + testName(aElm, aElm.getAttribute("textequiv"), msg); + testAbsentAttrs(aElm, { "explicit-name": "true" }); + + if (gDumpToConsole) { + dump( + "\nProcessed from subtree rule. Wait for reorder event on " + + prettyName(aElm) + + "\n" + ); + } + waitForEvent( + EVENT_NAME_CHANGE, + aElm, + gTestIterator.iterateNext, + gTestIterator + ); + + while (aElm.firstChild) { + aElm.firstChild.remove(); + } +} + +/** + * Return array of 'rule' elements. Used in conjunction with + * getRuleElmsFromRulesetElm() function. + */ +function getRuleElmsByRulesetId(aRulesetId) { + var expr = "//rules/ruledfn/ruleset[@id='" + aRulesetId + "']"; + var rulesetElm = evaluateXPath(gRuleDoc, expr); + return getRuleElmsFromRulesetElm(rulesetElm[0]); +} + +function getRuleElmsFromRulesetElm(aRulesetElm) { + var rulesetId = aRulesetElm.getAttribute("ref"); + if (rulesetId) { + return getRuleElmsByRulesetId(rulesetId); + } + + var ruleElms = []; + + var child = aRulesetElm.firstChild; + while (child) { + if (child.localName == "ruleset") { + ruleElms = ruleElms.concat(getRuleElmsFromRulesetElm(child)); + } + if (child.localName == "rule") { + ruleElms.push(child); + } + + child = child.nextSibling; + } + + return ruleElms; +} + +/** + * Helper method to evaluate xpath expression. + */ +function evaluateXPath(aNode, aExpr, aResolver) { + var xpe = new XPathEvaluator(); + + var resolver = aResolver; + if (!resolver) { + var node = + aNode.ownerDocument == null + ? aNode.documentElement + : aNode.ownerDocument.documentElement; + resolver = xpe.createNSResolver(node); + } + + var result = xpe.evaluate(aExpr, aNode, resolver, 0, null); + var found = []; + var res; + while ((res = result.iterateNext())) { + found.push(res); + } + + return found; +} + +function htmlDocResolver(aPrefix) { + var ns = { + html: "http://www.w3.org/1999/xhtml", + }; + return ns[aPrefix] || null; +} diff --git a/accessible/tests/mochitest/name/markuprules.xml b/accessible/tests/mochitest/name/markuprules.xml new file mode 100644 index 0000000000..045a25b437 --- /dev/null +++ b/accessible/tests/mochitest/name/markuprules.xml @@ -0,0 +1,367 @@ +<?xml version="1.0"?> + +<!-- + This XML file is used to create sequence of accessible name tests. It consist + of two sections. The first section 'ruledfn' declares name computation rules. + The second section 'rulesample' defines markup samples we need to check name + computation rules for. + + <ruledfn> + <ruleset> + <rule> + + Section 'ruledfn' contains 'ruleset' elements. Every 'ruleset' element is + presented by 'rule' elements so that sequence of 'rule' elements gives the + sequence of name computations rules. Every 'rule' element can be one of four + types. + + * <rule attr='' type='string'/> used when name is equal to the value of + attribute presented on the element. + + Example, 'aria-label' attribute. In this case 'rule' element has 'attr' + attribute pointing to attribute name and 'type' attribute with 'string' + value. For example, <rule attr="aria-label" type="string"/>. + + * <rule attr='' type='ref'/> used when name is calculated from elements that + are pointed to by attribute value on the element. + + Example is 'aria-labelledby'. In this case 'rule' element has 'attr' + attribute holding the sequence of IDs of elements used to compute the name, + in addition the 'rule' element has 'type' attribute with 'ref' value. + For example, <rule attr="aria-labelledby" type="ref"/>. + + * <rule elm='' elmattr=''/> used when name is calculated from another + element. These attributes are used to find an element by tagname and + attribute with value equaled to ID of the element. If 'elmattr' is missed + then element from subtree with the given tagname is used. + + Example, html:label@for element, <rule elm="label" elmattr="for"/>. + Example, html:caption element, <rule elm="caption"/> + + * <rule fromsubtree='true'/> used when name is computed from subtree. + + Example, html:button. In this case 'rule' element has 'fromsubtree' + attribute with 'true' value. + + <rulesample> + <markup ruleset=''> + + Section 'rulesample' provides set of markup samples ('markup' elements). Every + 'markup' element contains an element that accessible name will be computed for + (let's call it test element). In addition the 'markup' element contains some + other elements from native markup used in name calculation process for test + element. Test element is pointed to by 'ref' attribute on 'markup' element. + Also 'markup' element has 'ruleset' attribute to indicate ruleset for the test + element. + + How does it work? Let's consider simple example: + <ruledfn> + <ruleset id="aria"> + <rule attr="aria-label" type="string"/> + <rule attr="aria-labelledby" type="ref"/> + </ruleset> + </ruledfn> + <rulesample> + <markup ref="html:div" ruleset="aria"> + <html:span id="label" textequiv="test2">test2</html:span> + <html:div aria-label="test1" + aria-labelledby="label">it's a div</html:div> + </markup> + </rulesample> + + Initially 'markup' element holds markup for all rules specified by 'ruleset' + attribute. This allows us to check if the sequence of name computation rules + is correct. Here 'ruleset' element defines two rules. We get the first rule + which means accessible name is computed from value of 'aria-label' attribute. + Then we check accessible name for the test element and remove 'aria-label' + attribute. After we get the second rule which means we should get IDs from + 'aria-labelledby' attribute and compose accessible name from values of + 'textequiv' attributes (that are supposed to give the desired name for each + element that is being pointed to by aria-labelledby). Check accessible name + and finish test. +--> + +<rules xmlns:html="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <ruledfn> + + <!-- bricks --> + <ruleset id="ARIA"> + <rule attr="aria-labelledby" type="ref"/> + <rule attr="aria-label" type="string"/> + </ruleset> + + <ruleset id="HTMLControl:Head"> + <ruleset ref="ARIA"/> + <rule elm="label" elmattr="for"/> + </ruleset> + + <!-- general --> + <ruleset id="HTMLControl"> + <ruleset ref="HTMLControl:Head"/> + <rule fromsubtree="true"/> + <rule attr="title" type="string"/> + </ruleset> + + <ruleset id="HTMLElm"> + <ruleset ref="ARIA"/> + <rule attr="title" type="string"/> + </ruleset> + + <!-- specific --> + <ruleset id="HTMLARIAGridCell"> + <ruleset ref="ARIA"/> + <rule fromsubtree="true"/> + <rule attr="title" type="string"/> + </ruleset> + + <ruleset id="HTMLInputButton"> + <ruleset ref="HTMLControl:Head"/> + <rule attr="value" type="string" explict-name="false" reordered="true"/> + <rule attr="title" type="string"/> + </ruleset> + + <ruleset id="HTMLInputSubmit" defaultName="Submit Query"> + <ruleset ref="HTMLControl:Head"/> + <rule attr="value" type="string" explict-name="false"/> + </ruleset> + + <ruleset id="HTMLInputReset" defaultName="Reset"> + <ruleset ref="HTMLControl:Head"/> + <rule attr="value" type="string" explict-name="false"/> + </ruleset> + + <ruleset id="HTMLInputImage"> + <ruleset ref="HTMLControl:Head"/> + <rule attr="alt" type="string"/> + <rule attr="value" type="string"/> + <rule attr="title" type="string"/> + </ruleset> + + <ruleset id="HTMLInputImageNoValidSrc" defaultName="Submit Query"> + <ruleset ref="HTMLControl:Head"/> + <rule attr="alt" type="string" explict-name="false"/> + <rule attr="value" type="string" explict-name="false"/> + </ruleset> + + <ruleset id="HTMLOption"> + <ruleset ref="ARIA"/> + <rule attr="label" type="string"/> + <rule fromsubtree="true"/> + <rule attr="title" type="string"/> + </ruleset> + + <ruleset id="HTMLImg"> + <ruleset ref="ARIA"/> + <rule attr="alt" type="string"/> + <rule attr="title" type="string"/> + </ruleset> + + <ruleset id="HTMLTable"> + <ruleset ref="ARIA"/> + <rule elm="caption"/> + <rule attr="summary" type="string"/> + <rule attr="title" type="string"/> + </ruleset> + </ruledfn> + + <rulesample> + + <markup id="HTMLButtonTest" + ref="html:button" ruleset="HTMLControl"> + <html:span id="l1" textequiv="test2">test2</html:span> + <html:span id="l2" textequiv="test3">test3</html:span> + <html:label for="btn" textequiv="test4">test4</html:label> + <html:button id="btn" + aria-label="test1" + aria-labelledby="l1 l2" + title="test5" + textequiv="press me">press me</html:button> + </markup> + + <markup id="HTMLInputButtonTest" + ref="html:input" ruleset="HTMLInputButton"> + <html:span id="l1" textequiv="test2">test2</html:span> + <html:span id="l2" textequiv="test3">test3</html:span> + <html:label for="btn" textequiv="test4">test4</html:label> + <html:input id="btn" + type="button" + aria-label="test1" + aria-labelledby="l1 l2" + value="name from value" + alt="no name from al" + src="no name from src" + data="no name from data" + title="name from title"/> + </markup> + + <markup id="HTMLInputSubmitTest" + ref="html:input" ruleset="HTMLInputSubmit"> + <html:span id="l1" textequiv="test2">test2</html:span> + <html:span id="l2" textequiv="test3">test3</html:span> + <html:label for="btn-submit" textequiv="test4">test4</html:label> + <html:input id="btn-submit" + type="submit" + aria-label="test1" + aria-labelledby="l1 l2" + value="name from value" + alt="no name from atl" + src="no name from src" + data="no name from data" + title="no name from title"/> + </markup> + + <markup id="HTMLInputResetTest" + ref="html:input" ruleset="HTMLInputReset"> + <html:span id="l1" textequiv="test2">test2</html:span> + <html:span id="l2" textequiv="test3">test3</html:span> + <html:label for="btn-reset" textequiv="test4">test4</html:label> + <html:input id="btn-reset" + type="reset" + aria-label="test1" + aria-labelledby="l1 l2" + value="name from value" + alt="no name from alt" + src="no name from src" + data="no name from data" + title="no name from title"/> + </markup> + + <!-- + Disabled due to intermittent failures (bug 1436323) which became more + frequent due to the landing of bug 1383682. The latter bug made loading + of images from cache much more consistent, which appears to have impacted + the timing for this test case. If the image is switched to a unique + image (e.g. always decoding since there is no cache), the failure rate + increases, presumably because the test is dependent on a specific ordering + of events, and implicitly assumes the image is loaded immediately. + --> + + <!-- + <markup id="HTMLInputImageTest" + ref="html:input" ruleset="HTMLInputImage"> + <html:span id="l1" textequiv="test2">test2</html:span> + <html:span id="l2" textequiv="test3">test3</html:span> + <html:label for="btn-image" textequiv="test4">test4</html:label> + <html:input id="btn-image" + type="image" + aria-label="test1" + aria-labelledby="l1 l2" + alt="name from alt" + value="name from value" + src="../moz.png" + data="no name from data" + title="name from title"/> + </markup> + --> + + <markup id="HTMLInputImageNoValidSrcTest" + ref="html:input" ruleset="HTMLInputImageNoValidSrc"> + <html:span id="l1" textequiv="test2">test2</html:span> + <html:span id="l2" textequiv="test3">test3</html:span> + <html:label for="btn-image" textequiv="test4">test4</html:label> + <html:input id="btn-image" + type="image" + aria-label="test1" + aria-labelledby="l1 l2" + alt="name from alt" + value="name from value" + data="no name from data" + title="no name from title"/> + </markup> + + <markup id="HTMLOptionTest" + ref="html:select/html:option[1]" ruleset="HTMLOption"> + <html:span id="l1" textequiv="test2">test2</html:span> + <html:span id="l2" textequiv="test3">test3</html:span> + <html:select> + <html:option id="opt" + aria-label="test1" + aria-labelledby="l1 l2" + label="test4" + title="test5" + textequiv="option1">option1</html:option> + <html:option>option2</html:option> + </html:select> + </markup> + + <markup id="HTMLImageTest" + ref="html:img" ruleset="HTMLImg"> + <html:span id="l1" textequiv="test2">test2</html:span> + <html:span id="l2" textequiv="test3">test3</html:span> + <html:img id="img" + aria-label="Logo of Mozilla" + aria-labelledby="l1 l2" + alt="Mozilla logo" + title="This is a logo" + src="../moz.png"/> + </markup> + + <markup id="HTMLTdTest" + ref="html:table/html:tr/html:td" ruleset="HTMLElm"> + <html:span id="l1" textequiv="test2">test2</html:span> + <html:span id="l2" textequiv="test3">test3</html:span> + <html:label for="tc" textequiv="test4">test4</html:label> + <html:table> + <html:tr> + <html:td id="tc" + aria-label="test1" + aria-labelledby="l1 l2" + title="test5"> + <html:p>This is a paragraph</html:p> + <html:a href="#">This is a link</html:a> + <html:ul> + <html:li>This is a list</html:li> + </html:ul> + </html:td> + </html:tr> + </html:table> + </markup> + + <markup id="HTMLTdARIAGridCellTest" + ref="html:table/html:tr/html:td" ruleset="HTMLARIAGridCell"> + <html:span id="l1" textequiv="test2">test2</html:span> + <html:span id="l2" textequiv="test3">test3</html:span> + <html:label for="gc" textequiv="test4">test4</html:label> + <html:table> + <html:tr> + <html:td id="gc" + role="gridcell" + aria-label="test1" + aria-labelledby="l1 l2" + textequiv="This is a paragraph This is a link • Listitem1 • Listitem2" + title="This is a paragraph This is a link This is a list"> + <html:p>This is a paragraph</html:p> + <html:a href="#">This is a link</html:a> + <html:ul> + <html:li>Listitem1</html:li> + <html:li>Listitem2</html:li> + </html:ul> + </html:td> + </html:tr> + </html:table> + </markup> + + <markup id="HTMLTableTest" + ref="html:table" ruleset="HTMLTable"> + <html:span id="l1" textequiv="lby_tst6_1">lby_tst6_1</html:span> + <html:span id="l2" textequiv="lby_tst6_2">lby_tst6_2</html:span> + <html:label for="t" textequiv="label_tst6">label_tst6</html:label> + <!-- layout frame are recreated due to varous reasons, here's text frame + placed after caption frame triggres table frame recreation when + caption element is removed from DOM; get rid text node after caption + node to make the test working --> + <html:table id="t" aria-label="arialabel_tst6" + aria-labelledby="l1 l2" + summary="summary_tst6" + title="title_tst6"> + <html:caption textequiv="caption_tst6">caption_tst6</html:caption><html:tr> + <html:td>cell1</html:td> + <html:td>cell2</html:td> + </html:tr> + </html:table> + </markup> + + </rulesample> +</rules> diff --git a/accessible/tests/mochitest/name/test_ARIACore_examples.html b/accessible/tests/mochitest/name/test_ARIACore_examples.html new file mode 100644 index 0000000000..a15fee78f8 --- /dev/null +++ b/accessible/tests/mochitest/name/test_ARIACore_examples.html @@ -0,0 +1,90 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Testing a11y name ccomputation testcases</title> + + <link rel="stylesheet" + type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../name.js"></script> + + <script type="application/javascript"> + function doTest() { + // All test cases taken from https://www.w3.org/TR/accname-1.1/ + // These were especially called out to demonstrate edge cases. + + // Example 1 from section 4.3.1 under 2.B. + // Element1 should get its name from the text in element3. + // Element2 should not gets name from element1 because that already + // gets its name from another element. + testName("el1", "hello"); + testName("el2", null); + + // Example 2 from section 4.3.1 under 2.C. + // The buttons should get their name from their labels and the links. + testName("del_row1", "Delete Documentation.pdf"); + testName("del_row2", "Delete HolidayLetter.pdf"); + + // Example 3 from section 4.3.1 under 2.F. + // Name should be own content text plus the value of the input plus + // more own inner text, separated by 1 space. + testName("chkbx", "Flash the screen 5 times"); + + // Example 4 from section 4.3.1 under 2.F. + // Name from content should include all the child nodes, including + // table cells. + testName("input_with_html_label", "foo bar baz"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- el1 should be labeled, el2 should not. --> + <div id="el1" aria-labelledby="el3"></div> + <div id="el2" aria-labelledby="el1"></div> + <div id="el3"> hello </div> + + <!-- The buttons should be labeled by themselves and the referenced link --> + <ul> + <li> + <a id="file_row1" href="./files/Documentation.pdf">Documentation.pdf</a> + <span role="button" tabindex="0" id="del_row1" aria-label="Delete" + aria-labelledby="del_row1 file_row1"></span> + </li> + <li> + <a id="file_row2" href="./files/HolidayLetter.pdf">HolidayLetter.pdf</a> + <span role="button" tabindex="0" id="del_row2" aria-label="Delete" + aria-labelledby="del_row2 file_row2"></span> + </li> + </ul> + + <!-- Label from combined text and subtree --> + <div id="chkbx" role="checkbox" aria-checked="false">Flash the screen + <span role="textbox" aria-multiline="false"> 5 </span> times</div> + + <!-- Label with name from content should include table --> + <input id="input_with_html_label" /> + <label for="input_with_html_label" id="label"> + <div>foo</div> + <table><tr><td>bar</td></tr></table> + <div>baz</div> + </label> + +</body> +</html> diff --git a/accessible/tests/mochitest/name/test_browserui.xhtml b/accessible/tests/mochitest/name/test_browserui.xhtml new file mode 100644 index 0000000000..348cb89202 --- /dev/null +++ b/accessible/tests/mochitest/name/test_browserui.xhtml @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessibility Name Calculating Test."> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + <![CDATA[ + const { BrowserTestUtils } = ChromeUtils.import( + "resource://testing-common/BrowserTestUtils.jsm"); + const ABOUT_MOZILLA_URL = "about:mozilla"; + const ABOUT_LICENSE_URL = "about:license"; + + SimpleTest.waitForExplicitFinish(); + + (async () => { + info("Opening a new browser window."); + const win = await BrowserTestUtils.openNewBrowserWindow({ + remote: false, + fission: false, + }); + const winFocused = SimpleTest.promiseFocus(win); + const loaded = BrowserTestUtils.browserLoaded( + win.gBrowser.selectedBrowser); + let docLoaded = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, event => + event.accessible.QueryInterface(nsIAccessibleDocument).URL === ABOUT_LICENSE_URL, + `Loaded tab: ${ABOUT_LICENSE_URL}`); + BrowserTestUtils.loadURIString(win.gBrowser.selectedBrowser, + "about:license"); + await loaded; + await docLoaded; + await winFocused; + + info(`Loading a new tab: ${ABOUT_MOZILLA_URL}.`); + docLoaded = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, event => + event.accessible.QueryInterface(nsIAccessibleDocument).URL === ABOUT_MOZILLA_URL, + `Added tab: ${ABOUT_MOZILLA_URL}`); + const tab = win.gBrowser.addTrustedTab(ABOUT_MOZILLA_URL); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + await docLoaded; + + info("Focusing on the newly opened tab."); + const focused = waitForEvent(EVENT_FOCUS, event => + event.DOMNode === win.gBrowser.getBrowserAtIndex(1).contentDocument); + await BrowserTestUtils.synthesizeKey("VK_TAB", { ctrlKey: true }, + win.gBrowser.selectedBrowser); + const focusEvent = await focused; + + const title = getAccessible(win.document).name; + const accName = focusEvent.accessible.name; + isnot(title.indexOf(accName), -1, + `Window title contains the name of active tab document (Is "${accName}" in "${title}"?)`); + + await BrowserTestUtils.closeWindow(win); + SimpleTest.finish(); + })(); + ]]> + </script> + + <vbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=507382" + title="focus is fired earlier than root accessible name is changed when switching between tabs"> + Mozilla Bug + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="eventdump"></vbox> + </vbox> +</window> diff --git a/accessible/tests/mochitest/name/test_counterstyle.html b/accessible/tests/mochitest/name/test_counterstyle.html new file mode 100644 index 0000000000..a20aca23f1 --- /dev/null +++ b/accessible/tests/mochitest/name/test_counterstyle.html @@ -0,0 +1,150 @@ +<html> + +<head> + <title>nsIAccessible::name calculation for @counter-style</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../name.js"></script> + + <style id="counterstyles" type="text/css"> + @counter-style system-alphabetic { + system: alphabetic; + symbols: x y z; + } + @counter-style system-cyclic { + system: cyclic; + symbols: x y z; + } + @counter-style system-numeric { + system: numeric; + symbols: x y z; + } + @counter-style speak-as-bullets { + system: extends decimal; + speak-as: bullets; + } + @counter-style speak-as-numbers { + system: extends system-alphabetic; + speak-as: numbers; + } + @counter-style speak-as-words { + system: additive; + additive-symbols: 20 "twenty ", 9 "nine", 7 "seven", 1 "one"; + speak-as: words; + } + @counter-style speak-as-spell-out { + system: extends system-alphabetic; + speak-as: spell-out; + } + @counter-style speak-as-other { + system: extends decimal; + speak-as: speak-as-words; + } + @counter-style speak-as-loop { + system: extends upper-latin; + speak-as: speak-as-loop0; + } + @counter-style speak-as-loop0 { + system: extends disc; + speak-as: speak-as-loop1; + } + @counter-style speak-as-loop1 { + system: extends decimal; + speak-as: speak-as-loop0; + } + @counter-style speak-as-extended0 { + system: extends decimal; + speak-as: speak-as-extended1; + } + @counter-style speak-as-extended1 { + system: extends speak-as-extended0; + speak-as: disc; + } + @counter-style speak-as-extended2 { + system: extends decimal; + speak-as: speak-as-extended3; + } + @counter-style speak-as-extended3 { + system: extends speak-as-extended2; + } + </style> + + <script type="application/javascript"> + + function doTest() { + function testRule(aRule, aNames, aTodo) { + testName(aRule + "-1", aNames[0], null, aTodo); + testName(aRule + "-7", aNames[1], null, aTodo); + testName(aRule + "-29", aNames[2], null, aTodo); + } + + var spellOutNames = ["X. 1", "Y X. 7", "Y Z Y. 29"]; + var bulletsNames = [kDiscBulletText + "1", + kDiscBulletText + "7", + kDiscBulletText + "29"]; + var numbersNames = ["1. 1", "7. 7", "29. 29"]; + var wordsNames = ["one. 1", "seven. 7", "twenty nine. 29"]; + + testRule("system-alphabetic", spellOutNames, true); // bug 1024178 + testRule("system-cyclic", bulletsNames); + testRule("system-numeric", numbersNames); + + testRule("speak-as-bullets", bulletsNames); + testRule("speak-as-numbers", numbersNames); + testRule("speak-as-words", wordsNames); + testRule("speak-as-spell-out", spellOutNames, true); // bug 1024178 + testRule("speak-as-other", wordsNames); + + testRule("speak-as-loop", bulletsNames); + testRule("speak-as-loop0", bulletsNames); + testRule("speak-as-loop1", numbersNames); + + testRule("speak-as-extended0", bulletsNames); + testRule("speak-as-extended1", bulletsNames); + testRule("speak-as-extended2", numbersNames); + testRule("speak-as-extended3", numbersNames); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=966166" + title="Bug 966166 - Implement @counter-style rule"> + Bug 966166 + </a> + + <ol id="list"></ol> + + <script type="application/javascript"> + var list = getNode("list"); + var rules = getNode("counterstyles").sheet.cssRules; + var values = [1, 7, 29]; + for (var i = 0; i < rules.length; i++) { + var rule = rules[i]; + for (var j = 0; j < values.length; j++) { + var item = document.createElement("li"); + item.id = rule.name + "-" + values[j]; + item.value = values[j]; + item.textContent = values[j]; + item.setAttribute("style", "list-style-type: " + rule.name); + list.appendChild(item); + } + } + </script> + +</body> +</html> diff --git a/accessible/tests/mochitest/name/test_general.html b/accessible/tests/mochitest/name/test_general.html new file mode 100644 index 0000000000..89314712fb --- /dev/null +++ b/accessible/tests/mochitest/name/test_general.html @@ -0,0 +1,732 @@ +<html> + +<head> + <title>nsIAccessible::name calculation</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../name.js"></script> + + <script type="application/javascript"> + + function doTest() { + // aria-label + + // Simple label provided via ARIA + testName("btn_simple_aria_label", "I am a button"); + + // aria-label and aria-labelledby, expect aria-labelledby + testName("btn_both_aria_labels", "text I am a button, two"); + + // //////////////////////////////////////////////////////////////////////// + // aria-labelledby + + // Single relation. The value of 'aria-labelledby' contains the ID of + // an element. Gets the name from text node of that element. + testName("btn_labelledby_text", "text"); + + // Multiple relations. The value of 'aria-labelledby' contains the IDs + // of elements. Gets the name from text nodes of those elements. + testName("btn_labelledby_texts", "text1 text2"); + + // //////////////////////////////////////////////////////////////////////// + // Name from named accessible + + testName("input_labelledby_namedacc", "Data"); + + // //////////////////////////////////////////////////////////////////////// + // Name from subtree (single relation labelled_by). + + // Gets the name from text nodes contained by nested elements + testName("btn_labelledby_mixed", "nomore text"); + + // Gets the name from text nodes contained by nested elements, ignores + // hidden elements (bug 443081). + testName("btn_labelledby_mixed_hidden_child", "nomore text2"); + + // Gets the name from hidden text nodes contained by nested elements, + // (label element is hidden entirely), (bug 443081). + testName("btn_labelledby_mixed_hidden", "lala more hidden text"); + + // Gets the name from text nodes contained by nested elements having block + // representation (every text node value in the name should be devided by + // spaces) + testName("btn_labelledby_mixed_block", "text more text"); + + // Gets the name from text nodes contained by html:td. The text nodes + // should not be divided by spaces. + testName("btn_labelledby_mixed_table", "textTEXTtext"); + + // Gets the name from image accessible. + testName("btn_labelledby_mixed_img", "text image"); + + // Gets the name from input accessibles + // Note: if input have label elements then the name isn't calculated + // from them. + testName("btn_labelledby_mixed_input", + "input button Submit Query Reset Submit Query"); + + // Gets the name from the title of object element. + testName("btn_labelledby_mixed_object", "object"); + + // Gets the name from text nodes. Element br adds space between them. + testName("btn_labelledby_mixed_br", "text text"); + + // Gets the name from label content which allows name from subtree, + // ignore @title attribute on label + testName("from_label_ignoretitle", "Country:"); + + // Gets the name from html:p content, which doesn't allow name from + // subtree, ignore @title attribute on label + testName("from_p_ignoretitle", "Choose country from."); + + // Gets the name from html:input value, ignore @title attribute on input + testName("from_input_ignoretitle", "Custom country"); + + // Insert spaces around the control's value to not jamm sibling text nodes + testName("insert_spaces_around_control", "start value end"); + + // Gets the name from @title, ignore whitespace content + testName("from_label_ignore_ws_subtree", "about"); + + // role="alert" doesn't get name from subtree... + testName("alert", null); + // but the subtree is used if referenced by aria-labelledby. + testName("inputLabelledByAlert", "Error"); + + // //////////////////////////////////////////////////////////////////////// + // label element + + // The label element contains the button. The name is calculated from + // this button. + // Note: the name contains the content of the button. + testName("btn_label_inside", "text10text"); + + // The label element and the button are placed in the same form. Gets + // the name from the label subtree. + testName("btn_label_inform", "in form"); + + // The label element is placed outside of form where the button is. + // Take into account the label. + testName("btn_label_outform", "out form"); + + // The label element and the button are in the same document. Gets the + // name from the label subtree. + testName("btn_label_indocument", "in document"); + + // Multiple label elements for single button + testName("btn_label_multi", "label1label2"); + + // Multiple controls inside a label element + testName("ctrl_in_label_1", "Enable a button control"); + testName("ctrl_in_label_2", "button"); + + + // //////////////////////////////////////////////////////////////////////// + // name from children + + // ARIA role button is presented allowing the name calculation from + // children. + testName("btn_children", "14"); + + // html:button, no name from content + testName("btn_nonamefromcontent", null); + + // ARIA role option is presented allowing the name calculation from + // visible children (bug 443081). + testName("lb_opt1_children_hidden", "i am visible"); + + // Get the name from subtree of menuitem crossing role nothing to get + // the name from its children. + testName("tablemenuitem", "menuitem 1"); + + // Get the name from child acronym title attribute rather than from + // acronym content. + testName("label_with_acronym", "O A T F World Wide Web"); + + testName("testArticle", "Test article"); + + testName("h1", "heading"); + testName("aria_heading", "aria_heading"); + + // //////////////////////////////////////////////////////////////////////// + // title attribute + + // If nothing is left. Let's try title attribute. + testName("btn_title", "title"); + + // //////////////////////////////////////////////////////////////////////// + // textarea name + + // textarea's name should have the value, which initially is specified by + // a text child. + testName("textareawithchild", "Story Foo is ended."); + + // new textarea name should reflect the value change. + var elem = document.getElementById("textareawithchild"); + elem.value = "Bar"; + + testName("textareawithchild", "Story Bar is ended."); + + // //////////////////////////////////////////////////////////////////////// + // controls having a value used as a part of computed name + + testName("ctrlvalue_progressbar:input", "foo 5 baz"); + testName("ctrlvalue_scrollbar:input", "foo 5 baz"); + testName("ctrlvalue_slider:input", "foo 5 baz"); + testName("ctrlvalue_spinbutton:input", "foo 5 baz"); + testName("ctrlvalue_combobox:input", "foo 5 baz"); + testName("ctrlvalue_meter:input", "foo 5 baz"); + + + // /////////////////////////////////////////////////////////////////////// + // label with nested combobox (test for 'f' item of name computation guide) + + testName("comboinstart", "One day(s)."); + testName("combo3", "day(s)."); + + testName("textboxinstart", "Two days."); + testName("textbox1", "days."); + + testName("comboinmiddle", "Subscribe to ATOM feed."); + testName("combo4", "Subscribe to ATOM feed."); + + testName("comboinmiddle2", "Play the Haliluya sound when new mail arrives"); + testName("combo5", null); // label isn't used as a name for control + testName("checkbox", "Play the Haliluya sound when new mail arrives"); + testName("comboinmiddle3", "Play the Haliluya sound when new mail arrives"); + testName("combo6", "Play the Haliluya sound when new mail arrives"); + + testName("comboinend", "This day was sunny"); + testName("combo7", "This day was"); + + testName("textboxinend", "This day was sunny"); + testName("textbox2", "This day was"); + + // placeholder + testName("ph_password", "a placeholder"); + testName("ph_text", "a placeholder"); + testName("ph_textarea", "a placeholder"); + testName("ph_text2", "a label"); + testName("ph_textarea2", "a label"); + testName("ph_text3", "a label"); + + // Test equation image + testName("img_eq", "x^2 + y^2 + z^2"); + testName("input_img_eq", "x^2 + y^2 + z^2"); + testName("txt_eq", "x^2 + y^2 + z^2"); + + // ////////////////////////////////////////////////////////////////////// + // tests for duplicate announcement of content + + testName("test_note", null); + + // ////////////////////////////////////////////////////////////////////// + // Tests for name from sub tree of tr element. + + // By default, we want no name. + testName("NoNameForTR", null); + testName("NoNameForNonStandardTR", null); + + // But we want it if the tr has an ARIA role. + testName("NameForTRBecauseStrongARIA", "a b"); + + // But not if it is a weak (landmark) ARIA role + testName("NoNameForTRBecauseWeakARIA", null); + + // Name from sub tree of grouping if requested by other accessible. + testName("grouping", null); + testName("requested_name_from_grouping", "label"); + testName("listitem_containing_block_tbody", "label"); + // Groupings shouldn't be included when calculating from the subtree of + // a treeitem. + testName("treeitem_containing_grouping", "root"); + + // Name from subtree of grouping labelled by an ancestor. + testName("grouping_labelledby_ancestor", "label"); + + // Name from subtree of a container containing text nodes and inline + // elements. There should be no spaces because everyhing is inline. + testName("container_text_inline", "abc"); + // Name from subtree of a container containing text nodes and block + // elements. There should be a space on both sides of the block. + testName("container_text_block", "a b c"); + // Name from subtree of a container containing text nodes and empty + // block elements. There should be space on either side of the blocks, but + // not a double space. + testName("container_text_emptyblock", "a b"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=428479" + title="Bug 428479 - Support ARIA role=math"> + Bug 428479 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=429666" + title="Expose ROLE_DOCUMENT for ARIA landmarks that inherit from document"> + Bug 429666 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=444279" + title="mochitest for accessible name calculating"> + Bug 444279 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=459635" + title="nsIAccessible::name calculation for HTML buttons"> + Bug 459635 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=530081" + title="Clean up our tree walker"> + Bug 530081 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=604391" + title="Use placeholder as name if name is otherwise empty"> + Bug 604391 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=669312" + title="Accessible name is duplicated when input has a label associated uisng for/id and is wrapped around the input"> + Bug 669312 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=704416" + title="HTML acronym and abbr names should be provided by @title"> + Bug 704416 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=812041" + title="ARIA slider and spinbutton don't provide a value for name computation"> + Bug 812041 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=823927" + title="Text is jammed with control's text in name computation"> + Bug 823927 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=835666" + title="ARIA combobox selected value is not a part of name computation"> + Bug 835666 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=833256" + title="role note shouldn't pick up the name from subtree"> + Mozilla Bug 833256 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- aria-label, simple label --> + <span id="btn_simple_aria_label" role="button" aria-label="I am a button"/> + <br/> + <!-- aria-label plus aria-labelledby --> + <span id="btn_both_aria_labels" role="button" aria-label="I am a button, two" + aria-labelledby="labelledby_text btn_both_aria_labels"/> + <br/> + + <!-- aria-labelledby, single relation --> + <span id="labelledby_text">text</span> + <button id="btn_labelledby_text" + aria-labelledby="labelledby_text">1</button> + <br/> + + <!-- aria-labelledby, multiple relations --> + <span id="labelledby_text1">text1</span> + <span id="labelledby_text2">text2</span> + <button id="btn_labelledby_texts" + aria-labelledby="labelledby_text1 labelledby_text2">2</button> + <br/> + + <!-- name from named accessible --> + <input id="labelledby_namedacc" type="checkbox" + aria-label="Data" /> + <input id="input_labelledby_namedacc" + aria-labelledby="labelledby_namedacc" /> + + <!-- the name from subtree, mixed content --> + <span id="labelledby_mixed">no<span>more text</span></span> + <button id="btn_labelledby_mixed" + aria-labelledby="labelledby_mixed">3</button> + <br/> + + <!-- the name from subtree, mixed/hidden content --> + <span id="labelledby_mixed_hidden_child"> + no<span>more + <span style="display: none;">hidden</span> + text2 + <span style="visibility: hidden">hidden2</span> + </span> + </span> + <button id="btn_labelledby_mixed_hidden_child" + aria-labelledby="labelledby_mixed_hidden_child">3.1</button> + <br/> + + <!-- the name from subtree, mixed/completely hidden content --> + <span id="labelledby_mixed_hidden" + style="display: none;">lala <span>more hidden </span>text</span></span> + <button id="btn_labelledby_mixed_hidden" + aria-labelledby="labelledby_mixed_hidden">3.2</button> + <br/> + + <!-- the name from subtree, mixed content, block structure --> + <div id="labelledby_mixed_block"><div>text</div>more text</div></div> + <button id="btn_labelledby_mixed_block" + aria-labelledby="labelledby_mixed_block">4</button> + <br/> + + <!-- the name from subtree, mixed content, table structure --> + <table><tr> + <td id="labelledby_mixed_table">text<span>TEXT</span>text</td> + </tr></table> + <button id="btn_labelledby_mixed_table" + aria-labelledby="labelledby_mixed_table">5</button> + <br/> + + <!-- the name from subtree, child img --> + <span id="labelledby_mixed_img">text<img alt="image"/></span> + <button id="btn_labelledby_mixed_img" + aria-labelledby="labelledby_mixed_img">6</button> + <br/> + + <!-- the name from subtree, child inputs --> + <span id="labelledby_mixed_input"> + <input type="button" id="input_button" title="input button"/> + <input type="submit" id="input_submit"/> + <input type="reset" id="input_reset"/> + <input type="image" id="input_image" title="input image"/> + </span> + <button id="btn_labelledby_mixed_input" + aria-labelledby="labelledby_mixed_input">7</button> + <br/> + + <!-- the name from subtree, child object --> + <span id="labelledby_mixed_object"> + <object data="about:blank" title="object"></object> + </span> + <button id="btn_labelledby_mixed_object" + aria-labelledby="labelledby_mixed_object">8</button> + <br/> + + <!-- the name from subtree, child br --> + <span id="labelledby_mixed_br">text<br/>text</span> + <button id="btn_labelledby_mixed_br" + aria-labelledby="labelledby_mixed_br">9</button> + <br/> + + <!-- the name from subtree, name from label content rather than from its title + attribute --> + <label for="from_label_ignoretitle" + title="Select your country of origin">Country:</label> + <select id="from_label_ignoretitle"> + <option>Germany</option> + <option>Russia</option> + </select> + + <!-- the name from subtree, name from html:p content rather than from its + title attribute --> + <p id="p_ignoretitle" + title="Select your country of origin">Choose country from.</p> + <select id="from_p_ignoretitle" aria-labelledby="p_ignoretitle"> + <option>Germany</option> + <option>Russia</option> + </select> + + <!-- the name from subtree, name from html:input value rather than from its + title attribute --> + <p id="from_input_ignoretitle" aria-labelledby="input_ignoretitle">Country</p> + <input id="input_ignoretitle" + value="Custom country" + title="Input your country of origin"/ > + + <!-- name from subtree, surround control by spaces to not jamm the text --> + <label id="insert_spaces_around_control"> + start<input value="value">end + </label> + + <!-- no name from subtree because it holds whitespaces only --> + <a id="from_label_ignore_ws_subtree" href="about:mozilla" title="about"> </a> + + <!-- Don't use subtree unless referenced by aria-labelledby. --> + <div id="alert" role="alert">Error</div> + <input type="text" id="inputLabelledByAlert" aria-labelledby="alert"> + + <!-- label element, label contains control --> + <label>text<button id="btn_label_inside">10</button>text</label> + <br/> + + <!-- label element, label and the button are in the same form --> + <form> + <label for="btn_label_inform">in form</label> + <button id="btn_label_inform">11</button> + </form> + + <!-- label element, label is outside of the form of the button --> + <label for="btn_label_outform">out form</label> + <form> + <button id="btn_label_outform">12</button> + </form> + + <!-- label element, label and the button are in the same document --> + <label for="btn_label_indocument">in document</label> + <button id="btn_label_indocument">13</button> + + <!-- multiple label elements for single button --> + <label for="btn_label_multi">label1</label> + <label for="btn_label_multi">label2</label> + <button id="btn_label_multi">button</button> + + <!-- a label containing more than one controls --> + <label> + Enable <input id="ctrl_in_label_1" type="checkbox"> a + <input id="ctrl_in_label_2" type="button" value="button"> control + </label> + + <!-- name from children --> + <span id="btn_children" role="button">14</span> + + <!-- no name from content, ARIA role overrides this rule --> + <button id="btn_nonamefromcontent" role="img">1</button> + + <!-- name from children, hidden children --> + <div role="listbox" tabindex="0"> + <div id="lb_opt1_children_hidden" role="option" tabindex="0"> + <span>i am visible</span> + <span style="display:none">i am hidden</span> + </div> + </div> + + <table role="menu"> + <tr role="menuitem" id="tablemenuitem"> + <td>menuitem 1</td> + </tr> + <tr role="menuitem"> + <td>menuitem 2</td> + </tr> + </table> + + <label id="label_with_acronym"> + <acronym title="O A T F">OATF</acronym> + <abbr title="World Wide Web">WWW</abbr> + </label> + + <div id="testArticle" role="article" title="Test article"> + <p>This is a paragraph inside the article.</p> + </div> + + <h1 id="h1" title="oops">heading</h1> + <div role="heading" id="aria_heading">aria_heading</div> + + <!-- name from title attribute --> + <span id="btn_title" role="group" title="title">15</span> + + <!-- A textarea nested in a label with a text child (bug #453371). --> + <form> + <label>Story + <textarea id="textareawithchild" name="name">Foo</textarea> + is ended. + </label> + </form> + + <!-- controls having a value used as part of computed name --> + <input type="checkbox" id="ctrlvalue_progressbar:input"> + <label for="ctrlvalue_progressbar:input"> + foo <span role="progressbar" + aria-valuenow="5" aria-valuemin="1" + aria-valuemax="10">5</span> baz + </label> + + <input type="checkbox" id="ctrlvalue_scrollbar:input" /> + <label for="ctrlvalue_scrollbar:input"> + foo <span role="scrollbar" + aria-valuenow="5" aria-valuemin="1" + aria-valuemax="10">5</span> baz + </label> + + <input type="checkbox" id="ctrlvalue_slider:input"> + <label for="ctrlvalue_slider:input"> + foo <input role="slider" type="range" + value="5" min="1" max="10" + aria-valuenow="5" aria-valuemin="1" + aria-valuemax="10"> baz + </label> + + <input type="checkbox" id="ctrlvalue_spinbutton:input"> + <label for="ctrlvalue_spinbutton:input"> + foo <input role="spinbutton" type="number" + value="5" min="1" max="10" + aria-valuenow="5" aria-valuemin="1" + aria-valuemax="10"> + baz + </label> + + <input type="checkbox" id="ctrlvalue_combobox:input"> + <label for="ctrlvalue_combobox:input"> + foo + <div role="combobox"> + <div role="textbox"></div> + <ul role="listbox" style="list-style-type: none;"> + <li role="option">1</li> + <li role="option" aria-selected="true">5</li> + <li role="option">3</li> + </ul> + </div> + baz + </label> + + <input type="checkbox" id="ctrlvalue_meter:input"> + <label for="ctrlvalue_meter:input"> + foo + <meter>5</meter> + </div> + baz + </label> + + <!-- a label with a nested control in the start, middle and end --> + <form> + <!-- at the start (without and with whitespaces) --> + <label id="comboinstart"><select id="combo3"> + <option>One</option> + <option>Two</option> + </select> + day(s). + </label> + + <label id="textboxinstart"> + <input id="textbox1" value="Two"> + days. + </label> + + <!-- in the middle --> + <label id="comboinmiddle"> + Subscribe to + <select id="combo4" name="occupation"> + <option>ATOM</option> + <option>RSS</option> + </select> + feed. + </label> + + <label id="comboinmiddle2" for="checkbox">Play the + <select id="combo5"> + <option>Haliluya</option> + <option>Hurra</option> + </select> + sound when new mail arrives + </label> + <input id="checkbox" type="checkbox" /> + + <label id="comboinmiddle3" for="combo6">Play the + <select id="combo6"> + <option>Haliluya</option> + <option>Hurra</option> + </select> + sound when new mail arrives + </label> + + <!-- at the end (without and with whitespaces) --> + <label id="comboinend"> + This day was + <select id="combo7" name="occupation"> + <option>sunny</option> + <option>rainy</option> + </select></label> + + <label id="textboxinend"> + This day was + <input id="textbox2" value="sunny"> + </label> + </form> + + <!-- placeholder --> + <input id="ph_password" type="password" value="" placeholder="a placeholder" /> + <input id="ph_text" type="text" placeholder="a placeholder" /> + <textarea id="ph_textarea" cols="5" placeholder="a placeholder"></textarea> + + <!-- placeholder does not win --> + <input id="ph_text2" type="text" aria-label="a label" placeholder="meh" /> + <textarea id="ph_textarea2" cols="5" aria-labelledby="ph_text2" + placeholder="meh"></textarea> + + <label for="ph_text3">a label</label> + <input id="ph_text3" placeholder="meh" /> + + <p>Image: + <img id="img_eq" role="math" src="foo" alt="x^2 + y^2 + z^2"> + <input type="image" id="input_img_eq" src="foo" alt="x^2 + y^2 + z^2"> + </p> + + <p>Text: + <span id="txt_eq" role="math" title="x^2 + y^2 + z^2">x<sup>2</sup> + + y<sup>2</sup> + z<sup>2</sup></span> + + <!-- duplicate announcement --> + <div id="test_note" role="note">subtree</div> + + <!-- No name for tr from its sub tree --> + <table><tr id="NoNameForTR"><th>a</th><td>b</td></tr></table> + <table style="display: block;"> + <tr id="NoNameForNonStandardTR" style="display:block;"> + <th>a</th><td>b</td> + </tr> + </table> + + <!-- Name from sub tree of tr, because it has a strong ARIA role --> + <table><tr id="NameForTRBecauseStrongARIA" role="row"><th>a</th><td>b</td></tr></table> + + <!-- No name for tr because of weak (landmark) role --> + <table><tr id="NoNameForTRBecauseWeakARIA" role="main"><th>a</th><td>b</td></tr></table> + + <!-- Name from subtree of grouping if requested by other object --> + <div id="grouping" role="group">label</div> + <button id="requested_name_from_grouping"aria-labelledby="grouping"></button> + <!-- Name from sub tree of tbody marked as display:block;, which is also a grouping --> + <div id="listitem_containing_block_tbody" role="listitem"> + <table> + <tbody style="display: block;"> + <tr><td>label</td></tr> + </tbody> + </table> + </div> + <!-- Name from subtree of treeitem containing grouping --> + <div id="treeitem_containing_grouping" role="treeitem" aria-level="1" aria-expanded="true">root + <div role="group"> + <div role="treeitem" aria-level="2">sub</div> + </div> + </div> + + <!-- Name from subtree of grouping labelled by an ancestor. --> + <div id="grouping_ancestor_label">label + <div id="grouping_labelledby_ancestor" role="group" aria-labelledby="grouping_ancestor_label"> + This content should not be included in the grouping's label. + </div> + </div> + + <!-- Text nodes and inline elements. --> + <div id="container_text_inline" role="option">a<strong>b</strong>c</div> + <!-- Text nodes and block elements. --> + <div id="container_text_block" role="option">a<p>b</p>c</div> + <!-- Text nodes and empty block elements. --> + <div id="container_text_emptyblock" role="option">a<p></p><p></p>b</div> +</body> +</html> diff --git a/accessible/tests/mochitest/name/test_general.xhtml b/accessible/tests/mochitest/name/test_general.xhtml new file mode 100644 index 0000000000..a1c6f1461e --- /dev/null +++ b/accessible/tests/mochitest/name/test_general.xhtml @@ -0,0 +1,343 @@ +<?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" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Accessibility Name Calculating Test."> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../name.js"></script> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + // aria-label + + // Simple label provided via ARIA + testName("btn_simple_aria_label", "I am a button"); + + // aria-label and aria-labelledby, expect aria-labelledby + testName("btn_both_aria_labels", "text I am a button, two"); + + ////////////////////////////////////////////////////////////////////////// + // aria-labelledby + + // Single relation. The value of 'aria-labelledby' contains the ID of + // an element. Gets the name from text node of that element. + testName("btn_labelledby_text", "text"); + + // Multiple relations. The value of 'aria-labelledby' contains the IDs + // of elements. Gets the name from text nodes of those elements. + testName("btn_labelledby_texts", "text1 text2"); + + // Trick cases. Self and recursive referencing. + testName("rememberHistoryDays", "Remember 3 days"); + testName("historyDays", "Remember 3 days"); + testName("rememberAfter", "days"); + + ////////////////////////////////////////////////////////////////////////// + // Name from subtree (single relation labelled_by). + + // Gets the name from text nodes contained by nested elements. + testName("btn_labelledby_mixed", "no more text"); + + // Gets the name from text nodes and selected item of menulist + // (other items are ignored). + testName("btn_labelledby_mixed_menulist", + "no more text selected item more text"); + + // Gets the name from text nodes contained by nested elements, ignores + // hidden elements (bug 443081). + testName("btn_labelledby_mixed_hidden_child", "no more text2"); + + // Gets the name from hidden text nodes contained by nested elements, + // (label element is hidden entirely), (bug 443081) + testName("btn_labelledby_mixed_hidden", "lala more hidden text"); + + + ////////////////////////////////////////////////////////////////////////// + // Name from @label attribute. + + // Gets the name from @label attribute. + testName("btn_labelattr", "labeled element"); + + + ////////////////////////////////////////////////////////////////////////// + // Name for nsIDOMXULSelectControlItemElement. + + // Gets the name from @label attribute. + testName("li_nsIDOMXULSelectControlItemElement", "select control item"); + + + ////////////////////////////////////////////////////////////////////////// + // Name if the XUL element doesn't implement nsIDOMXULSelectControlElement + // and has @label attribute. + + testName("box_not_nsIDOMXULSelectControlElement", "box"); + + + ////////////////////////////////////////////////////////////////////////// + // Name from the label element. + + // The label and button are placed on 2nd level relative common parent. + testName("btn_label_1", "label1"); + + // The label is on 1st, the button is on 5th level relative common parent. + testName("btn_label_2", "label2"); + + // The label and button are siblings. + testName("btn_label_3", "label3"); + + // Multiple labels for single button: XUL button takes the last one. + testName("btn_label_4", "label5"); + + // Label associated with HTML element. + testName("input_label", "input label"); + + + ////////////////////////////////////////////////////////////////////////// + // tooltiptext (if nothing above isn't presented then tooltiptext is used) + testName("box_tooltiptext", "tooltiptext label"); + + + ////////////////////////////////////////////////////////////////////////// + // Name from the @title attribute of <toolbaritem/> (original bug 237249). + + // Direct child of toolbaritem. + testName("toolbaritem_child", null); + + // Child from subtree of toolbaritem. + testName("toolbaritem_hboxbutton", "button"); + + + ////////////////////////////////////////////////////////////////////////// + // name from label inside toolbar button + testName("toolbarbuttonwithlabel", "I am the button"); + + + ////////////////////////////////////////////////////////////////////////// + // Name from children + + // ARIA role button is presented allowing the name calculation from + // children. + testName("box_children", "14"); + + // Button labelled by a text child. + testName("button_text", "Text"); + + // ARIA role option is presented allowing the name calculation from + // the visible children (bug 443081) + testName("lb_opt1_children_hidden", "i am visible"); + + + ////////////////////////////////////////////////////////////////////////// + // Name from aria-labelledby: menuitem label+ listitem label + testName("li_labelledby", "Show an Alert The moment the event starts"); + + ////////////////////////////////////////////////////////////////////////// + // groupbox labeling from first label + testName("groupbox", "Some caption"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=444279" + title="mochitest for accessible name calculating"> + Mozilla Bug 444279 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=441991" + title="nsXULListitemAccessible::GetName prefers label \ + attribute over aria-labelledby and doesn't allow recursion"> + Mozilla Bug 441991 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <!-- aria-label, simple label --> + <button id="btn_simple_aria_label" aria-label="I am a button"/> + + <!-- aria-label plus aria-labelledby --> + <button id="btn_both_aria_labels" aria-label="I am a button, two" + aria-labelledby="labelledby_text btn_both_aria_labels"/> + + <!-- aria-labelledby, single relation --> + <description id="labelledby_text">text</description> + <button id="btn_labelledby_text" + aria-labelledby="labelledby_text"/> + + <!-- aria-labelledby, multiple relations --> + <description id="labelledby_text1">text1</description> + <description id="labelledby_text2">text2</description> + <button id="btn_labelledby_texts" + aria-labelledby="labelledby_text1 labelledby_text2"/> + + <!-- trick aria-labelledby --> + <checkbox id="rememberHistoryDays" + label="Remember " + aria-labelledby="rememberHistoryDays historyDays rememberAfter"/> + <html:input id="historyDays" value="3" + aria-labelledby="rememberHistoryDays historyDays rememberAfter"/> + <label id="rememberAfter">days</label> + + <!-- the name from subtree, mixed content --> + <description id="labelledby_mixed"> + no<description>more text</description> + </description> + <button id="btn_labelledby_mixed" + aria-labelledby="labelledby_mixed"/> + + <!-- the name from subtree, mixed/hidden content --> + <description id="labelledby_mixed_hidden_child">no<description>more <description hidden="true">hidden</description>text2</description></description> + <button id="btn_labelledby_mixed_hidden_child" + aria-labelledby="labelledby_mixed_hidden_child"/> + + <!-- the name from subtree, mixed/completely hidden content --> + <description id="labelledby_mixed_hidden" + hidden="true">lala <description>more hidden </description>text</description> + <button id="btn_labelledby_mixed_hidden" + aria-labelledby="labelledby_mixed_hidden"/> + <br/> + + <!-- the name from subtree, mixed content, ignore items of menulist --> + <description id="labelledby_mixed_menulist"> + no<description>more text</description> + <menulist> + <menupopup> + <menuitem label="selected item"/> + <menuitem label="item"/> + </menupopup> + </menulist> + more text + </description> + <button id="btn_labelledby_mixed_menulist" + aria-labelledby="labelledby_mixed_menulist"/> + + <!-- @label --> + <button id="btn_labelattr" + label="labeled element"/> + + <!-- nsIDOMXULSelectControlItemElement --> + <richlistbox> + <richlistitem id="li_nsIDOMXULSelectControlItemElement"> + <label value="select control item"/> + </richlistitem> + </richlistbox> + + <!-- not nsIDOMXULSelectControlElement --> + <box id="box_not_nsIDOMXULSelectControlElement" role="group" label="box"/> + + <!-- label element --> + <hbox> + <box> + <label control="btn_label_1">label1</label> + </box> + <label control="btn_label_2">label2</label> + <box> + <button id="btn_label_1"/> + <box> + <box> + <box> + <button id="btn_label_2"/> + </box> + </box> + </box> + </box> + <label control="btn_label_3">label3</label> + <button id="btn_label_3"/> + + <label control="btn_label_4">label4</label> + <label control="btn_label_4">label5</label> + <button id="btn_label_4"/> + + <label control="input_label">input label</label> + <html:input id="input_label"/> + </hbox> + + <!-- tooltiptext --> + <box id="box_tooltiptext" + role="group" + tooltiptext="tooltiptext label"/> + + <!-- the name from @title of toolbaritem --> + <!-- and the name from label of a toolbarbutton --> + <toolbar> + <toolbaritem title="ooospspss"> + <box id="toolbaritem_child" + role="group" + flex="1"> + <hbox role="button" id="toolbaritem_hboxbutton"> + <description value="button"/> + </hbox> + </box> + </toolbaritem> + <toolbarbutton id="toolbarbuttonwithlabel"> + <label flex="1">I am the button</label> + </toolbarbutton> + </toolbar> + + <!-- name from children --> + <box id="box_children" role="button">14</box> + <button id="button_text">Text</button> + + <!-- name from children, hidden children --> + <vbox role="listbox" tabindex="0"> + <hbox id="lb_opt1_children_hidden" role="option" tabindex="0"> + <description>i am visible</description> + <description style="display:none">i am hidden</description> + </hbox> + + <!-- Name from caption sub tree --> + <groupbox id="groupbox"> + <label>Some caption</label> + <checkbox label="some checkbox label" /> + </groupbox> + </vbox> + + <!-- bug 441991; create name from other menuitem label listitem's own label --> + <hbox> + <richlistbox> + <richlistitem id="li_labelledby" + aria-labelledby="menuitem-DISPLAY li_labelledby"> + <label value="The moment the event starts"/> + </richlistitem> + </richlistbox> + <menulist> + <menupopup> + <menuitem id="menuitem-DISPLAY" + value="DISPLAY" + label="Show an Alert"/> + <menuitem id="menuitem-EMAIL" + value="EMAIL" + label="Send an E-mail"/> + </menupopup> + </menulist> + </hbox> + + </vbox> <!-- close tests area --> + </hbox> <!-- close main area --> +</window> diff --git a/accessible/tests/mochitest/name/test_link.html b/accessible/tests/mochitest/name/test_link.html new file mode 100644 index 0000000000..6a289dd44f --- /dev/null +++ b/accessible/tests/mochitest/name/test_link.html @@ -0,0 +1,87 @@ +<html> + +<head> + <title>nsIAccessible::name calculation for HTML links (html:a)</title> + + <link rel="stylesheet" + type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../name.js"></script> + + <script type="application/javascript"> + function doTest() { + // aria-label + testName("aria_label", "anchor label"); + + // aria-labelledby + testName("aria_labelledby", "text"); + + // name from content + testName("namefromcontent", "1"); + + // name from content + testName("namefromimg", "img title"); + + // no name from content + testName("nonamefromcontent", null); + + // title + testName("title", "title"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=459782" + title="nsIAccessible::name calculation for HTML links (html:a)"> + Mozilla Bug 459782 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- aria-label --> + <a id="aria_label" href="mozilla.org" + aria-label="anchor label">1</a> + <br/> + + <!-- aria-labelledby, preferred to html:label --> + <span id="text">text</span> + <label for="aria_labelledby">label</label> + <a id="aria_labelledby" href="mozilla.org" + aria-labelledby="text">1</a> + <br/> + + <!-- name from content, preferred to @title --> + <a id="namefromcontent" href="mozilla.org" + title="title">1</a> + <br/> + + <!-- name from content, preferred to @title --> + <a id="namefromimg" href="mozilla.org" + title="title"><img alt="img title" /></a> + + <!-- no name from content, ARIA role overrides this rule --> + <a id="nonamefromcontent" href="mozilla.org" role="img">1</a> + <br/> + + <!-- no content, name from @title --> + <a id="title" href="mozilla.org" + title="title"></a> + +</body> +</html> diff --git a/accessible/tests/mochitest/name/test_list.html b/accessible/tests/mochitest/name/test_list.html new file mode 100644 index 0000000000..95f0c06d2a --- /dev/null +++ b/accessible/tests/mochitest/name/test_list.html @@ -0,0 +1,103 @@ +<html> + +<head> + <title>nsIAccessible::name calculation for HTML li</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../name.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + /** + * Alter list item numbering and change list style type. + */ + function bulletUpdate() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode("list")), + ]; + + this.invoke = function bulletUpdate_invoke() { + testName("li_end", "1. list end"); + + var li = document.createElement("li"); + li.setAttribute("id", "li_start"); + li.textContent = "list start"; + getNode("list").insertBefore(li, getNode("li_end")); + }; + + this.finalCheck = function bulletUpdate_finalCheck() { + testName("li_start", "1. list start"); + testName("li_end", "2. list end"); + }; + + this.getID = function bulletUpdate_getID() { + return "insertBefore new list item"; + }; + } + function bulletUpdate2() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode("li_end")), + ]; + + this.invoke = function bulletUpdate2_invoke() { + // change list style type + var list = getNode("list"); + list.setAttribute("style", "list-style-type: disc;"); + + // Flush both the style change and the resulting layout change. + // Flushing style on its own is not sufficient, because that can + // leave frames marked with NS_FRAME_IS_DIRTY, which will cause + // nsTextFrame::GetRenderedText to report the text of a text + // frame is empty. + list.offsetWidth; // flush layout (which also flushes style) + }; + + this.finalCheck = function bulletUpdate2_finalCheck() { + testName("li_start", kDiscBulletText + "list start"); + testName("li_end", kDiscBulletText + "list end"); + }; + + this.getID = function bulletUpdate2_getID() { + return "Update list item style"; + }; + } + + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + gQueue.push(new bulletUpdate()); + gQueue.push(new bulletUpdate2()); + gQueue.invoke(); // SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=634200" + title="crash [@ nsIFrame::StyleVisibility() ]"> + Mozilla Bug 634200 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <ol id="list"> + <li id="li_end">list end</li> + </ol> + +</body> +</html> diff --git a/accessible/tests/mochitest/name/test_markup.html b/accessible/tests/mochitest/name/test_markup.html new file mode 100644 index 0000000000..735027f44f --- /dev/null +++ b/accessible/tests/mochitest/name/test_markup.html @@ -0,0 +1,58 @@ +<html> + +<head> + <title>nsIAccessible::name calculation for elements</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../name.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript" + src="markup.js"></script> + + <script type="application/javascript"> + // gA11yEventDumpID = "eventdump"; + // gDumpToConsole = true; + // gA11yEventDumpToConsole = true; + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(testNames); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=459635" + title="nsIAccessible::name calculation for elements"> + Bug 459635 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=666212" + title="summary attribute content mapped to accessible name in MSAA"> + Bug 666212 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=786163" + title=" Sort out name calculation for HTML input buttons"> + Bug 786163 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/name/test_svg.html b/accessible/tests/mochitest/name/test_svg.html new file mode 100644 index 0000000000..535fcdbf20 --- /dev/null +++ b/accessible/tests/mochitest/name/test_svg.html @@ -0,0 +1,53 @@ +<html> + +<head> + <title>Accessible name and description for SVG elements</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../name.js"></script> + + <script type="application/javascript"> + + function doTest() { + testName("svg1", "A name"); + testDescr("svg1", "A description"); + testName("svg2", "A tooltip"); + testDescr("svg2", ""); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=459357" + title="Support accessible name computation for SVG"> + Mozilla Bug 459357 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg1"> + <title>A name</title> + <desc>A description</title> + </svg> + + <svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg2"> + <desc>A tooltip</desc> + </svg> +</body> +</html> diff --git a/accessible/tests/mochitest/name/test_tree.xhtml b/accessible/tests/mochitest/name/test_tree.xhtml new file mode 100644 index 0000000000..3564481d00 --- /dev/null +++ b/accessible/tests/mochitest/name/test_tree.xhtml @@ -0,0 +1,207 @@ +<?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="Accessibility Name Calculating Test."> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../name.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + <![CDATA[ + function treeTester(aID) + { + this.DOMNode = getNode(aID); + + this.invoke = function treeTester_invoke() + { + this.DOMNode.view = new nsTreeTreeView(); + } + + this.check = function treeTester_check(aEvent) + { + var tree = { + role: ROLE_OUTLINE, + children: [ + { + role: ROLE_LIST + }, + { + role: ROLE_OUTLINEITEM, + children: [], + name: "row1col" + }, + { + role: ROLE_OUTLINEITEM, + children: [], + name: "row2_col" + }, + { + role: ROLE_OUTLINEITEM, + children: [], + name: "row2.1_col" + }, + { + role: ROLE_OUTLINEITEM, + children: [], + name: "row2.2_col" + }, + { + role: ROLE_OUTLINEITEM, + children: [], + name: "row3_col" + }, + { + role: ROLE_OUTLINEITEM, + children: [], + name: "row4col" + } + ] + }; + testAccessibleTree(this.DOMNode, tree); + } + + this.getID = function treeTester_getID() + { + return "Tree name testing for " + aID; + } + } + + function tableTester(aID, aIsTable, aCol1ID, aCol2ID) + { + this.DOMNode = getNode(aID); + + this.invoke = function tableTester_invoke() + { + this.DOMNode.view = new nsTableTreeView(2); + } + + this.check = function tableTester_check(aEvent) + { + var tree = { + role: aIsTable ? ROLE_TABLE : ROLE_TREE_TABLE, + children: [ + { + role: ROLE_LIST + }, + { + role: ROLE_ROW, + children: [ + { + role: ROLE_GRID_CELL, + children: [], + name: "row0_" + aCol1ID + }, + { + role: ROLE_GRID_CELL, + children: [], + name: "row0_" + aCol2ID + } + ], + name: "row0_" + aCol1ID + " row0_" + aCol2ID + }, + { + role: ROLE_ROW, + children: [ + { + role: ROLE_GRID_CELL, + children: [], + name: "row1_" + aCol1ID + }, + { + role: ROLE_GRID_CELL, + children: [], + name: "row1_" + aCol2ID + } + ], + name: "row1_" + aCol1ID + " row1_" + aCol2ID + } + ] + }; + testAccessibleTree(this.DOMNode, tree); + } + + this.getID = function tableTester_getID() + { + return "Tree name testing for " + aID; + } + } + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(EVENT_REORDER); + + gQueue.push(new treeTester("tree")); + gQueue.push(new tableTester("table", true, "t_col1", "t_col2")); + gQueue.push(new tableTester("treetable", false, "tt_col1", "tt_col2")); + + gQueue.invoke(); // Will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=546812" + title="Treegrid row accessible shouldn't inherit name from tree accessible"> + Mozilla Bug 546812 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=664376" + title="Table rows of XUL trees no longer containing cell content as accessible name"> + Mozilla Bug 664376 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + </treecols> + <treechildren/> + </tree> + + <tree id="table" flex="1"> + <treecols> + <treecol id="t_col1" flex="1" label="column"/> + <treecol id="t_col2" flex="1" label="column 2"/> + </treecols> + <treechildren/> + </tree> + + <tree id="treetable" flex="1"> + <treecols> + <treecol id="tt_col1" flex="1" label="column" primary="true"/> + <treecol id="tt_col2" flex="1" label="column 2"/> + </treecols> + <treechildren/> + </tree> + + </vbox> <!-- close tests area --> + </hbox> <!-- close main area --> +</window> diff --git a/accessible/tests/mochitest/pivot.js b/accessible/tests/mochitest/pivot.js new file mode 100644 index 0000000000..9e43134add --- /dev/null +++ b/accessible/tests/mochitest/pivot.js @@ -0,0 +1,664 @@ +/* import-globals-from common.js */ +/* import-globals-from events.js */ +/* import-globals-from role.js */ +/* import-globals-from states.js */ +/* import-globals-from text.js */ + +// ////////////////////////////////////////////////////////////////////////////// +// Constants + +const PREFILTER_INVISIBLE = nsIAccessibleTraversalRule.PREFILTER_INVISIBLE; +const PREFILTER_TRANSPARENT = nsIAccessibleTraversalRule.PREFILTER_TRANSPARENT; +const FILTER_MATCH = nsIAccessibleTraversalRule.FILTER_MATCH; +const FILTER_IGNORE = nsIAccessibleTraversalRule.FILTER_IGNORE; +const FILTER_IGNORE_SUBTREE = nsIAccessibleTraversalRule.FILTER_IGNORE_SUBTREE; +const NO_BOUNDARY = nsIAccessiblePivot.NO_BOUNDARY; +const CHAR_BOUNDARY = nsIAccessiblePivot.CHAR_BOUNDARY; +const WORD_BOUNDARY = nsIAccessiblePivot.WORD_BOUNDARY; +const LINE_BOUNDARY = nsIAccessiblePivot.LINE_BOUNDARY; + +const NS_ERROR_NOT_IN_TREE = 0x80780026; +const NS_ERROR_INVALID_ARG = 0x80070057; + +// ////////////////////////////////////////////////////////////////////////////// +// Traversal rules + +/** + * Rule object to traverse all focusable nodes and text nodes. + */ +var HeadersTraversalRule = { + getMatchRoles() { + return [ROLE_HEADING]; + }, + + preFilter: PREFILTER_INVISIBLE, + + match(aAccessible) { + return FILTER_MATCH; + }, + + QueryInterface: ChromeUtils.generateQI([nsIAccessibleTraversalRule]), +}; + +/** + * Traversal rule for all focusable nodes or leafs. + */ +var ObjectTraversalRule = { + getMatchRoles() { + return []; + }, + + preFilter: PREFILTER_INVISIBLE | PREFILTER_TRANSPARENT, + + match(aAccessible) { + var rv = FILTER_IGNORE; + var role = aAccessible.role; + if ( + hasState(aAccessible, STATE_FOCUSABLE) && + role != ROLE_DOCUMENT && + role != ROLE_INTERNAL_FRAME + ) { + rv = FILTER_IGNORE_SUBTREE | FILTER_MATCH; + } else if ( + aAccessible.childCount == 0 && + role != ROLE_LISTITEM_MARKER && + aAccessible.name.trim() + ) { + rv = FILTER_MATCH; + } + + return rv; + }, + + QueryInterface: ChromeUtils.generateQI([nsIAccessibleTraversalRule]), +}; + +// ////////////////////////////////////////////////////////////////////////////// +// Virtual state invokers and checkers + +/** + * A checker for virtual cursor changed events. + */ +function VCChangedChecker( + aDocAcc, + aIdOrNameOrAcc, + aTextOffsets, + aPivotMoveMethod, + aIsFromUserInput, + aBoundaryType = NO_BOUNDARY +) { + this.__proto__ = new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc); + + this.match = function VCChangedChecker_match(aEvent) { + var event = null; + try { + event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent); + } catch (e) { + return false; + } + + var expectedReason = + VCChangedChecker.methodReasonMap[aPivotMoveMethod] || + nsIAccessiblePivot.REASON_NONE; + + return ( + event.reason == expectedReason && event.boundaryType == aBoundaryType + ); + }; + + this.check = function VCChangedChecker_check(aEvent) { + SimpleTest.info("VCChangedChecker_check"); + + var event = null; + try { + event = aEvent.QueryInterface(nsIAccessibleVirtualCursorChangeEvent); + } catch (e) { + SimpleTest.ok(false, "Does not support correct interface: " + e); + } + + var position = aDocAcc.virtualCursor.position; + var idMatches = position && position.DOMNode.id == aIdOrNameOrAcc; + var nameMatches = position && position.name == aIdOrNameOrAcc; + var accMatches = position == aIdOrNameOrAcc; + + SimpleTest.ok( + idMatches || nameMatches || accMatches, + "id or name matches - expecting " + + prettyName(aIdOrNameOrAcc) + + ", got '" + + prettyName(position) + ); + + SimpleTest.is( + aEvent.isFromUserInput, + aIsFromUserInput, + "Expected user input is " + aIsFromUserInput + "\n" + ); + + SimpleTest.is( + event.newAccessible, + position, + "new position in event is incorrect" + ); + + if (aTextOffsets) { + SimpleTest.is( + aDocAcc.virtualCursor.startOffset, + aTextOffsets[0], + "wrong start offset" + ); + SimpleTest.is( + aDocAcc.virtualCursor.endOffset, + aTextOffsets[1], + "wrong end offset" + ); + SimpleTest.is( + event.newStartOffset, + aTextOffsets[0], + "wrong start offset in event" + ); + SimpleTest.is( + event.newEndOffset, + aTextOffsets[1], + "wrong end offset in event" + ); + } + + var prevPosAndOffset = VCChangedChecker.getPreviousPosAndOffset( + aDocAcc.virtualCursor + ); + + if (prevPosAndOffset) { + SimpleTest.is( + event.oldAccessible, + prevPosAndOffset.position, + "previous position does not match" + ); + SimpleTest.is( + event.oldStartOffset, + prevPosAndOffset.startOffset, + "previous start offset does not match" + ); + SimpleTest.is( + event.oldEndOffset, + prevPosAndOffset.endOffset, + "previous end offset does not match" + ); + } + }; +} + +VCChangedChecker.prevPosAndOffset = {}; + +VCChangedChecker.storePreviousPosAndOffset = function storePreviousPosAndOffset( + aPivot +) { + VCChangedChecker.prevPosAndOffset[aPivot] = { + position: aPivot.position, + startOffset: aPivot.startOffset, + endOffset: aPivot.endOffset, + }; +}; + +VCChangedChecker.getPreviousPosAndOffset = function getPreviousPosAndOffset( + aPivot +) { + return VCChangedChecker.prevPosAndOffset[aPivot]; +}; + +VCChangedChecker.methodReasonMap = { + moveNext: nsIAccessiblePivot.REASON_NEXT, + movePrevious: nsIAccessiblePivot.REASON_PREV, + moveFirst: nsIAccessiblePivot.REASON_FIRST, + moveLast: nsIAccessiblePivot.REASON_LAST, + setTextRange: nsIAccessiblePivot.REASON_NONE, + moveNextByText: nsIAccessiblePivot.REASON_NEXT, + movePreviousByText: nsIAccessiblePivot.REASON_PREV, + moveToPoint: nsIAccessiblePivot.REASON_POINT, +}; + +/** + * Set a text range in the pivot and wait for virtual cursor change event. + * + * @param aDocAcc [in] document that manages the virtual cursor + * @param aTextAccessible [in] accessible to set to virtual cursor's position + * @param aTextOffsets [in] start and end offsets of text range to set in + * virtual cursor. + */ +function setVCRangeInvoker(aDocAcc, aTextAccessible, aTextOffsets) { + this.invoke = function virtualCursorChangedInvoker_invoke() { + VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor); + SimpleTest.info(prettyName(aTextAccessible) + " " + aTextOffsets); + aDocAcc.virtualCursor.setTextRange( + aTextAccessible, + aTextOffsets[0], + aTextOffsets[1] + ); + }; + + this.getID = function setVCRangeInvoker_getID() { + return ( + "Set offset in " + + prettyName(aTextAccessible) + + " to (" + + aTextOffsets[0] + + ", " + + aTextOffsets[1] + + ")" + ); + }; + + this.eventSeq = [ + new VCChangedChecker( + aDocAcc, + aTextAccessible, + aTextOffsets, + "setTextRange", + true + ), + ]; +} + +/** + * Move the pivot and wait for virtual cursor change event. + * + * @param aDocAcc [in] document that manages the virtual cursor + * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.) + * @param aRule [in] traversal rule object + * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect + * virtual cursor to land on after performing move method. + * false if no move is expected. + * @param aIsFromUserInput [in] set user input flag when invoking method, and + * expect it in the event. + */ +function setVCPosInvoker( + aDocAcc, + aPivotMoveMethod, + aRule, + aIdOrNameOrAcc, + aIsFromUserInput +) { + // eslint-disable-next-line mozilla/no-compare-against-boolean-literals + var expectMove = aIdOrNameOrAcc != false; + this.invoke = function virtualCursorChangedInvoker_invoke() { + VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor); + if (aPivotMoveMethod && aRule) { + var moved = false; + switch (aPivotMoveMethod) { + case "moveFirst": + case "moveLast": + moved = aDocAcc.virtualCursor[aPivotMoveMethod]( + aRule, + aIsFromUserInput === undefined ? true : aIsFromUserInput + ); + break; + case "moveNext": + case "movePrevious": + moved = aDocAcc.virtualCursor[aPivotMoveMethod]( + aRule, + aDocAcc.virtualCursor.position, + false, + aIsFromUserInput === undefined ? true : aIsFromUserInput + ); + break; + } + SimpleTest.is( + !!moved, + !!expectMove, + "moved pivot with " + aPivotMoveMethod + " to " + aIdOrNameOrAcc + ); + } else { + aDocAcc.virtualCursor.position = getAccessible(aIdOrNameOrAcc); + } + }; + + this.getID = function setVCPosInvoker_getID() { + return "Do " + (expectMove ? "" : "no-op ") + aPivotMoveMethod; + }; + + if (expectMove) { + this.eventSeq = [ + new VCChangedChecker( + aDocAcc, + aIdOrNameOrAcc, + null, + aPivotMoveMethod, + aIsFromUserInput === undefined ? !!aPivotMoveMethod : aIsFromUserInput + ), + ]; + } else { + this.eventSeq = []; + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc), + ]; + } +} + +/** + * Move the pivot by text and wait for virtual cursor change event. + * + * @param aDocAcc [in] document that manages the virtual cursor + * @param aPivotMoveMethod [in] method to test (ie. "moveNext", "moveFirst", etc.) + * @param aBoundary [in] boundary constant + * @param aTextOffsets [in] start and end offsets of text range to set in + * virtual cursor. + * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect + * virtual cursor to land on after performing move method. + * false if no move is expected. + * @param aIsFromUserInput [in] set user input flag when invoking method, and + * expect it in the event. + */ +function setVCTextInvoker( + aDocAcc, + aPivotMoveMethod, + aBoundary, + aTextOffsets, + aIdOrNameOrAcc, + aIsFromUserInput +) { + // eslint-disable-next-line mozilla/no-compare-against-boolean-literals + var expectMove = aIdOrNameOrAcc != false; + this.invoke = function virtualCursorChangedInvoker_invoke() { + VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor); + SimpleTest.info(aDocAcc.virtualCursor.position); + var moved = aDocAcc.virtualCursor[aPivotMoveMethod]( + aBoundary, + aIsFromUserInput === undefined + ); + SimpleTest.is( + !!moved, + !!expectMove, + "moved pivot by text with " + aPivotMoveMethod + " to " + aIdOrNameOrAcc + ); + }; + + this.getID = function setVCPosInvoker_getID() { + return ( + "Do " + + (expectMove ? "" : "no-op ") + + aPivotMoveMethod + + " in " + + prettyName(aIdOrNameOrAcc) + + ", " + + boundaryToString(aBoundary) + + ", [" + + aTextOffsets + + "]" + ); + }; + + if (expectMove) { + this.eventSeq = [ + new VCChangedChecker( + aDocAcc, + aIdOrNameOrAcc, + aTextOffsets, + aPivotMoveMethod, + aIsFromUserInput === undefined ? true : aIsFromUserInput, + aBoundary + ), + ]; + } else { + this.eventSeq = []; + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc), + ]; + } +} + +/** + * Move the pivot to the position under the point. + * + * @param aDocAcc [in] document that manages the virtual cursor + * @param aX [in] screen x coordinate + * @param aY [in] screen y coordinate + * @param aIgnoreNoMatch [in] don't unset position if no object was found at + * point. + * @param aRule [in] traversal rule object + * @param aIdOrNameOrAcc [in] id, accessible or accessible name to expect + * virtual cursor to land on after performing move method. + * false if no move is expected. + */ +function moveVCCoordInvoker( + aDocAcc, + aX, + aY, + aIgnoreNoMatch, + aRule, + aIdOrNameOrAcc +) { + // eslint-disable-next-line mozilla/no-compare-against-boolean-literals + var expectMove = aIdOrNameOrAcc != false; + this.invoke = function virtualCursorChangedInvoker_invoke() { + VCChangedChecker.storePreviousPosAndOffset(aDocAcc.virtualCursor); + var moved = aDocAcc.virtualCursor.moveToPoint( + aRule, + aX, + aY, + aIgnoreNoMatch + ); + SimpleTest.ok( + (expectMove && moved) || (!expectMove && !moved), + "moved pivot" + ); + }; + + this.getID = function setVCPosInvoker_getID() { + return ( + "Do " + (expectMove ? "" : "no-op ") + "moveToPoint " + aIdOrNameOrAcc + ); + }; + + if (expectMove) { + this.eventSeq = [ + new VCChangedChecker(aDocAcc, aIdOrNameOrAcc, null, "moveToPoint", true), + ]; + } else { + this.eventSeq = []; + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc), + ]; + } +} + +/** + * Change the pivot modalRoot + * + * @param aDocAcc [in] document that manages the virtual cursor + * @param aModalRootAcc [in] accessible of the modal root, or null + * @param aExpectedResult [in] error result expected. 0 if expecting success + */ +function setModalRootInvoker(aDocAcc, aModalRootAcc, aExpectedResult) { + this.invoke = function setModalRootInvoker_invoke() { + var errorResult = 0; + try { + aDocAcc.virtualCursor.modalRoot = aModalRootAcc; + } catch (x) { + SimpleTest.ok( + x.result, + "Unexpected exception when changing modal root: " + x + ); + errorResult = x.result; + } + + SimpleTest.is( + errorResult, + aExpectedResult, + "Did not get expected result when changing modalRoot" + ); + }; + + this.getID = function setModalRootInvoker_getID() { + return "Set modalRoot to " + prettyName(aModalRootAcc); + }; + + this.eventSeq = []; + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_VIRTUALCURSOR_CHANGED, aDocAcc), + ]; +} + +/** + * Add invokers to a queue to test a rule and an expected sequence of element ids + * or accessible names for that rule in the given document. + * + * @param aQueue [in] event queue in which to push invoker sequence. + * @param aDocAcc [in] the managing document of the virtual cursor we are + * testing + * @param aRule [in] the traversal rule to use in the invokers + * @param aModalRoot [in] a modal root to use in this traversal sequence + * @param aSequence [in] a sequence of accessible names or element ids to expect + * with the given rule in the given document + */ +function queueTraversalSequence(aQueue, aDocAcc, aRule, aModalRoot, aSequence) { + aDocAcc.virtualCursor.position = null; + + // Add modal root (if any) + aQueue.push(new setModalRootInvoker(aDocAcc, aModalRoot, 0)); + + aQueue.push(new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0])); + + for (let i = 1; i < aSequence.length; i++) { + let invoker = new setVCPosInvoker(aDocAcc, "moveNext", aRule, aSequence[i]); + aQueue.push(invoker); + } + + // No further more matches for given rule, expect no virtual cursor changes. + aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false)); + + for (let i = aSequence.length - 2; i >= 0; i--) { + let invoker = new setVCPosInvoker( + aDocAcc, + "movePrevious", + aRule, + aSequence[i] + ); + aQueue.push(invoker); + } + + // No previous more matches for given rule, expect no virtual cursor changes. + aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false)); + + aQueue.push( + new setVCPosInvoker( + aDocAcc, + "moveLast", + aRule, + aSequence[aSequence.length - 1] + ) + ); + + // No further more matches for given rule, expect no virtual cursor changes. + aQueue.push(new setVCPosInvoker(aDocAcc, "moveNext", aRule, false)); + + // set isFromUserInput to false, just to test.. + aQueue.push( + new setVCPosInvoker(aDocAcc, "moveFirst", aRule, aSequence[0], false) + ); + + // No previous more matches for given rule, expect no virtual cursor changes. + aQueue.push(new setVCPosInvoker(aDocAcc, "movePrevious", aRule, false)); + + // Remove modal root (if any). + aQueue.push(new setModalRootInvoker(aDocAcc, null, 0)); +} + +/** + * A checker for removing an accessible while the virtual cursor is on it. + */ +function removeVCPositionChecker(aDocAcc, aHiddenParentAcc) { + this.__proto__ = new invokerChecker(EVENT_REORDER, aHiddenParentAcc); + + this.check = function removeVCPositionChecker_check(aEvent) { + var errorResult = 0; + try { + aDocAcc.virtualCursor.moveNext(ObjectTraversalRule); + } catch (x) { + errorResult = x.result; + } + SimpleTest.is( + errorResult, + NS_ERROR_NOT_IN_TREE, + "Expecting NOT_IN_TREE error when moving pivot from invalid position." + ); + }; +} + +/** + * Put the virtual cursor's position on an object, and then remove it. + * + * @param aDocAcc [in] document that manages the virtual cursor + * @param aPosNode [in] DOM node to hide after virtual cursor's position is + * set to it. + */ +function removeVCPositionInvoker(aDocAcc, aPosNode) { + this.accessible = getAccessible(aPosNode); + this.invoke = function removeVCPositionInvoker_invoke() { + aDocAcc.virtualCursor.position = this.accessible; + aPosNode.remove(); + }; + + this.getID = function removeVCPositionInvoker_getID() { + return "Bring virtual cursor to accessible, and remove its DOM node."; + }; + + this.eventSeq = [ + new removeVCPositionChecker(aDocAcc, this.accessible.parent), + ]; +} + +/** + * A checker for removing the pivot root and then calling moveFirst, and + * checking that an exception is thrown. + */ +function removeVCRootChecker(aPivot) { + this.__proto__ = new invokerChecker(EVENT_REORDER, aPivot.root.parent); + + this.check = function removeVCRootChecker_check(aEvent) { + var errorResult = 0; + try { + aPivot.moveLast(ObjectTraversalRule); + } catch (x) { + errorResult = x.result; + } + SimpleTest.is( + errorResult, + NS_ERROR_NOT_IN_TREE, + "Expecting NOT_IN_TREE error when moving pivot from invalid position." + ); + }; +} + +/** + * Create a pivot, remove its root, and perform an operation where the root is + * needed. + * + * @param aRootNode [in] DOM node of which accessible will be the root of the + * pivot. Should have more than one child. + */ +function removeVCRootInvoker(aRootNode) { + this.pivot = gAccService.createAccessiblePivot(getAccessible(aRootNode)); + this.invoke = function removeVCRootInvoker_invoke() { + this.pivot.position = this.pivot.root.firstChild; + aRootNode.remove(); + }; + + this.getID = function removeVCRootInvoker_getID() { + return "Remove root of pivot from tree."; + }; + + this.eventSeq = [new removeVCRootChecker(this.pivot)]; +} + +/** + * A debug utility for writing proper sequences for queueTraversalSequence. + */ +function dumpTraversalSequence(aPivot, aRule) { + var sequence = []; + if (aPivot.moveFirst(aRule)) { + do { + sequence.push("'" + prettyName(aPivot.position) + "'"); + } while (aPivot.moveNext(aRule)); + } + SimpleTest.info("\n[" + sequence.join(", ") + "]\n"); +} diff --git a/accessible/tests/mochitest/pivot/a11y.ini b/accessible/tests/mochitest/pivot/a11y.ini new file mode 100644 index 0000000000..8add460947 --- /dev/null +++ b/accessible/tests/mochitest/pivot/a11y.ini @@ -0,0 +1,8 @@ +[DEFAULT] +support-files = + doc_virtualcursor.html + doc_virtualcursor_text.html + !/accessible/tests/mochitest/*.js + +[test_virtualcursor.html] +[test_virtualcursor_text.html] diff --git a/accessible/tests/mochitest/pivot/doc_virtualcursor.html b/accessible/tests/mochitest/pivot/doc_virtualcursor.html new file mode 100644 index 0000000000..a456f2dfcd --- /dev/null +++ b/accessible/tests/mochitest/pivot/doc_virtualcursor.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<head> + <title>Pivot test document</title> + <meta charset="utf-8" /> +</head> +<body> + <h1 id="heading-1-1">Main Title</h1> + <h2 id="heading-2-1" aria-hidden="true">First Section Title</h2> + <p id="paragraph-1"> + Lorem ipsum <strong>dolor</strong> sit amet. Integer vitae urna + leo, id <a href="#">semper</a> nulla. + </p> + <h2 id="heading-2-2" aria-hidden="undefined">Second Section Title</h2> + <p id="paragraph-2" aria-hidden=""> + Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.</p> + <p id="paragraph-3" aria-hidden="true"> + <a href="#" id="hidden-link">Maybe</a> it was the other <i>George Michael</i>. + You know, the <a href="#">singer-songwriter</a>. + </p> + <p style="opacity: 0;" id="paragraph-4"> + This is completely transparent + </p> + <iframe + src="data:text/html,<html><body>An <i>embedded</i> document.</body></html>"> + </iframe> + <div id="hide-me">Hide me</div> + <p id="links" aria-hidden="false"> + <a href="http://mozilla.org" title="Link 1 title">Link 1</a> + <a href="http://mozilla.org" title="Link 2 title">Link 2</a> + <a href="http://mozilla.org" title="Link 3 title">Link 3</a> + </p> + <ul> + <li>Hello<span> </span></li> + <li>World</li> + </ul> +</body> +</html> diff --git a/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html b/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html new file mode 100644 index 0000000000..a0565058d9 --- /dev/null +++ b/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html> +<head> + <title>Pivot test document</title> + <meta charset="utf-8" /> +</head> +<body> + <div id="start-block">This is the very beginning.</div> + <p id="paragraph-1"> + This <b>is</b> <a id="p1-link-1" href="#">the</a> test of text. + </p> + <div id="section-1">A <a id="s1-link-1" href="#">multiword link</a> is here. <a id="s1-link-2" href="#">We</a> will traverse</div> + <div id="section-2">into, out, and between the subtrees.</div> + <p id="paragraph-2">Singularity.</p> + <table> + <tr> + <td id="cell-1">Magical</td> + <td id="cell-2">unicorns</td> + </tr> + <tr> + <td id="cell-3">and wizards</td> + <td id="cell-4">really exist.</td> + </tr> + </table> + <div id="section-3">Endless fun!</div> + <p id="paragraph-3">Objects<a id="p3-link-1" href="#">adjacent</a>to <a id="p3-link-2" href="#">each</a><a id="p3-link-3" href="#">other</a> should be separate.</p> + <p id="paragraph-4">Hello <strong>real</strong><a href="#"> world</p> + <a href="#" id="image-desc-link"> + <img src="../moz.png" alt="">Hello + </a> + <p id="image-end-line"> + a<img src="../moz.png" alt="b"><br> + c + </p> + <div id="end-block">End!</div> +</body> +</html> diff --git a/accessible/tests/mochitest/pivot/test_virtualcursor.html b/accessible/tests/mochitest/pivot/test_virtualcursor.html new file mode 100644 index 0000000000..9f3225fcf6 --- /dev/null +++ b/accessible/tests/mochitest/pivot/test_virtualcursor.html @@ -0,0 +1,119 @@ +<!DOCTYPE html> +<html> +<head> + <title>Tests pivot functionality in virtual cursors</title> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + <script src="chrome://mochikit/content/chrome-harness.js"> + </script> + + <script type="application/javascript" src="../common.js"></script> + <script type="application/javascript" src="../browser.js"></script> + <script type="application/javascript" src="../events.js"></script> + <script type="application/javascript" src="../role.js"></script> + <script type="application/javascript" src="../states.js"></script> + <script type="application/javascript" src="../pivot.js"></script> + <script type="application/javascript" src="../layout.js"></script> + + <script type="application/javascript"> + var gBrowserWnd = null; + var gQueue = null; + + function doTest() { + var rootAcc = getAccessible(browserDocument(), [nsIAccessibleDocument]); + ok(rootAcc.virtualCursor, + "root document does not have virtualCursor"); + + var doc = currentTabDocument(); + var docAcc = getAccessible(doc, [nsIAccessibleDocument]); + + // Test that embedded documents have their own virtual cursor. + is(docAcc.childDocumentCount, 1, "Expecting one child document"); + ok(docAcc.getChildDocumentAt(0).virtualCursor, + "child document does not have virtualCursor"); + + gQueue = new eventQueue(); + + gQueue.onFinish = function onFinish() { + closeBrowserWindow(); + }; + + queueTraversalSequence(gQueue, docAcc, HeadersTraversalRule, null, + ["heading-1-1", "heading-2-2"]); + + queueTraversalSequence( + gQueue, docAcc, ObjectTraversalRule, null, + ["Main Title", "Lorem ipsum ", + "dolor", " sit amet. Integer vitae urna leo, id ", + "semper", " nulla. ", "Second Section Title", + "Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.", + "An ", "embedded", " document.", "Hide me", "Link 1", "Link 2", + "Link 3", "Hello", "World"]); + + // Just a random smoke test to see if our setTextRange works. + gQueue.push( + new setVCRangeInvoker( + docAcc, + getAccessible(doc.getElementById("paragraph-2"), nsIAccessibleText), + [2, 6])); + + gQueue.push(new removeVCPositionInvoker( + docAcc, doc.getElementById("hide-me"))); + + gQueue.push(new removeVCRootInvoker( + doc.getElementById("links"))); + + var [x, y] = getBounds(getAccessible(doc.getElementById("heading-1-1"))); + gQueue.push(new moveVCCoordInvoker(docAcc, x + 1, y + 1, true, + HeadersTraversalRule, "heading-1-1")); + + // Already on the point, so we should not get a move event. + gQueue.push(new moveVCCoordInvoker(docAcc, x + 1, y + 1, true, + HeadersTraversalRule, false)); + + // Attempting a coordinate outside any header, should not move. + gQueue.push(new moveVCCoordInvoker(docAcc, x - 1, y - 1, true, + HeadersTraversalRule, false)); + + // Attempting a coordinate outside any header, should move to null + gQueue.push(new moveVCCoordInvoker(docAcc, x - 1, y - 1, false, + HeadersTraversalRule, null)); + + queueTraversalSequence( + gQueue, docAcc, ObjectTraversalRule, + getAccessible(doc.getElementById("paragraph-1")), + ["Lorem ipsum ", "dolor", " sit amet. Integer vitae urna leo, id ", + "semper", " nulla. "]); + + gQueue.push(new setModalRootInvoker(docAcc, docAcc.parent, + NS_ERROR_INVALID_ARG)); + + gQueue.push(new setVCPosInvoker(docAcc, "moveNext", ObjectTraversalRule, "dolor")); + + gQueue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(function() { + /* We open a new browser because we need to test with a top-level content + document. */ + openBrowserWindow( + doTest, + getRootDirectory(window.location.href) + "doc_virtualcursor.html"); + }); + </script> +</head> +<body id="body"> + + <a target="_blank" + title="Introduce virtual cursor/soft focus functionality to a11y API" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=698823">Mozilla Bug 698823</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/pivot/test_virtualcursor_text.html b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html new file mode 100644 index 0000000000..23d46f3fe6 --- /dev/null +++ b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html @@ -0,0 +1,271 @@ +<!DOCTYPE html> +<html> +<head> + <title>Tests pivot functionality in virtual cursors</title> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + <script src="chrome://mochikit/content/chrome-harness.js"> + </script> + + <script type="application/javascript" src="../common.js"></script> + <script type="application/javascript" src="../text.js"></script> + <script type="application/javascript" src="../browser.js"></script> + <script type="application/javascript" src="../events.js"></script> + <script type="application/javascript" src="../role.js"></script> + <script type="application/javascript" src="../states.js"></script> + <script type="application/javascript" src="../pivot.js"></script> + <script type="application/javascript" src="../layout.js"></script> + + <script type="application/javascript"> + var gBrowserWnd = null; + var gQueue = null; + + function doTest() { + var doc = currentTabDocument(); + var docAcc = getAccessible(doc, [nsIAccessibleDocument]); + + gQueue = new eventQueue(); + + gQueue.onFinish = function onFinish() { + closeBrowserWindow(); + }; + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("paragraph-1")))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 4], + getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [4, 5], + getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", CHAR_BOUNDARY, [3, 4], + getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [5, 7], + getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 3], + getAccessible(doc.getElementById("p1-link-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [10, 14], + getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 3], + getAccessible(doc.getElementById("p1-link-1"), nsIAccessibleText))); + // set user input to false, and see if it works + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [5, 7], + getAccessible(doc.getElementById("paragraph-1"), nsIAccessibleText)), + false); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("section-1")))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 1], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 9], + getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText))); + // set user input to false, and see if it works + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [10, 14], + getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText), + false)); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [4, 6], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [7, 12], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 2], + getAccessible(doc.getElementById("s1-link-2"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [15, 19], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [20, 28], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 5], + getAccessible(doc.getElementById("section-2"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [6, 10], + getAccessible(doc.getElementById("section-2"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 5], + getAccessible(doc.getElementById("section-2"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [20, 28], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [15, 19], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 2], + getAccessible(doc.getElementById("s1-link-2"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [7, 12], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [4, 6], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [10, 14], + getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 9], + getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 1], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + + gQueue.push(new setVCRangeInvoker(docAcc, getAccessible(doc.getElementById("s1-link-1")), [0, 0])); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", CHAR_BOUNDARY, [1, 2], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", CHAR_BOUNDARY, [0, 1], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [1, 2], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [0, 1], + getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [1, 2], + getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [2, 9], + getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [10, 14], + getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [3, 4], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", CHAR_BOUNDARY, [13, 14], + getAccessible(doc.getElementById("s1-link-1"), nsIAccessibleText))); + + gQueue.push(new setVCRangeInvoker(docAcc, getAccessible(doc.getElementById("section-2")), [0, 0])); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", CHAR_BOUNDARY, [27, 28], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [0, 1], + getAccessible(doc.getElementById("section-2"), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("paragraph-2")))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 12], + getAccessible(doc.getElementById("paragraph-2"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 7], + getAccessible(doc.getElementById("cell-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 8], + getAccessible(doc.getElementById("cell-2"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 3], + getAccessible(doc.getElementById("cell-3"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [4, 11], + getAccessible(doc.getElementById("cell-3"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 6], + getAccessible(doc.getElementById("cell-4"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [7, 13], + getAccessible(doc.getElementById("cell-4"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 7], + getAccessible(doc.getElementById("section-3"), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("section-3")))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 7], + getAccessible(doc.getElementById("section-3"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [7, 13], + getAccessible(doc.getElementById("cell-4"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 6], + getAccessible(doc.getElementById("cell-4"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [4, 11], + getAccessible(doc.getElementById("cell-3"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 3], + getAccessible(doc.getElementById("cell-3"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 8], + getAccessible(doc.getElementById("cell-2"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 7], + getAccessible(doc.getElementById("cell-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 12], + getAccessible(doc.getElementById("paragraph-2"), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("paragraph-3")))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 7], + getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 8], + getAccessible(doc.getElementById("p3-link-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [8, 10], + getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 4], + getAccessible(doc.getElementById("p3-link-2"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 5], + getAccessible(doc.getElementById("p3-link-3"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [14, 20], + getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 5], + getAccessible(doc.getElementById("p3-link-3"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 4], + getAccessible(doc.getElementById("p3-link-2"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [8, 10], + getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 8], + getAccessible(doc.getElementById("p3-link-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 7], + getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("s1-link-2")))); + // Start with the pivot in the middle of the paragraph + gQueue.push(new setVCPosInvoker(docAcc, "moveNext", ObjectTraversalRule, " will traverse")); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [15, 19], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [0, 2], + getAccessible(doc.getElementById("s1-link-2"), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("end-block")))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 4], + getAccessible(doc.getElementById("end-block"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, null, false)); + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("start-block")))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 4], + getAccessible(doc.getElementById("start-block"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, null, false)); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, null, false)); + gQueue.push(new setVCRangeInvoker(docAcc, getAccessible(doc.getElementById("start-block")), [0, 0])); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, null, false)); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("paragraph-3")).firstChild)); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [0, 7], + getAccessible(doc.getElementById("paragraph-3"), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("s1-link-1")).nextSibling)); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [4, 6], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("paragraph-4")).firstChild.nextSibling)); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", WORD_BOUNDARY, [6, 10], + getAccessible(doc.getElementById("paragraph-4"), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("section-1")))); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [20, 28], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("section-1")).lastChild)); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", WORD_BOUNDARY, [20, 28], + getAccessible(doc.getElementById("section-1"), nsIAccessibleText))); + + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("image-desc-link")))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [0, 1], + getAccessible(doc.getElementById("image-desc-link"), nsIAccessibleText))); + gQueue.push(new setVCTextInvoker(docAcc, "moveNextByText", CHAR_BOUNDARY, [1, 2], + getAccessible(doc.getElementById("image-desc-link"), nsIAccessibleText))); + + gQueue.push(new setVCRangeInvoker(docAcc, getAccessible(doc.getElementById("image-end-line")), [3, 3])); + gQueue.push(new setVCTextInvoker(docAcc, "movePreviousByText", LINE_BOUNDARY, [0, 2], + getAccessible(doc.getElementById("image-end-line"), nsIAccessibleText))); + + gQueue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(function() { + /* We open a new browser because we need to test with a top-level content + document. */ + openBrowserWindow( + doTest, + getRootDirectory(window.location.href) + "doc_virtualcursor_text.html"); + }); + </script> +</head> +<body id="body"> + + <a target="_blank" + title="Support Movement By Granularity" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=886076">Mozilla Bug 886076</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/promisified-events.js b/accessible/tests/mochitest/promisified-events.js new file mode 100644 index 0000000000..71b2cf013f --- /dev/null +++ b/accessible/tests/mochitest/promisified-events.js @@ -0,0 +1,328 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// This is loaded by head.js, so has the same globals, hence we import the +// globals from there. +/* import-globals-from common.js */ + +/* exported EVENT_ANNOUNCEMENT, EVENT_REORDER, EVENT_SCROLLING, + EVENT_SCROLLING_END, EVENT_SHOW, EVENT_TEXT_INSERTED, + EVENT_TEXT_REMOVED, EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE, + EVENT_TEXT_ATTRIBUTE_CHANGED, EVENT_TEXT_CARET_MOVED, EVENT_SELECTION, + EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_STATE_CHANGE, + EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE, EVENT_FOCUS, + EVENT_DOCUMENT_RELOAD, EVENT_VIRTUALCURSOR_CHANGED, EVENT_ALERT, + EVENT_OBJECT_ATTRIBUTE_CHANGED, UnexpectedEvents, waitForEvent, + waitForEvents, waitForOrderedEvents, waitForStateChange, + stateChangeEventArgs */ + +const EVENT_ANNOUNCEMENT = nsIAccessibleEvent.EVENT_ANNOUNCEMENT; +const EVENT_DOCUMENT_LOAD_COMPLETE = + nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE; +const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE; +const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER; +const EVENT_SCROLLING = nsIAccessibleEvent.EVENT_SCROLLING; +const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START; +const EVENT_SCROLLING_END = nsIAccessibleEvent.EVENT_SCROLLING_END; +const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION; +const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN; +const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW; +const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE; +const EVENT_TEXT_ATTRIBUTE_CHANGED = + nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED; +const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED; +const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED; +const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED; +const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE; +const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE; +const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE; +const EVENT_TEXT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_TEXT_VALUE_CHANGE; +const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS; +const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD; +const EVENT_VIRTUALCURSOR_CHANGED = + nsIAccessibleEvent.EVENT_VIRTUALCURSOR_CHANGED; +const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT; +const EVENT_TEXT_SELECTION_CHANGED = + nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED; +const EVENT_LIVE_REGION_ADDED = nsIAccessibleEvent.EVENT_LIVE_REGION_ADDED; +const EVENT_LIVE_REGION_REMOVED = nsIAccessibleEvent.EVENT_LIVE_REGION_REMOVED; +const EVENT_OBJECT_ATTRIBUTE_CHANGED = + nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED; +const EVENT_INNER_REORDER = nsIAccessibleEvent.EVENT_INNER_REORDER; + +const EventsLogger = { + enabled: false, + + log(msg) { + if (this.enabled) { + info(msg); + } + }, +}; + +/** + * Describe an event in string format. + * @param {nsIAccessibleEvent} event event to strigify + */ +function eventToString(event) { + let type = eventTypeToString(event.eventType); + let info = `Event type: ${type}`; + + if (event instanceof nsIAccessibleStateChangeEvent) { + let stateStr = statesToString( + event.isExtraState ? 0 : event.state, + event.isExtraState ? event.state : 0 + ); + info += `, state: ${stateStr}, is enabled: ${event.isEnabled}`; + } else if (event instanceof nsIAccessibleTextChangeEvent) { + let tcType = event.isInserted ? "inserted" : "removed"; + info += `, start: ${event.start}, length: ${event.length}, ${tcType} text: ${event.modifiedText}`; + } + + info += `. Target: ${prettyName(event.accessible)}`; + return info; +} + +function matchEvent(event, matchCriteria) { + if (!matchCriteria) { + return true; + } + + let acc = event.accessible; + switch (typeof matchCriteria) { + case "string": + let id = getAccessibleDOMNodeID(acc); + if (id === matchCriteria) { + EventsLogger.log(`Event matches DOMNode id: ${id}`); + return true; + } + break; + case "function": + if (matchCriteria(event)) { + EventsLogger.log( + `Lambda function matches event: ${eventToString(event)}` + ); + return true; + } + break; + default: + if (matchCriteria instanceof nsIAccessible) { + if (acc === matchCriteria) { + EventsLogger.log(`Event matches accessible: ${prettyName(acc)}`); + return true; + } + } else if (event.DOMNode == matchCriteria) { + EventsLogger.log( + `Event matches DOM node: ${prettyName(event.DOMNode)}` + ); + return true; + } + } + + return false; +} + +/** + * A helper function that returns a promise that resolves when an accessible + * event of the given type with the given target (defined by its id or + * accessible) is observed. + * @param {Number} eventType expected accessible event + * type + * @param {String|nsIAccessible|Function} matchCriteria expected content + * element id + * for the event + * @param {String} message Message to prepend to logging. + * @return {Promise} promise that resolves to an + * event + */ +function waitForEvent(eventType, matchCriteria, message) { + return new Promise(resolve => { + let eventObserver = { + observe(subject, topic, data) { + if (topic !== "accessible-event") { + return; + } + + let event = subject.QueryInterface(nsIAccessibleEvent); + if (EventsLogger.enabled) { + // Avoid calling eventToString if the EventsLogger isn't enabled in order + // to avoid an intermittent crash (bug 1307645). + EventsLogger.log(eventToString(event)); + } + + // If event type does not match expected type, skip the event. + if (event.eventType !== eventType) { + return; + } + + if (matchEvent(event, matchCriteria)) { + EventsLogger.log( + `Correct event type: ${eventTypeToString(eventType)}` + ); + Services.obs.removeObserver(this, "accessible-event"); + ok( + true, + `${message ? message + ": " : ""}Recieved ${eventTypeToString( + eventType + )} event` + ); + resolve(event); + } + }, + }; + Services.obs.addObserver(eventObserver, "accessible-event"); + }); +} + +class UnexpectedEvents { + constructor(unexpected) { + if (unexpected.length) { + this.unexpected = unexpected; + Services.obs.addObserver(this, "accessible-event"); + } + } + + observe(subject, topic, data) { + if (topic !== "accessible-event") { + return; + } + + let event = subject.QueryInterface(nsIAccessibleEvent); + + let unexpectedEvent = this.unexpected.find( + ([etype, criteria]) => + etype === event.eventType && matchEvent(event, criteria) + ); + + if (unexpectedEvent) { + ok(false, `Got unexpected event: ${eventToString(event)}`); + } + } + + stop() { + if (this.unexpected) { + Services.obs.removeObserver(this, "accessible-event"); + } + } +} + +/** + * A helper function that waits for a sequence of accessible events in + * specified order. + * @param {Array} events a list of events to wait (same format as + * waitForEvent arguments) + * @param {String} message Message to prepend to logging. + * @param {Boolean} ordered Events need to be recieved in given order. + * @param {Object} invokerOrWindow a local window or a special content invoker + * it takes a list of arguments and a task + * function. + */ +async function waitForEvents( + events, + message, + ordered = false, + invokerOrWindow = null +) { + let expected = events.expected || events; + // Next expected event index. + let currentIdx = 0; + + let unexpectedListener = events.unexpected + ? new UnexpectedEvents(events.unexpected) + : null; + + let results = await Promise.all( + expected.map((evt, idx) => { + const [eventType, matchCriteria] = evt; + return waitForEvent(eventType, matchCriteria, message).then(result => { + return [result, idx == currentIdx++]; + }); + }) + ); + + if (unexpectedListener) { + let flushQueue = async win => { + // Flush all notifications or queued a11y events. + win.windowUtils.advanceTimeAndRefresh(100); + + // Flush all DOM async events. + await new Promise(r => win.setTimeout(r, 0)); + + // Flush all notifications or queued a11y events resulting from async DOM events. + win.windowUtils.advanceTimeAndRefresh(100); + + // Flush all notifications or a11y events that may have been queued in the last tick. + win.windowUtils.advanceTimeAndRefresh(100); + + // Return refresh to normal. + win.windowUtils.restoreNormalRefresh(); + }; + + if (invokerOrWindow instanceof Function) { + await invokerOrWindow([flushQueue.toString()], async _flushQueue => { + // eslint-disable-next-line no-eval, no-undef + await eval(_flushQueue)(content); + }); + } else { + await flushQueue(invokerOrWindow ? invokerOrWindow : window); + } + + unexpectedListener.stop(); + } + + if (ordered) { + ok( + results.every(([, isOrdered]) => isOrdered), + `${message ? message + ": " : ""}Correct event order` + ); + } + + return results.map(([event]) => event); +} + +function waitForOrderedEvents(events, message) { + return waitForEvents(events, message, true); +} + +function stateChangeEventArgs(id, state, isEnabled, isExtra = false) { + return [ + EVENT_STATE_CHANGE, + e => { + e.QueryInterface(nsIAccessibleStateChangeEvent); + return ( + e.state == state && + e.isExtraState == isExtra && + isEnabled == e.isEnabled && + (typeof id == "string" + ? id == getAccessibleDOMNodeID(e.accessible) + : getAccessible(id) == e.accessible) + ); + }, + ]; +} + +function waitForStateChange(id, state, isEnabled, isExtra = false) { + return waitForEvent(...stateChangeEventArgs(id, state, isEnabled, isExtra)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Utility functions ported from events.js. + +/** + * This function selects all text in the passed-in element if it has an editor, + * before setting focus to it. This simulates behavio with the keyboard when + * tabbing to the element. This does explicitly what synthFocus did implicitly. + * This should be called only if you really want this behavior. + * @param {string} id The element ID to focus + */ +function selectAllTextAndFocus(id) { + const elem = getNode(id); + if (elem.editor) { + elem.selectionStart = elem.selectionEnd = elem.value.length; + } + + elem.focus(); +} diff --git a/accessible/tests/mochitest/relations.js b/accessible/tests/mochitest/relations.js new file mode 100644 index 0000000000..aa956649ff --- /dev/null +++ b/accessible/tests/mochitest/relations.js @@ -0,0 +1,204 @@ +/* import-globals-from common.js */ + +// ////////////////////////////////////////////////////////////////////////////// +// Constants + +var RELATION_CONTROLLED_BY = nsIAccessibleRelation.RELATION_CONTROLLED_BY; +var RELATION_CONTROLLER_FOR = nsIAccessibleRelation.RELATION_CONTROLLER_FOR; +var RELATION_DEFAULT_BUTTON = nsIAccessibleRelation.RELATION_DEFAULT_BUTTON; +var RELATION_DESCRIBED_BY = nsIAccessibleRelation.RELATION_DESCRIBED_BY; +var RELATION_DESCRIPTION_FOR = nsIAccessibleRelation.RELATION_DESCRIPTION_FOR; +var RELATION_EMBEDDED_BY = nsIAccessibleRelation.RELATION_EMBEDDED_BY; +var RELATION_EMBEDS = nsIAccessibleRelation.RELATION_EMBEDS; +var RELATION_FLOWS_FROM = nsIAccessibleRelation.RELATION_FLOWS_FROM; +var RELATION_FLOWS_TO = nsIAccessibleRelation.RELATION_FLOWS_TO; +var RELATION_LABEL_FOR = nsIAccessibleRelation.RELATION_LABEL_FOR; +var RELATION_LABELLED_BY = nsIAccessibleRelation.RELATION_LABELLED_BY; +var RELATION_MEMBER_OF = nsIAccessibleRelation.RELATION_MEMBER_OF; +var RELATION_NODE_CHILD_OF = nsIAccessibleRelation.RELATION_NODE_CHILD_OF; +var RELATION_NODE_PARENT_OF = nsIAccessibleRelation.RELATION_NODE_PARENT_OF; +var RELATION_PARENT_WINDOW_OF = nsIAccessibleRelation.RELATION_PARENT_WINDOW_OF; +var RELATION_POPUP_FOR = nsIAccessibleRelation.RELATION_POPUP_FOR; +var RELATION_SUBWINDOW_OF = nsIAccessibleRelation.RELATION_SUBWINDOW_OF; +var RELATION_CONTAINING_DOCUMENT = + nsIAccessibleRelation.RELATION_CONTAINING_DOCUMENT; +var RELATION_CONTAINING_TAB_PANE = + nsIAccessibleRelation.RELATION_CONTAINING_TAB_PANE; +var RELATION_CONTAINING_APPLICATION = + nsIAccessibleRelation.RELATION_CONTAINING_APPLICATION; +const RELATION_DETAILS = nsIAccessibleRelation.RELATION_DETAILS; +const RELATION_DETAILS_FOR = nsIAccessibleRelation.RELATION_DETAILS_FOR; +const RELATION_ERRORMSG = nsIAccessibleRelation.RELATION_ERRORMSG; +const RELATION_ERRORMSG_FOR = nsIAccessibleRelation.RELATION_ERRORMSG_FOR; +const RELATION_LINKS_TO = nsIAccessibleRelation.RELATION_LINKS_TO; + +// ////////////////////////////////////////////////////////////////////////////// +// General + +/** + * Test the accessible relation. + * + * @param aIdentifier [in] identifier to get an accessible, may be ID + * attribute or DOM element or accessible object + * @param aRelType [in] relation type (see constants above) + * @param aRelatedIdentifiers [in] identifier or array of identifiers of + * expected related accessibles + */ +function testRelation(aIdentifier, aRelType, aRelatedIdentifiers) { + var relation = getRelationByType(aIdentifier, aRelType); + + var relDescr = getRelationErrorMsg(aIdentifier, aRelType); + var relDescrStart = getRelationErrorMsg(aIdentifier, aRelType, true); + + if (!relation || !relation.targetsCount) { + if (!aRelatedIdentifiers) { + ok(true, "No" + relDescr); + return; + } + + var msg = + relDescrStart + + "has no expected targets: '" + + prettyName(aRelatedIdentifiers) + + "'"; + + ok(false, msg); + return; + } else if (!aRelatedIdentifiers) { + ok(false, "There are unexpected targets of " + relDescr); + return; + } + + var relatedIds = + aRelatedIdentifiers instanceof Array + ? aRelatedIdentifiers + : [aRelatedIdentifiers]; + + var targets = []; + for (let idx = 0; idx < relatedIds.length; idx++) { + targets.push(getAccessible(relatedIds[idx])); + } + + if (targets.length != relatedIds.length) { + return; + } + + var actualTargets = relation.getTargets(); + + // Check if all given related accessibles are targets of obtained relation. + for (let idx = 0; idx < targets.length; idx++) { + var isFound = false; + for (let relatedAcc of actualTargets.enumerate(Ci.nsIAccessible)) { + if (targets[idx] == relatedAcc) { + isFound = true; + break; + } + } + + ok(isFound, prettyName(relatedIds[idx]) + " is not a target of" + relDescr); + } + + // Check if all obtained targets are given related accessibles. + for (let relatedAcc of actualTargets.enumerate(Ci.nsIAccessible)) { + let idx; + // eslint-disable-next-line no-empty + for (idx = 0; idx < targets.length && relatedAcc != targets[idx]; idx++) {} + + if (idx == targets.length) { + ok( + false, + "There is unexpected target" + prettyName(relatedAcc) + "of" + relDescr + ); + } + } +} + +/** + * Test that the given accessible relations don't exist. + * + * @param aIdentifier [in] identifier to get an accessible, may be ID + * attribute or DOM element or accessible object + * @param aRelType [in] relation type (see constants above) + * @param aUnrelatedIdentifiers [in] identifier or array of identifiers of + * accessibles that shouldn't exist for this + * relation. + */ +function testAbsentRelation(aIdentifier, aRelType, aUnrelatedIdentifiers) { + var relation = getRelationByType(aIdentifier, aRelType); + + var relDescr = getRelationErrorMsg(aIdentifier, aRelType); + + if (!aUnrelatedIdentifiers) { + ok(false, "No identifiers given for unrelated accessibles."); + return; + } + + if (!relation || !relation.targetsCount) { + ok(true, "No relations exist."); + return; + } + + var relatedIds = + aUnrelatedIdentifiers instanceof Array + ? aUnrelatedIdentifiers + : [aUnrelatedIdentifiers]; + + var targets = []; + for (let idx = 0; idx < relatedIds.length; idx++) { + targets.push(getAccessible(relatedIds[idx])); + } + + if (targets.length != relatedIds.length) { + return; + } + + var actualTargets = relation.getTargets(); + + // Any found targets that match given accessibles should be called out. + for (let idx = 0; idx < targets.length; idx++) { + var notFound = true; + for (let relatedAcc of actualTargets.enumerate(Ci.nsIAccessible)) { + if (targets[idx] == relatedAcc) { + notFound = false; + break; + } + } + + ok(notFound, prettyName(relatedIds[idx]) + " is a target of " + relDescr); + } +} + +/** + * Return related accessible for the given relation type. + * + * @param aIdentifier [in] identifier to get an accessible, may be ID attribute + * or DOM element or accessible object + * @param aRelType [in] relation type (see constants above) + */ +function getRelationByType(aIdentifier, aRelType) { + var acc = getAccessible(aIdentifier); + if (!acc) { + return null; + } + + var relation = null; + try { + relation = acc.getRelationByType(aRelType); + } catch (e) { + ok(false, "Can't get" + getRelationErrorMsg(aIdentifier, aRelType)); + } + + return relation; +} + +// ////////////////////////////////////////////////////////////////////////////// +// Private implementation details + +function getRelationErrorMsg(aIdentifier, aRelType, aIsStartSentence) { + var relStr = relationTypeToString(aRelType); + var msg = aIsStartSentence ? "Relation of '" : " relation of '"; + msg += relStr + "' type for '" + prettyName(aIdentifier) + "'"; + msg += aIsStartSentence ? " " : "."; + + return msg; +} diff --git a/accessible/tests/mochitest/relations/a11y.ini b/accessible/tests/mochitest/relations/a11y.ini new file mode 100644 index 0000000000..89ffaffdce --- /dev/null +++ b/accessible/tests/mochitest/relations/a11y.ini @@ -0,0 +1,14 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_embeds.xhtml] +skip-if = os == 'linux' && !debug # bug 1411145 +[test_general.html] +[test_general.xhtml] +[test_groupInfoUpdate.html] +[test_tabbrowser.xhtml] +[test_tree.xhtml] +[test_ui_modalprompt.html] +[test_shadowdom.html] +[test_update.html] diff --git a/accessible/tests/mochitest/relations/test_embeds.xhtml b/accessible/tests/mochitest/relations/test_embeds.xhtml new file mode 100644 index 0000000000..d49ce1f73c --- /dev/null +++ b/accessible/tests/mochitest/relations/test_embeds.xhtml @@ -0,0 +1,128 @@ +<?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="Embeds relation tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../relations.js"></script> + <script type="application/javascript" + src="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function loadURI(aURI) + { + this.invoke = function loadURI_invoke() + { + tabBrowser().loadURI(Services.io.newURI(aURI), { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + } + + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument) + ]; + + this.finalCheck = function loadURI_finalCheck() + { + testRelation(browserDocument(), RELATION_EMBEDS, + getAccessible(currentTabDocument())); + } + + this.getID = function loadURI_getID() + { + return "load uri " + aURI; + } + } + + function addTab(aURI) + { + this.invoke = function addTab_invoke() + { + tabBrowser().addTab(aURI, { + referrerURI: null, + charset: null, + postData: null, + inBackground: false, + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + } + + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument) + ]; + + this.finalCheck = function loadURI_finalCheck() + { + testRelation(browserDocument(), RELATION_EMBEDS, + getAccessible(currentTabDocument())); + } + + this.getID = function addTab_getID() + { + return "load uri '" + aURI + "' in new tab"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Testing + + //gA11yEventDumpToConsole = true; // debug + + var gQueue = null; + function doTests() + { + testRelation(browserDocument(), RELATION_EMBEDS, + getAccessible(currentTabDocument())); + + enableLogging("docload"); + gQueue = new eventQueue(); + + gQueue.push(new loadURI("about:robots")); + gQueue.push(new addTab("about:mozilla")); + + gQueue.onFinish = function() + { + disableLogging(); + closeBrowserWindow(); + } + gQueue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTests, "about:license"); + ]]> + </script> + + <vbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=707654" + title="Embeds relation on root accessible can return not content document"> + Mozilla Bug 707654 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + </vbox> +</window> diff --git a/accessible/tests/mochitest/relations/test_general.html b/accessible/tests/mochitest/relations/test_general.html new file mode 100644 index 0000000000..d16b7c1492 --- /dev/null +++ b/accessible/tests/mochitest/relations/test_general.html @@ -0,0 +1,456 @@ +<html> + +<head> + <title>nsIAccessible::getAccessibleRelated() tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../relations.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // html:label@for + testRelation("label1_1", RELATION_LABEL_FOR, "control1_1"); + testRelation("control1_1", RELATION_LABELLED_BY, "label1_1"); + + // html:label@for, multiple + testRelation("label1_2", RELATION_LABEL_FOR, "control1_2"); + testRelation("label1_3", RELATION_LABEL_FOR, "control1_2"); + testRelation("control1_2", RELATION_LABELLED_BY, + [ "label1_2", "label1_3" ]); + + // ancestor html:label (implicit association) + testRelation("label1_4", RELATION_LABEL_FOR, "control1_4"); + testRelation("control1_4", RELATION_LABELLED_BY, "label1_4"); + testRelation("control1_4_option1", RELATION_LABELLED_BY, null); + testRelation("label1_5", RELATION_LABEL_FOR, "control1_5"); + testRelation("control1_5", RELATION_LABELLED_BY, "label1_5"); + testRelation("label1_6", RELATION_LABEL_FOR, "control1_6"); + testRelation("control1_6", RELATION_LABELLED_BY, "label1_6"); + testRelation("label1_7", RELATION_LABEL_FOR, "control1_7"); + testRelation("control1_7", RELATION_LABELLED_BY, "label1_7"); + testRelation("label1_8", RELATION_LABEL_FOR, "control1_8"); + testRelation("control1_8", RELATION_LABELLED_BY, "label1_8"); + testRelation("label1_9", RELATION_LABEL_FOR, "control1_9"); + testRelation("control1_9", RELATION_LABELLED_BY, "label1_9"); + testRelation("label1_10", RELATION_LABEL_FOR, "control1_10"); + testRelation("control1_10", RELATION_LABELLED_BY, "label1_10"); + testRelation("label1_11", RELATION_LABEL_FOR, "control1_11"); + testRelation("control1_11", RELATION_LABELLED_BY, "label1_11"); + testRelation("label1_12", RELATION_LABEL_FOR, "control1_12"); + testRelation("control1_12", RELATION_LABELLED_BY, "label1_12"); + + testRelation("label1_13", RELATION_LABEL_FOR, null); + testRelation("control1_13", RELATION_LABELLED_BY, null); + testRelation("control1_14", RELATION_LABELLED_BY, "label1_14"); + + // aria-labelledby + testRelation("label2", RELATION_LABEL_FOR, "checkbox2"); + testRelation("checkbox2", RELATION_LABELLED_BY, "label2"); + + // aria-labelledby, multiple relations + testRelation("label3", RELATION_LABEL_FOR, "checkbox3"); + testRelation("label4", RELATION_LABEL_FOR, "checkbox3"); + testRelation("checkbox3", RELATION_LABELLED_BY, ["label3", "label4"]); + + // aria-describedby + testRelation("descr1", RELATION_DESCRIPTION_FOR, "checkbox4"); + testRelation("checkbox4", RELATION_DESCRIBED_BY, "descr1"); + + // aria-describedby, multiple relations + testRelation("descr2", RELATION_DESCRIPTION_FOR, "checkbox5"); + testRelation("descr3", RELATION_DESCRIPTION_FOR, "checkbox5"); + testRelation("checkbox5", RELATION_DESCRIBED_BY, ["descr2", "descr3"]); + + // aria_owns, multiple relations + testRelation("treeitem1", RELATION_NODE_CHILD_OF, "tree"); + testRelation("treeitem2", RELATION_NODE_CHILD_OF, "tree"); + + // 'node child of' relation for outlineitem role + testRelation("treeitem3", RELATION_NODE_CHILD_OF, "tree"); + testRelation("treeitem4", RELATION_NODE_CHILD_OF, "tree"); + testRelation("treeitem5", RELATION_NODE_CHILD_OF, "treeitem4"); + testRelation("treeitem6", RELATION_NODE_CHILD_OF, "tree"); + testRelation("treeitem7", RELATION_NODE_CHILD_OF, "treeitem6"); + testRelation("tree2_ti1", RELATION_NODE_CHILD_OF, "tree2"); + testRelation("tree2_ti1a", RELATION_NODE_CHILD_OF, "tree2_ti1"); + testRelation("tree2_ti1b", RELATION_NODE_CHILD_OF, "tree2_ti1"); + + // 'node child of' relation for row role in grid. + // Relation for row associated using aria-level should exist. + testRelation("simplegrid-row3", RELATION_NODE_CHILD_OF, + "simplegrid-row2"); + // Relations for hierarchical children elements shouldn't exist. + testAbsentRelation("simplegrid-row1", RELATION_NODE_CHILD_OF, + "simplegrid"); + testAbsentRelation("simplegrid-row2", RELATION_NODE_CHILD_OF, + "simplegrid"); + + // 'node child of' relation for row role of treegrid + testRelation("treegridrow1", RELATION_NODE_CHILD_OF, "treegrid"); + testRelation("treegridrow2", RELATION_NODE_CHILD_OF, "treegrid"); + testRelation("treegridrow3", RELATION_NODE_CHILD_OF, "treegridrow2"); + + // 'node child of' relation for lists organized by groups + testRelation("listitem1", RELATION_NODE_CHILD_OF, "list"); + testRelation("listitem1.1", RELATION_NODE_CHILD_OF, "listitem1"); + testRelation("listitem1.2", RELATION_NODE_CHILD_OF, "listitem1"); + + // 'node child of' relation for lists and trees organized by groups, with + // intervening generic accessibles between widget components. + testRelation("list2_listitem1.1", RELATION_NODE_CHILD_OF, "list2_listitem1"); + testRelation("list2_listitem1.2", RELATION_NODE_CHILD_OF, "list2_listitem1"); + testRelation("tree4_treeitem1.1", RELATION_NODE_CHILD_OF, "tree4_treeitem1"); + testRelation("tree4_treeitem1.2", RELATION_NODE_CHILD_OF, "tree4_treeitem1"); + + // 'node child of' relation for a treeitem sibling group special case, + // with intervening generic accessibles. In this case, if a treeitem's + // parent is a group and that group has a previous treeitem sibling, the + // treeitem is a child of that previous treeitem sibling. + testRelation("tree3_treeitem1.1", RELATION_NODE_CHILD_OF, "tree3_treeitem1"); + testRelation("tree3_treeitem1.2", RELATION_NODE_CHILD_OF, "tree3_treeitem1"); + + // 'node child of' relation for the document having window, returns + // direct accessible parent (fixed in bug 419770). + var iframeElmObj = {}; + var iframeAcc = getAccessible("iframe", null, iframeElmObj); + var iframeDoc = iframeElmObj.value.contentDocument; + var iframeDocAcc = getAccessible(iframeDoc); + testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc); + + // 'node parent of' relation on ARIA tree and treegrid. + testRelation("tree", RELATION_NODE_PARENT_OF, + ["treeitem1", "treeitem2", // aria-owns + "treeitem3", "treeitem4", "treeitem6"]); // children + testRelation("treeitem4", RELATION_NODE_PARENT_OF, + "treeitem5"); // aria-level + testRelation("treeitem6", RELATION_NODE_PARENT_OF, + "treeitem7"); // // group role + testRelation("tree2", RELATION_NODE_PARENT_OF, "tree2_ti1"); // group role + testRelation("tree2_ti1", RELATION_NODE_PARENT_OF, + ["tree2_ti1a", "tree2_ti1b"]); // group role + + testRelation("treegridrow2", RELATION_NODE_PARENT_OF, "treegridrow3"); + testRelation("treegrid", RELATION_NODE_PARENT_OF, + ["treegridrow1", "treegridrow2"]); + + // 'node parent of' relation on ARIA grid. + // 'node parent of' relation on ARIA grid's row. + // Should only have relation to child through aria-level. + testRelation("simplegrid-row2", RELATION_NODE_PARENT_OF, + "simplegrid-row3"); + + // 'node parent of' relation on ARIA list structured by groups + testRelation("list", RELATION_NODE_PARENT_OF, + "listitem1"); + testRelation("listitem1", RELATION_NODE_PARENT_OF, + [ "listitem1.1", "listitem1.2" ]); + + // aria-atomic + testRelation(getNode("atomic").firstChild, RELATION_MEMBER_OF, "atomic"); + + // aria-controls + getAccessible("tab"); + todo(false, + "Getting an accessible tab, otherwise relations for tabpanel aren't cached. Bug 606924 will fix that."); + testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab"); + testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel"); + + // aria-controls, multiple relations + testRelation("lr1", RELATION_CONTROLLED_BY, "button"); + testRelation("lr2", RELATION_CONTROLLED_BY, "button"); + testRelation("button", RELATION_CONTROLLER_FOR, ["lr1", "lr2"]); + + // aria-flowto + testRelation("flowto", RELATION_FLOWS_TO, "flowfrom"); + testRelation("flowfrom", RELATION_FLOWS_FROM, "flowto"); + + // aria-flowto, multiple relations + testRelation("flowto1", RELATION_FLOWS_TO, ["flowfrom1", "flowfrom2"]); + testRelation("flowfrom1", RELATION_FLOWS_FROM, "flowto1"); + testRelation("flowfrom2", RELATION_FLOWS_FROM, "flowto1"); + + // 'default button' relation + testRelation("input", RELATION_DEFAULT_BUTTON, "submit"); + + // output 'for' relations + testRelation("output", RELATION_CONTROLLED_BY, ["input", "input2"]); + testRelation("output2", RELATION_CONTROLLED_BY, ["input", "input2"]); + testRelation("input", RELATION_CONTROLLER_FOR, ["output", "output2"]); + testRelation("input2", RELATION_CONTROLLER_FOR, ["output", "output2"]); + + // 'described by'/'description for' relation for html:table and + // html:caption + testRelation("caption", RELATION_LABEL_FOR, "table"); + testRelation("table", RELATION_LABELLED_BY, "caption"); + + // 'labelled by'/'label for' relation for html:fieldset and + // html:legend + testRelation("legend", RELATION_LABEL_FOR, "fieldset"); + testRelation("fieldset", RELATION_LABELLED_BY, "legend"); + + // containing relations + testRelation("control1_1", RELATION_CONTAINING_DOCUMENT, document); + testRelation("control1_1", RELATION_CONTAINING_TAB_PANE, getTabDocAccessible("control1_1")); + testRelation("control1_1", RELATION_CONTAINING_APPLICATION, getApplicationAccessible()); + + // details + testRelation("has_details", RELATION_DETAILS, "details"); + testRelation("details", RELATION_DETAILS_FOR, "has_details"); + testRelation("has_multiple_details", RELATION_DETAILS, ["details2", "details3"]); + testRelation("details2", RELATION_DETAILS_FOR, "has_multiple_details"); + testRelation("details3", RELATION_DETAILS_FOR, "has_multiple_details"); + + // error + testRelation("has_error", RELATION_ERRORMSG, "error"); + testRelation("error", RELATION_ERRORMSG_FOR, "has_error"); + + // finish test + SimpleTest.finish(); + } + + disableLogging(); // from test_embeds.xhtml + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=475298" + title="mochitests for accessible relations"> + Bug 475298 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=527461" + title="Implement RELATION_NODE_PARENT_OF"> + Bug 527461 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=558036" + title="make HTML <output> accessible"> + Bug 558036 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=682790" + title="Ignore implicit label association when it's associated explicitly"> + Bug 682790 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=687393" + title="HTML select options gets relation from containing label"> + Bug 687393 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=864224" + title="Support nested ARIA listitems structured by role='group'"> + Bug 864224 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <label id="label1_1" for="control1_1">label</label> + <input id="control1_1"> + + <label id="label1_2" for="control1_2">label</label> + <label id="label1_3" for="control1_2">label</label> + <input id="control1_2"> + + <label id="label1_4">Label + <select id="control1_4"> + <option id="control1_4_option1">option</option> + </select> + </label> + <label id="label1_5">Label + <button id="control1_5">button</button> + </label> + <label id="label1_6">Label + <input id="control1_6"> + </label> + <label id="label1_7">Label + <input id="control1_7" type="checkbox"> + </label> + <label id="label1_8">Label + <input id="control1_8" type="radio"> + </label> + <label id="label1_9">Label + <input id="control1_9" type="button" value="button"> + </label> + <label id="label1_10">Label + <input id="control1_10" type="submit"> + </label> + <label id="label1_11">Label + <input id="control1_11" type="image"> + </label> + <label id="label1_12">Label + <progress id="control1_12"></progress> + </label> + + <label id="label1_13" for="">Label + <input id="control1_13"> + </label> + <label id="label1_14" for="control1_14">Label + <input id="control1_14"> + </label> + + <span id="label2">label</span> + <span role="checkbox" id="checkbox2" aria-labelledby="label2"></span> + + <span id="label3">label1</span> + <span id="label4">label2</span> + <span role="checkbox" id="checkbox3" aria-labelledby="label3 label4"></span> + + <span id="descr1">description</span> + <span role="checkbox" id="checkbox4" aria-describedby="descr1"></span> + + <span id="descr2">description1</span> + <span id="descr3">description2</span> + <span role="checkbox" id="checkbox5" aria-describedby="descr2 descr3"></span> + + <div role="treeitem" id="treeitem1">Yellow</div> + <div role="treeitem" id="treeitem2">Orange</div> + <div id="tree" role="tree" aria-owns="treeitem1 treeitem2"> + <div role="treeitem" id="treeitem3">Blue</div> + <div role="treeitem" id="treeitem4" aria-level="1">Green</div> + <div role="treeitem" id="treeitem5" aria-level="2">Light green</div> + <div role="treeitem" id="treeitem6" aria-level="1">Green2</div> + <div role="group"> + <div role="treeitem" id="treeitem7">Super light green</div> + </div> + </div> + + <div role="grid" id="simplegrid"> + <div role="row" id="simplegrid-row1" aria-level="1"> + <div role="gridcell">cell 1,1</div> + <div role="gridcell">cell 1,2</div> + </div> + <div role="row" id="simplegrid-row2" aria-level="1"> + <div role="gridcell">cell 2,1</div> + <div role="gridcell">cell 2,2</div> + </div> + <div role="row" id="simplegrid-row3" aria-level="2"> + <div role="gridcell">cell 3,1</div> + <div role="gridcell">cell 3,2</div> + </div> + </div> + + <ul role="tree" id="tree2"> + <li role="treeitem" id="tree2_ti1">Item 1 + <ul role="group"> + <li role="treeitem" id="tree2_ti1a">Item 1A</li> + <li role="treeitem" id="tree2_ti1b">Item 1B</li> + </ul> + </li> + </ul> + + <div role="tree" id="tree3"> + <div tabindex="0"> + <div role="treeitem" id="tree3_treeitem1">1</div> + </div> + <div tabindex="0"> + <div role="group"> + <div role="treeitem" id="tree3_treeitem1.1">1.1</div> + <div role="treeitem" id="tree3_treeitem1.2">1.2</div> + </div> + </div> + </div> + + <div role="treegrid" id="treegrid"> + <div role="row" id="treegridrow1"> + <span role="gridcell">cell1</span><span role="gridcell">cell2</span> + </div> + <div role="row" id="treegridrow2" aria-level="1"> + <span role="gridcell">cell3</span><span role="gridcell">cell4</span> + </div> + <div role="row" id="treegridrow3" aria-level="2"> + <span role="gridcell">cell5</span><span role="gridcell">cell6</span> + </div> + </div> + + <div role="list" id="list"> + <div role="listitem" id="listitem1">Item 1 + <div role="group"> + <div role="listitem" id="listitem1.1">Item 1A</div> + <div role="listitem" id="listitem1.2">Item 1B</div> + </div> + </div> + </div> + + <div role="tree" id="tree4"> + <div role="treeitem" id="tree4_treeitem1">1 + <div tabindex="0"> + <div role="group"> + <div role="treeitem" id="tree4_treeitem1.1">1.1</div> + <div role="treeitem" id="tree4_treeitem1.2">1.2</div> + </div> + </div> + </div> + </div> + + <div role="list" id="list2"> + <div role="listitem" id="list2_listitem1">1 + <div tabindex="0"> + <div role="group"> + <div role="listitem" id="list2_listitem1.1">1.1</div> + <div role="listitem" id="list2_listitem1.2">1.2</div> + </div> + </div> + </div> + </div> + + <iframe id="iframe"></iframe> + + <div id="tablist" role="tablist"> + <div id="tab" role="tab" aria-controls="tabpanel">tab</div> + </div> + <div id="tabpanel" role="tabpanel">tabpanel</div> + + <div id="lr1" aria-live="assertive">1</div> + <div id="lr2" aria-live="assertive">a</div> + <input type="button" id="button" aria-controls="lr1 lr2" + onclick="getNode('lr1').textContent += '1'; getNode('lr2').textContent += 'a';"/> + + <div id="atomic" aria-atomic="true">live region</div> + + <span id="flowto" aria-flowto="flowfrom">flow to</span> + <span id="flowfrom">flow from</span> + + <span id="flowto1" aria-flowto="flowfrom1 flowfrom2">flow to</span> + <span id="flowfrom1">flow from</span> + <span id="flowfrom2">flow from</span> + + <form id="form"> + <input id="input" /> + <input id="input2" /> + <input type="submit" id="submit" /> + <output id="output" style="display:block" for="input input2"></output> + <output id="output2" for="input input2"></output> + </form> + + <table id="table"> + <caption id="caption">tabple caption</caption> + <tr> + <td>cell1</td><td>cell2</td> + </tr> + </table> + + <fieldset id="fieldset"> + <legend id="legend">legend</legend> + <input /> + </fieldset> + + <input id="has_details" aria-details="details"><section id="details"></section> + <input id="has_multiple_details" aria-details="details2 details3"><section id="details2"></section><section id="details3"></section> + <input id="has_error" aria-errormessage="error"><section id="error"></section> +</body> +</html> diff --git a/accessible/tests/mochitest/relations/test_general.xhtml b/accessible/tests/mochitest/relations/test_general.xhtml new file mode 100644 index 0000000000..bc3b328fd9 --- /dev/null +++ b/accessible/tests/mochitest/relations/test_general.xhtml @@ -0,0 +1,237 @@ +<?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" + xmlns:html="http://www.w3.org/1999/xhtml" + title="nsIAccessible::getAccessibleRelated() tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../relations.js" /> + <script type="application/javascript" + src="../role.js" /> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + // xul:label@control + testRelation("label1", RELATION_LABEL_FOR, "checkbox1"); + testRelation("checkbox1", RELATION_LABELLED_BY, "label1"); + + // xul:label@control, multiple + testRelation("label1_1", RELATION_LABEL_FOR, "checkbox1_1"); + testRelation("label1_2", RELATION_LABEL_FOR, "checkbox1_1"); + testRelation("checkbox1_1", RELATION_LABELLED_BY, + [ "label1_1", "label1_2" ]); + + // aria-labelledby + testRelation("label2", RELATION_LABEL_FOR, "checkbox2"); + testRelation("checkbox2", RELATION_LABELLED_BY, "label2"); + + // aria-labelledby, multiple relations + testRelation("label3", RELATION_LABEL_FOR, "checkbox3"); + testRelation("label4", RELATION_LABEL_FOR, "checkbox3"); + testRelation("checkbox3", RELATION_LABELLED_BY, ["label3", "label4"]); + + // xul:label@control referring to HTML element + testRelation("label_input", RELATION_LABEL_FOR, "input"); + testRelation("input", RELATION_LABELLED_BY, "label_input"); + + // aria-describedby + testRelation("descr1", RELATION_DESCRIPTION_FOR, "checkbox4"); + testRelation("checkbox4", RELATION_DESCRIBED_BY, "descr1"); + + // aria-describedby, multiple relations + testRelation("descr2", RELATION_DESCRIPTION_FOR, "checkbox5"); + testRelation("descr3", RELATION_DESCRIPTION_FOR, "checkbox5"); + testRelation("checkbox5", RELATION_DESCRIBED_BY, ["descr2", "descr3"]); + + // xul:description@control + testRelation("descr4", RELATION_DESCRIPTION_FOR, "checkbox6"); + testRelation("checkbox6", RELATION_DESCRIBED_BY, "descr4"); + + // xul:description@control, multiple + testRelation("descr5", RELATION_DESCRIPTION_FOR, "checkbox7"); + testRelation("descr6", RELATION_DESCRIPTION_FOR, "checkbox7"); + testRelation("checkbox7", RELATION_DESCRIBED_BY, + [ "descr5", "descr6" ]); + + // aria_owns, multiple relations + testRelation("treeitem1", RELATION_NODE_CHILD_OF, "tree"); + testRelation("treeitem2", RELATION_NODE_CHILD_OF, "tree"); + + // 'node child of' relation for outlineitem role + testRelation("treeitem3", RELATION_NODE_CHILD_OF, "tree"); + testRelation("treeitem4", RELATION_NODE_CHILD_OF, "tree"); + testRelation("treeitem5", RELATION_NODE_CHILD_OF, "treeitem4"); + + // no relation node_child_of for accessible contained in an unexpected + // parent + testRelation("treeitem6", RELATION_NODE_CHILD_OF, null); + + // 'node child of' relation for the document having window, returns + // direct accessible parent (fixed in bug 419770). + var iframeElmObj = {}; + var iframeAcc = getAccessible("iframe", null, iframeElmObj); + var iframeDoc = iframeElmObj.value.contentDocument; + var iframeDocAcc = getAccessible(iframeDoc); + testRelation(iframeDocAcc, RELATION_NODE_CHILD_OF, iframeAcc); + + // aria-controls + getAccessible("tab"); + todo(false, + "Getting an accessible tab, otherwise relations for tabpanel aren't cached. Bug 606924 will fix that."); + testRelation("tabpanel", RELATION_CONTROLLED_BY, "tab"); + testRelation("tab", RELATION_CONTROLLER_FOR, "tabpanel"); + + // aria-controls, multiple relations + testRelation("lr1", RELATION_CONTROLLED_BY, "button"); + testRelation("lr2", RELATION_CONTROLLED_BY, "button"); + testRelation("button", RELATION_CONTROLLER_FOR, ["lr1", "lr2"]); + + // aria-flowto + testRelation("flowto", RELATION_FLOWS_TO, "flowfrom"); + testRelation("flowfrom", RELATION_FLOWS_FROM, "flowto"); + + // aria-flowto, multiple relations + testRelation("flowto1", RELATION_FLOWS_TO, ["flowfrom1", "flowfrom2"]); + testRelation("flowfrom1", RELATION_FLOWS_FROM, "flowto1"); + testRelation("flowfrom2", RELATION_FLOWS_FROM, "flowto1"); + + // 'labelled by'/'label for' relation for xul:groupbox and xul:label + var groupboxAcc = getAccessible("groupbox"); + var labelAcc = groupboxAcc.firstChild; + testRelation(labelAcc, RELATION_LABEL_FOR, groupboxAcc); + testRelation(groupboxAcc, RELATION_LABELLED_BY, labelAcc); + + // 'labelled by'/'label for' relations for xul:tab and xul:tabpanel + // (fixed in bug 366527) + testRelation("tabpanel1", RELATION_LABELLED_BY, "tab1"); + testRelation("tab1", RELATION_LABEL_FOR, "tabpanel1"); + testRelation("tabpanel2", RELATION_LABELLED_BY, "tab2"); + testRelation("tab2", RELATION_LABEL_FOR, "tabpanel2"); + testRelation("tabpanel3", RELATION_LABELLED_BY, "tab3"); + testRelation("tab3", RELATION_LABEL_FOR, "tabpanel3"); + + // finish test + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <vbox style="overflow: auto;" flex="1"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=475298" + title="mochitests for accessible relations"> + Mozilla Bug 475298 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673389" + title="node_child_of on an item not in a proper container"> + Mozilla Bug 67389 + </a><br/> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <label id="label1" control="checkbox1">label</label> + <checkbox id="checkbox1"/> + + <label id="label1_1" control="checkbox1_1">label</label> + <label id="label1_2" control="checkbox1_1">label</label> + <checkbox id="checkbox1_1"/> + + <description id="label2">label</description> + <description role="checkbox" id="checkbox2" aria-labelledby="label2"/> + + <description id="label3">label</description> + <description id="label4">label</description> + <description role="checkbox" id="checkbox3" + aria-labelledby="label3 label4"/> + + <label id="label_input" control="input">label</label> + <html:input id="input"/> + + <description id="descr1">description</description> + <description role="checkbox" id="checkbox4" aria-describedby="descr1"/> + + <description id="descr2">label</description> + <description id="descr3">label</description> + <description role="checkbox" id="checkbox5" + aria-describedby="descr2 descr3"/> + + <description id="descr4" control="checkbox6">description</description> + <checkbox id="checkbox6"/> + + <description id="descr5" control="checkbox7">description</description> + <description id="descr6" control="checkbox7">description</description> + <checkbox id="checkbox7"/> + + <description role="treeitem" id="treeitem1">Yellow</description> + <description role="treeitem" id="treeitem2">Orange</description> + <vbox id="tree" role="tree" aria-owns="treeitem1 treeitem2"> + <description role="treeitem" id="treeitem3">Blue</description> + <description role="treeitem" id="treeitem4" aria-level="1">Green</description> + <description role="treeitem" id="treeitem5" aria-level="2">Light green</description> + </vbox> + + <description role="treeitem" id="treeitem6">Dark green</description> + + <iframe id="iframe"/> + + <hbox id="tablist" role="tablist"> + <description id="tab" role="tab" aria-controls="tabpanel">tab</description> + </hbox> + <description id="tabpanel" role="tabpanel">tabpanel</description> + + <description id="lr1" aria-live="assertive">1</description> + <description id="lr2" aria-live="assertive">a</description> + <button id="button" aria-controls="lr1 lr2" label="button" + oncommand="getNode('lr1').textContent += '1'; getNode('lr2').textContent += 'a';"/> + + <description id="flowto1" aria-flowto="flowfrom1 flowfrom2">flow to</description> + <description id="flowfrom1">flow from</description> + <description id="flowfrom2">flow from</description> + + <description id="flowto" aria-flowto="flowfrom">flow to</description> + <description id="flowfrom">flow from</description> + + <groupbox id="groupbox"> + <label value="caption"/> + </groupbox> + + <tabbox> + <tabs> + <tab label="tab1" id="tab1"/> + <tab label="tab2" id="tab2" linkedpanel="tabpanel2"/> + <tab label="tab3" id="tab3" linkedpanel="tabpanel3"/> + </tabs> + <tabpanels> + <tabpanel id="tabpanel1"> + <description>tabpanel1</description> + </tabpanel> + <tabpanel id="tabpanel3"> + <description>tabpanel3</description> + </tabpanel> + <tabpanel id="tabpanel2"> + <description>tabpanel2</description> + </tabpanel> + </tabpanels> + </tabbox> + + </vbox> +</window> diff --git a/accessible/tests/mochitest/relations/test_groupInfoUpdate.html b/accessible/tests/mochitest/relations/test_groupInfoUpdate.html new file mode 100644 index 0000000000..efca27617c --- /dev/null +++ b/accessible/tests/mochitest/relations/test_groupInfoUpdate.html @@ -0,0 +1,57 @@ +<html> +<head> + <title>Test accessible relations when AccGroupInfo updated</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../relations.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + async function doTests() { + info("Testing NODE_CHILD_OF update after DOM removal"); + testRelation("l1i2", RELATION_NODE_CHILD_OF, "l1i1"); + let reorder = waitForEvent(EVENT_REORDER, "l1"); + getNode("l1i1").remove(); + await reorder; + testRelation("l1i2", RELATION_NODE_CHILD_OF, "l1"); + + info("Testing NODE_CHILD_OF update after aria-owns removal"); + testRelation("l2i2", RELATION_NODE_CHILD_OF, "l2i1"); + reorder = waitForEvent(EVENT_REORDER, "l2"); + // Move l2i1 out of l2 using aria-owns. + getNode("l2trash").setAttribute("aria-owns", "l2i1"); + await reorder; + testRelation("l2i2", RELATION_NODE_CHILD_OF, "l2"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body id="body"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="l1" role="list"> + <div id="l1i1" role="listitem" aria-level="1">a</div> + <div id="l1i2" role="listitem" aria-level="2">b</div> + </div> + + <div id="l2" role="list"> + <div id="l2i1" role="listitem" aria-level="1">a</div> + <div id="l2i2" role="listitem" aria-level="2">b</div> + </div> + <div id="l2trash"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/relations/test_shadowdom.html b/accessible/tests/mochitest/relations/test_shadowdom.html new file mode 100644 index 0000000000..adb9490d99 --- /dev/null +++ b/accessible/tests/mochitest/relations/test_shadowdom.html @@ -0,0 +1,58 @@ +<html> + +<head> + <title>Explicit content and shadow DOM content relations tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../relations.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // explicit content + let label = document.getElementById("label"); + let element = document.getElementById("element"); + testRelation(label, RELATION_LABEL_FOR, element); + testRelation(element, RELATION_LABELLED_BY, label); + + // shadow DOM content + let shadowRoot = document.getElementById("shadowcontainer").shadowRoot; + let shadowLabel = shadowRoot.getElementById("label"); + let shadowElement = shadowRoot.getElementById("element"); + + testRelation(shadowLabel, RELATION_LABEL_FOR, shadowElement); + testRelation(shadowElement, RELATION_LABELLED_BY, shadowLabel); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + addA11yLoadEvent(doTest, window); + </script> +</head> + +<body> + <p id="display"></p> + <div id="content"> + <div id="label"></div> + <div id="element" aria-labelledby="label"></div> + <div id="shadowcontainer"></div> + <script> + let shadowRoot = document.getElementById("shadowcontainer"). + attachShadow({mode: "open"}); + shadowRoot.innerHTML = + `<div id="label"></div><div id="element" aria-labelledby="label"></div>`; + </script> + </div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/relations/test_tabbrowser.xhtml b/accessible/tests/mochitest/relations/test_tabbrowser.xhtml new file mode 100644 index 0000000000..3356bc6140 --- /dev/null +++ b/accessible/tests/mochitest/relations/test_tabbrowser.xhtml @@ -0,0 +1,109 @@ +<?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="Accessible XUL tabbrowser relation tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../relations.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Invoker + function testTabRelations() + { + this.eventSeq = [ + new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 0), + new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1) + ]; + + this.invoke = function testTabRelations_invoke() + { + var docURIs = ["about:license", "about:mozilla"]; + tabBrowser().loadTabs(docURIs, { + inBackground: false, + replace: true, + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + // Flush layout, so as to guarantee that the a11y tree is constructed. + browserDocument().documentElement.getBoundingClientRect(); + } + + this.finalCheck = function testTabRelations_finalCheck(aEvent) + { + //////////////////////////////////////////////////////////////////////// + // 'labelled by'/'label for' relations for xul:tab and xul:tabpanel + + var tabs = Array.from(tabBrowser().tabContainer.allTabs); + // For preloaded tabs, there might be items in this array where this relation + // doesn't hold, so just deal with that: + var panels = tabs.map(t => t.linkedBrowser.closest("tabpanels > *")); + + testRelation(panels[0], RELATION_LABELLED_BY, tabs[0]); + testRelation(tabs[0], RELATION_LABEL_FOR, panels[0]); + testRelation(panels[1], RELATION_LABELLED_BY, tabs[1]); + testRelation(tabs[1], RELATION_LABEL_FOR, panels[1]); + } + + this.getID = function testTabRelations_getID() + { + return "relations of tabs"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTest() + { + // Load documents into tabs and wait for DocLoadComplete events caused by + // these documents load before we start the test. + + gQueue = new eventQueue(); + + gQueue.push(new testTabRelations()); + gQueue.onFinish = function() { closeBrowserWindow(); } + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTest); + ]]> + </script> + + <vbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=552944" + title="No relationship between tabs and associated property page in new tabbrowser construct"> + Mozilla Bug 552944 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="eventdump"></vbox> + </vbox> + +</window> + diff --git a/accessible/tests/mochitest/relations/test_tree.xhtml b/accessible/tests/mochitest/relations/test_tree.xhtml new file mode 100644 index 0000000000..7c309f0956 --- /dev/null +++ b/accessible/tests/mochitest/relations/test_tree.xhtml @@ -0,0 +1,105 @@ +<?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="Accessible XUL tree relations tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../relations.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + var treeNode = getNode("tree"); + + var tree = getAccessible(treeNode); + var treeitem1 = tree.firstChild.nextSibling; + testRelation(treeitem1, RELATION_NODE_CHILD_OF, [tree]); + + var treeitem2 = treeitem1.nextSibling; + testRelation(treeitem2, RELATION_NODE_CHILD_OF, [tree]); + + var treeitem3 = treeitem2.nextSibling; + testRelation(treeitem3, RELATION_NODE_CHILD_OF, [treeitem2]); + + var treeitem4 = treeitem3.nextSibling; + testRelation(treeitem4, RELATION_NODE_CHILD_OF, [treeitem2]); + + var treeitem5 = treeitem4.nextSibling; + testRelation(treeitem5, RELATION_NODE_CHILD_OF, [tree]); + + var treeitem6 = treeitem5.nextSibling; + testRelation(treeitem6, RELATION_NODE_CHILD_OF, [tree]); + + testRelation(tree, RELATION_NODE_PARENT_OF, + [treeitem1, treeitem2, treeitem5, treeitem6]); + testRelation(treeitem2, RELATION_NODE_PARENT_OF, + [treeitem3, treeitem4]); + + // treeitems and treecells shouldn't pick up relations from tree + testRelation(treeitem1, RELATION_LABELLED_BY, null); + testRelation(treeitem1.firstChild, RELATION_LABELLED_BY, null); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yXULTreeLoadEvent(doTest, "tree", new nsTreeTreeView()); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727" + title="Reorganize implementation of XUL tree accessibility"> + Bug 503727 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=527461" + title="Implement RELATION_NODE_PARENT_OF"> + Bug 527461 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=691248" + title="XUL tree items shouldn't pick up relations from XUL tree"> + Bug 691248 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <label control="tree" value="It's a tree"/> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + <treecol id="col2" flex="1" label="column2"/> + </treecols> + <treechildren/> + </tree> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/relations/test_ui_modalprompt.html b/accessible/tests/mochitest/relations/test_ui_modalprompt.html new file mode 100644 index 0000000000..a05b273d86 --- /dev/null +++ b/accessible/tests/mochitest/relations/test_ui_modalprompt.html @@ -0,0 +1,111 @@ +<html> + +<head> + <title>Modal prompts</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../relations.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../browser.js"></script> + + <script type="application/javascript"> + SpecialPowers.pushPrefEnv({ + set: [["prompts.contentPromptSubDialog", false]], + }); + function showAlert() { + this.eventSeq = [ + { + type: EVENT_SHOW, + match(aEvent) { + return aEvent.accessible.role == ROLE_DIALOG; + }, + }, + ]; + + this.invoke = function showAlert_invoke() { + window.setTimeout( + function() { + currentTabDocument().defaultView.alert("hello"); + }, 0); + }; + + this.check = function showAlert_finalCheck(aEvent) { + if(aEvent.type === EVENT_HIDE) { + return; + } + var dialog = aEvent.accessible.DOMNode; + var info = dialog.querySelector(".tabmodalprompt-infoBody"); + testRelation(info, RELATION_DESCRIPTION_FOR, dialog); + testRelation(dialog, RELATION_DESCRIBED_BY, info); + }; + + this.getID = function showAlert_getID() { + return "show alert"; + }; + } + + function closeAlert() { + this.eventSeq = [ + { + type: EVENT_HIDE, + match(aEvent) { + return aEvent.accessible.role == ROLE_DIALOG; + }, + }, + ]; + + this.invoke = function showAlert_invoke() { + synthesizeKey("VK_RETURN", {}, browserWindow()); + }; + + this.getID = function showAlert_getID() { + return "cleanup alert"; + }; + } + + + // gA11yEventDumpToConsole = true; // debug + + var gQueue = null; + function doTests() { + gQueue = new eventQueue(); + gQueue.push(new showAlert()); + gQueue.push(new closeAlert()); + gQueue.onFinish = function() { + closeBrowserWindow(); + }; + gQueue.invoke(); // will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTests); + </script> + +</head> + +<body id="body"> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=661293" + title="The tabmodalprompt dialog's prompt label doesn't get the text properly associated for accessibility"> + Mozilla Bug 661293 + </a> + <br> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + +</body> +</html> diff --git a/accessible/tests/mochitest/relations/test_update.html b/accessible/tests/mochitest/relations/test_update.html new file mode 100644 index 0000000000..581d592bec --- /dev/null +++ b/accessible/tests/mochitest/relations/test_update.html @@ -0,0 +1,213 @@ +<html> + +<head> + <title>Test updating of accessible relations</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../relations.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function testRelated(aRelAttr, aHostRelation, aDependentRelation, + aHostID, aHostNodeID, aDependent1ID, aDependent2ID) { + // no attribute + testRelation(aDependent1ID, aDependentRelation, null); + testRelation(aDependent2ID, aDependentRelation, null); + if (aHostRelation) + testRelation(aHostID, aHostRelation, null); + + // set attribute + getNode(aHostNodeID).setAttribute(aRelAttr, aDependent1ID); + testRelation(aDependent1ID, aDependentRelation, aHostID); + testRelation(aDependent2ID, aDependentRelation, null); + if (aHostRelation) + testRelation(aHostID, aHostRelation, aDependent1ID); + + // change attribute + getNode(aHostNodeID).setAttribute(aRelAttr, aDependent2ID); + testRelation(aDependent1ID, aDependentRelation, null); + testRelation(aDependent2ID, aDependentRelation, aHostID); + if (aHostRelation) + testRelation(aHostID, aHostRelation, aDependent2ID); + + // remove attribute + getNode(aHostNodeID).removeAttribute(aRelAttr); + testRelation(aDependent1ID, aDependentRelation, null); + testRelation(aDependent2ID, aDependentRelation, null); + if (aHostRelation) + testRelation(aHostID, aHostRelation, null); + } + + function insertRelated(aHostRelAttr, aDependentID, aInsertHostFirst, + aHostRelation, aDependentRelation) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, document), + ]; + + this.invoke = function insertRelated_invoke() { + this.hostNode = document.createElement("div"); + this.hostNode.setAttribute(aHostRelAttr, aDependentID); + + this.dependentNode = document.createElement("div"); + this.dependentNode.setAttribute("id", aDependentID); + + if (aInsertHostFirst) { + document.body.appendChild(this.hostNode); + document.body.appendChild(this.dependentNode); + } else { + document.body.appendChild(this.dependentNode); + document.body.appendChild(this.hostNode); + } + }; + + this.finalCheck = function insertRelated_finalCheck() { + testRelation(this.dependentNode, aDependentRelation, this.hostNode); + if (aHostRelation) + testRelation(this.hostNode, aHostRelation, this.dependentNode); + }; + + this.getID = function insertRelated_getID() { + return "Insert " + aHostRelAttr + "='" + aDependentID + "' node" + + (aInsertHostFirst ? " before" : "after") + " dependent node"; + }; + } + + /** + * Relative accessible recreation shouldn't break accessible relations. + * Note: modify this case if the invoke function doesn't change accessible + * tree due to changes in layout module. It can be changed on any case + * when accessibles are recreated. + */ + function recreateRelatives(aContainerID, aLabelID, aElmID) { + this.containerNode = getNode(aContainerID); + this.container = getNode(this.containerNode); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.container), + new invokerChecker(EVENT_SHOW, this.containerNode), + ]; + + this.invoke = function recreateRelatives_invoke() { + testRelation(aLabelID, RELATION_LABEL_FOR, aElmID); + testRelation(aElmID, RELATION_LABELLED_BY, aLabelID); + + this.containerNode.setAttribute('role', 'group'); + }; + + this.finalCheck = function recreateRelatives_finalCheck() { + testRelation(aLabelID, RELATION_LABEL_FOR, aElmID); + testRelation(aElmID, RELATION_LABELLED_BY, aLabelID); + }; + + this.getID = function recreateRelatives_getID() { + return "recreate relatives "; + }; + } + + // gA11yEventDumpToConsole = true; // debug + + var gQueue = null; + + function doTest() { + // Relation updates on ARIA attribute changes. + testRelated("aria-labelledby", + RELATION_LABELLED_BY, RELATION_LABEL_FOR, + "host", "host", "dependent1", "dependent2"); + + testRelated("aria-describedby", + RELATION_DESCRIBED_BY, RELATION_DESCRIPTION_FOR, + "host", "host", "dependent1", "dependent2"); + + testRelated("aria-controls", + RELATION_CONTROLLER_FOR, RELATION_CONTROLLED_BY, + "host", "host", "dependent1", "dependent2"); + + testRelated("aria-flowto", + RELATION_FLOWS_TO, RELATION_FLOWS_FROM, + "host", "host", "dependent1", "dependent2"); + + // Document relation updates on ARIA attribute change. + testRelated("aria-labelledby", + RELATION_LABELLED_BY, RELATION_LABEL_FOR, + document, "body", "dependent1", "dependent2"); + + // Insert related accessibles into tree. + gQueue = new eventQueue(); + gQueue.push(new insertRelated("aria-labelledby", "dependent3", true, + RELATION_LABELLED_BY, RELATION_LABEL_FOR)); + gQueue.push(new insertRelated("aria-labelledby", "dependent4", false, + RELATION_LABELLED_BY, RELATION_LABEL_FOR)); + + gQueue.push(new insertRelated("aria-describedby", "dependent5", true, + RELATION_DESCRIBED_BY, + RELATION_DESCRIPTION_FOR)); + gQueue.push(new insertRelated("aria-describedby", "dependent6", false, + RELATION_DESCRIBED_BY, + RELATION_DESCRIPTION_FOR)); + + gQueue.push(new insertRelated("aria-controls", "dependent9", true, + RELATION_CONTROLLER_FOR, + RELATION_CONTROLLED_BY)); + gQueue.push(new insertRelated("aria-controls", "dependent10", false, + RELATION_CONTROLLER_FOR, + RELATION_CONTROLLED_BY)); + + gQueue.push(new insertRelated("aria-flowto", "dependent11", true, + RELATION_FLOWS_TO, RELATION_FLOWS_FROM)); + gQueue.push(new insertRelated("aria-flowto", "dependent12", false, + RELATION_FLOWS_TO, RELATION_FLOWS_FROM)); + + // Update relations when accessibles are recreated + gQueue.push(new recreateRelatives("container", "label", "input")); + + gQueue.invoke(); // will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body id="body"> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=573469" + title="Cache relations defined by ARIA attributes"> + Mozilla Bug 573469 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=631068" + title="Accessible recreation breaks relations"> + Mozilla Bug 631068 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=635346" + title="Allow relations for document defined on document content"> + Mozilla Bug 635346 + </a> + <br> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="dependent1">label</div> + <div id="dependent2">label2</div> + <div role="checkbox" id="host"></div> + + <form id="container" style="overflow: hidden;"> + <label for="input" id="label">label</label> + <input id="input"> + </form> +</body> +</html> diff --git a/accessible/tests/mochitest/role.js b/accessible/tests/mochitest/role.js new file mode 100644 index 0000000000..36fa7af84f --- /dev/null +++ b/accessible/tests/mochitest/role.js @@ -0,0 +1,200 @@ +/* import-globals-from common.js */ + +// ////////////////////////////////////////////////////////////////////////////// +// Role constants + +const ROLE_ALERT = nsIAccessibleRole.ROLE_ALERT; +const ROLE_ARTICLE = nsIAccessibleRole.ROLE_ARTICLE; +const ROLE_ANIMATION = nsIAccessibleRole.ROLE_ANIMATION; +const ROLE_APPLICATION = nsIAccessibleRole.ROLE_APPLICATION; +const ROLE_APP_ROOT = nsIAccessibleRole.ROLE_APP_ROOT; +const ROLE_AUTOCOMPLETE = nsIAccessibleRole.ROLE_AUTOCOMPLETE; +const ROLE_BLOCKQUOTE = nsIAccessibleRole.ROLE_BLOCKQUOTE; +const ROLE_BUTTONDROPDOWNGRID = nsIAccessibleRole.ROLE_BUTTONDROPDOWNGRID; +const ROLE_CANVAS = nsIAccessibleRole.ROLE_CANVAS; +const ROLE_CAPTION = nsIAccessibleRole.ROLE_CAPTION; +const ROLE_CELL = nsIAccessibleRole.ROLE_CELL; +const ROLE_CHECKBUTTON = nsIAccessibleRole.ROLE_CHECKBUTTON; +const ROLE_CHECK_MENU_ITEM = nsIAccessibleRole.ROLE_CHECK_MENU_ITEM; +const ROLE_CHROME_WINDOW = nsIAccessibleRole.ROLE_CHROME_WINDOW; +const ROLE_CODE = nsIAccessibleRole.ROLE_CODE; +const ROLE_COLUMNHEADER = nsIAccessibleRole.ROLE_COLUMNHEADER; +const ROLE_COMBOBOX = nsIAccessibleRole.ROLE_COMBOBOX; +const ROLE_COMBOBOX_LIST = nsIAccessibleRole.ROLE_COMBOBOX_LIST; +const ROLE_COMBOBOX_OPTION = nsIAccessibleRole.ROLE_COMBOBOX_OPTION; +const ROLE_COMMENT = nsIAccessibleRole.ROLE_COMMENT; +const ROLE_CONTENT_DELETION = nsIAccessibleRole.ROLE_CONTENT_DELETION; +const ROLE_CONTENT_INSERTION = nsIAccessibleRole.ROLE_CONTENT_INSERTION; +const ROLE_DATE_EDITOR = nsIAccessibleRole.ROLE_DATE_EDITOR; +const ROLE_DEFINITION = nsIAccessibleRole.ROLE_DEFINITION; +const ROLE_DEFINITION_LIST = nsIAccessibleRole.ROLE_DEFINITION_LIST; +const ROLE_DETAILS = nsIAccessibleRole.ROLE_DETAILS; +const ROLE_DIAGRAM = nsIAccessibleRole.ROLE_DIAGRAM; +const ROLE_DIALOG = nsIAccessibleRole.ROLE_DIALOG; +const ROLE_DOCUMENT = nsIAccessibleRole.ROLE_DOCUMENT; +const ROLE_EDITCOMBOBOX = nsIAccessibleRole.ROLE_EDITCOMBOBOX; +const ROLE_EMBEDDED_OBJECT = nsIAccessibleRole.ROLE_EMBEDDED_OBJECT; +const ROLE_ENTRY = nsIAccessibleRole.ROLE_ENTRY; +const ROLE_EQUATION = nsIAccessibleRole.ROLE_EQUATION; +const ROLE_FIGURE = nsIAccessibleRole.ROLE_FIGURE; +const ROLE_FOOTER = nsIAccessibleRole.ROLE_FOOTER; +const ROLE_FOOTNOTE = nsIAccessibleRole.ROLE_FOOTNOTE; +const ROLE_FLAT_EQUATION = nsIAccessibleRole.ROLE_FLAT_EQUATION; +const ROLE_FORM = nsIAccessibleRole.ROLE_FORM; +const ROLE_FORM_LANDMARK = nsIAccessibleRole.ROLE_FORM_LANDMARK; +const ROLE_GRAPHIC = nsIAccessibleRole.ROLE_GRAPHIC; +const ROLE_GRID_CELL = nsIAccessibleRole.ROLE_GRID_CELL; +const ROLE_GROUPING = nsIAccessibleRole.ROLE_GROUPING; +const ROLE_HEADER = nsIAccessibleRole.ROLE_HEADER; +const ROLE_HEADING = nsIAccessibleRole.ROLE_HEADING; +const ROLE_IMAGE_MAP = nsIAccessibleRole.ROLE_IMAGE_MAP; +const ROLE_INTERNAL_FRAME = nsIAccessibleRole.ROLE_INTERNAL_FRAME; +const ROLE_LABEL = nsIAccessibleRole.ROLE_LABEL; +const ROLE_LANDMARK = nsIAccessibleRole.ROLE_LANDMARK; +const ROLE_LINK = nsIAccessibleRole.ROLE_LINK; +const ROLE_LIST = nsIAccessibleRole.ROLE_LIST; +const ROLE_LISTBOX = nsIAccessibleRole.ROLE_LISTBOX; +const ROLE_LISTITEM = nsIAccessibleRole.ROLE_LISTITEM; +const ROLE_LISTITEM_MARKER = nsIAccessibleRole.ROLE_LISTITEM_MARKER; +const ROLE_MARK = nsIAccessibleRole.ROLE_MARK; +const ROLE_MATHML_MATH = nsIAccessibleRole.ROLE_MATHML_MATH; +const ROLE_MATHML_IDENTIFIER = nsIAccessibleRole.ROLE_MATHML_IDENTIFIER; +const ROLE_MATHML_NUMBER = nsIAccessibleRole.ROLE_MATHML_NUMBER; +const ROLE_MATHML_OPERATOR = nsIAccessibleRole.ROLE_MATHML_OPERATOR; +const ROLE_MATHML_TEXT = nsIAccessibleRole.ROLE_MATHML_TEXT; +const ROLE_MATHML_STRING_LITERAL = nsIAccessibleRole.ROLE_MATHML_STRING_LITERAL; +const ROLE_MATHML_GLYPH = nsIAccessibleRole.ROLE_MATHML_GLYPH; +const ROLE_MATHML_ROW = nsIAccessibleRole.ROLE_MATHML_ROW; +const ROLE_MATHML_FRACTION = nsIAccessibleRole.ROLE_MATHML_FRACTION; +const ROLE_MATHML_SQUARE_ROOT = nsIAccessibleRole.ROLE_MATHML_SQUARE_ROOT; +const ROLE_MATHML_ROOT = nsIAccessibleRole.ROLE_MATHML_ROOT; +const ROLE_MATHML_FENCED = nsIAccessibleRole.ROLE_MATHML_FENCED; +const ROLE_MATHML_ENCLOSED = nsIAccessibleRole.ROLE_MATHML_ENCLOSED; +const ROLE_MATHML_STYLE = nsIAccessibleRole.ROLE_MATHML_STYLE; +const ROLE_MATHML_SUB = nsIAccessibleRole.ROLE_MATHML_SUB; +const ROLE_MATHML_SUP = nsIAccessibleRole.ROLE_MATHML_SUP; +const ROLE_MATHML_SUB_SUP = nsIAccessibleRole.ROLE_MATHML_SUB_SUP; +const ROLE_MATHML_UNDER = nsIAccessibleRole.ROLE_MATHML_UNDER; +const ROLE_MATHML_OVER = nsIAccessibleRole.ROLE_MATHML_OVER; +const ROLE_MATHML_UNDER_OVER = nsIAccessibleRole.ROLE_MATHML_UNDER_OVER; +const ROLE_MATHML_MULTISCRIPTS = nsIAccessibleRole.ROLE_MATHML_MULTISCRIPTS; +const ROLE_MATHML_TABLE = nsIAccessibleRole.ROLE_MATHML_TABLE; +const ROLE_MATHML_LABELED_ROW = nsIAccessibleRole.ROLE_MATHML_LABELED_ROW; +const ROLE_MATHML_TABLE_ROW = nsIAccessibleRole.ROLE_MATHML_TABLE_ROW; +const ROLE_MATHML_CELL = nsIAccessibleRole.ROLE_MATHML_CELL; +const ROLE_MATHML_ACTION = nsIAccessibleRole.ROLE_MATHML_ACTION; +const ROLE_MATHML_ERROR = nsIAccessibleRole.ROLE_MATHML_ERROR; +const ROLE_MATHML_STACK = nsIAccessibleRole.ROLE_MATHML_STACK; +const ROLE_MATHML_LONG_DIVISION = nsIAccessibleRole.ROLE_MATHML_LONG_DIVISION; +const ROLE_MATHML_STACK_GROUP = nsIAccessibleRole.ROLE_MATHML_STACK_GROUP; +const ROLE_MATHML_STACK_ROW = nsIAccessibleRole.ROLE_MATHML_STACK_ROW; +const ROLE_MATHML_STACK_CARRIES = nsIAccessibleRole.ROLE_MATHML_STACK_CARRIES; +const ROLE_MATHML_STACK_CARRY = nsIAccessibleRole.ROLE_MATHML_STACK_CARRY; +const ROLE_MATHML_STACK_LINE = nsIAccessibleRole.ROLE_MATHML_STACK_LINE; +const ROLE_MENUBAR = nsIAccessibleRole.ROLE_MENUBAR; +const ROLE_MENUITEM = nsIAccessibleRole.ROLE_MENUITEM; +const ROLE_MENUPOPUP = nsIAccessibleRole.ROLE_MENUPOPUP; +const ROLE_METER = nsIAccessibleRole.ROLE_METER; +const ROLE_NAVIGATION = nsIAccessibleRole.ROLE_NAVIGATION; +const ROLE_NON_NATIVE_DOCUMENT = nsIAccessibleRole.ROLE_NON_NATIVE_DOCUMENT; +const ROLE_NOTHING = nsIAccessibleRole.ROLE_NOTHING; +const ROLE_NOTE = nsIAccessibleRole.ROLE_NOTE; +const ROLE_OPTION = nsIAccessibleRole.ROLE_OPTION; +const ROLE_OUTLINE = nsIAccessibleRole.ROLE_OUTLINE; +const ROLE_OUTLINEITEM = nsIAccessibleRole.ROLE_OUTLINEITEM; +const ROLE_PAGETAB = nsIAccessibleRole.ROLE_PAGETAB; +const ROLE_PAGETABLIST = nsIAccessibleRole.ROLE_PAGETABLIST; +const ROLE_PANE = nsIAccessibleRole.ROLE_PANE; +const ROLE_PARAGRAPH = nsIAccessibleRole.ROLE_PARAGRAPH; +const ROLE_PARENT_MENUITEM = nsIAccessibleRole.ROLE_PARENT_MENUITEM; +const ROLE_PASSWORD_TEXT = nsIAccessibleRole.ROLE_PASSWORD_TEXT; +const ROLE_PROGRESSBAR = nsIAccessibleRole.ROLE_PROGRESSBAR; +const ROLE_PROPERTYPAGE = nsIAccessibleRole.ROLE_PROPERTYPAGE; +const ROLE_PUSHBUTTON = nsIAccessibleRole.ROLE_PUSHBUTTON; +const ROLE_RADIOBUTTON = nsIAccessibleRole.ROLE_RADIOBUTTON; +const ROLE_RADIO_GROUP = nsIAccessibleRole.ROLE_RADIO_GROUP; +const ROLE_RADIO_MENU_ITEM = nsIAccessibleRole.ROLE_RADIO_MENU_ITEM; +const ROLE_REGION = nsIAccessibleRole.ROLE_REGION; +const ROLE_RICH_OPTION = nsIAccessibleRole.ROLE_RICH_OPTION; +const ROLE_ROW = nsIAccessibleRole.ROLE_ROW; +const ROLE_ROWHEADER = nsIAccessibleRole.ROLE_ROWHEADER; +const ROLE_SCROLLBAR = nsIAccessibleRole.ROLE_SCROLLBAR; +const ROLE_SECTION = nsIAccessibleRole.ROLE_SECTION; +const ROLE_SEPARATOR = nsIAccessibleRole.ROLE_SEPARATOR; +const ROLE_SLIDER = nsIAccessibleRole.ROLE_SLIDER; +const ROLE_SPINBUTTON = nsIAccessibleRole.ROLE_SPINBUTTON; +const ROLE_STATICTEXT = nsIAccessibleRole.ROLE_STATICTEXT; +const ROLE_STATUSBAR = nsIAccessibleRole.ROLE_STATUSBAR; +const ROLE_SUBSCRIPT = nsIAccessibleRole.ROLE_SUBSCRIPT; +const ROLE_SUGGESTION = nsIAccessibleRole.ROLE_SUGGESTION; +const ROLE_SUPERSCRIPT = nsIAccessibleRole.ROLE_SUPERSCRIPT; +const ROLE_SUMMARY = nsIAccessibleRole.ROLE_SUMMARY; +const ROLE_SWITCH = nsIAccessibleRole.ROLE_SWITCH; +const ROLE_TABLE = nsIAccessibleRole.ROLE_TABLE; +const ROLE_TERM = nsIAccessibleRole.ROLE_TERM; +const ROLE_TEXT = nsIAccessibleRole.ROLE_TEXT; +const ROLE_TEXT_CONTAINER = nsIAccessibleRole.ROLE_TEXT_CONTAINER; +const ROLE_TEXT_LEAF = nsIAccessibleRole.ROLE_TEXT_LEAF; +const ROLE_TIME_EDITOR = nsIAccessibleRole.ROLE_TIME_EDITOR; +const ROLE_TOGGLE_BUTTON = nsIAccessibleRole.ROLE_TOGGLE_BUTTON; +const ROLE_TOOLBAR = nsIAccessibleRole.ROLE_TOOLBAR; +const ROLE_TOOLTIP = nsIAccessibleRole.ROLE_TOOLTIP; +const ROLE_TREE_TABLE = nsIAccessibleRole.ROLE_TREE_TABLE; +const ROLE_WHITESPACE = nsIAccessibleRole.ROLE_WHITESPACE; + +// ////////////////////////////////////////////////////////////////////////////// +// Public methods + +/** + * Test that the role of the given accessible is the role passed in. + * + * @param aAccOrElmOrID the accessible, DOM element or ID to be tested. + * @param aRole The role that is to be expected. + */ +function testRole(aAccOrElmOrID, aRole) { + var role = getRole(aAccOrElmOrID); + is(role, aRole, "Wrong role for " + prettyName(aAccOrElmOrID) + "!"); +} + +/** + * Return the role of the given accessible. Return -1 if accessible could not + * be retrieved. + * + * @param aAccOrElmOrID [in] The accessible, DOM element or element ID the + * accessible role is being requested for. + */ +function getRole(aAccOrElmOrID) { + var acc = getAccessible(aAccOrElmOrID); + if (!acc) { + return -1; + } + + var role = -1; + try { + role = acc.role; + } catch (e) { + ok(false, "Role for " + aAccOrElmOrID + " could not be retrieved!"); + } + + return role; +} + +/** + * Analogy of SimpleTest.is function used to check the role. + */ +function isRole(aIdentifier, aRole, aMsg) { + var role = getRole(aIdentifier); + if (role == -1) { + return; + } + + if (role == aRole) { + ok(true, aMsg); + return; + } + + var got = roleToString(role); + var expected = roleToString(aRole); + + ok(false, aMsg + "got '" + got + "', expected '" + expected + "'"); +} diff --git a/accessible/tests/mochitest/role/a11y.ini b/accessible/tests/mochitest/role/a11y.ini new file mode 100644 index 0000000000..02a2b61cb1 --- /dev/null +++ b/accessible/tests/mochitest/role/a11y.ini @@ -0,0 +1,13 @@ +[DEFAULT] +support-files = + chrome_body_role_alert.xhtml + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/moz.png + +[test_aria.html] +[test_aria.xhtml] +[test_dpub_aria.html] +[test_general.html] +[test_general.xhtml] +[test_graphics_aria.html] +[test_svg.html] diff --git a/accessible/tests/mochitest/role/chrome_body_role_alert.xhtml b/accessible/tests/mochitest/role/chrome_body_role_alert.xhtml new file mode 100644 index 0000000000..29ff10fb01 --- /dev/null +++ b/accessible/tests/mochitest/role/chrome_body_role_alert.xhtml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml"> + <body role="alert"> + </body> +</html> diff --git a/accessible/tests/mochitest/role/test_aria.html b/accessible/tests/mochitest/role/test_aria.html new file mode 100644 index 0000000000..bd7ffd27fb --- /dev/null +++ b/accessible/tests/mochitest/role/test_aria.html @@ -0,0 +1,729 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test weak ARIA roles</title> + + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + + // To test initial roles on body elements, we need to use an iframe. + function testBodyRole(iframeId, role) { + let iframe = getNode(iframeId); + let doc = iframe.contentDocument; + let docAcc = getAccessible(doc); + testRole(docAcc, role); + } + + async function doTest() { + // ARIA role map. + testRole("aria_alert", ROLE_ALERT); + testRole("aria_alert_mixed", ROLE_ALERT); + testRole("aria_alertdialog", ROLE_DIALOG); + testRole("aria_alertdialog_mixed", ROLE_DIALOG); + testRole("aria_application", ROLE_APPLICATION); + testRole("aria_application_mixed", ROLE_APPLICATION); + testRole("aria_article", ROLE_ARTICLE); + testRole("aria_article_mixed", ROLE_ARTICLE); + testRole("aria_blockquote", ROLE_BLOCKQUOTE); + testRole("aria_blockquote_mixed", ROLE_BLOCKQUOTE); + testRole("aria_button", ROLE_PUSHBUTTON); + testRole("aria_button_mixed", ROLE_PUSHBUTTON); + testRole("aria_caption", ROLE_CAPTION); + testRole("aria_caption_mixed", ROLE_CAPTION); + testRole("aria_checkbox", ROLE_CHECKBUTTON); + testRole("aria_checkbox_mixed", ROLE_CHECKBUTTON); + testRole("aria_code", ROLE_CODE); + testRole("aria_code_mixed", ROLE_CODE); + testRole("aria_columnheader", ROLE_COLUMNHEADER); + testRole("aria_columnheader_mixed", ROLE_COLUMNHEADER); + testRole("aria_combobox", ROLE_EDITCOMBOBOX); + testRole("aria_combobox_mixed", ROLE_EDITCOMBOBOX); + testRole("aria_comment", ROLE_COMMENT); + testRole("aria_comment_mixed", ROLE_COMMENT); + testRole("aria_deletion", ROLE_CONTENT_DELETION); + testRole("aria_deletion_mixed", ROLE_CONTENT_DELETION); + testRole("aria_dialog", ROLE_DIALOG); + testRole("aria_dialog_mixed", ROLE_DIALOG); + testRole("aria_directory", ROLE_LIST); + testRole("aria_directory_mixed", ROLE_LIST); + testRole("aria_document", ROLE_NON_NATIVE_DOCUMENT); + testRole("aria_document_mixed", ROLE_NON_NATIVE_DOCUMENT); + testRole("aria_form", ROLE_FORM); + testRole("aria_form_mixed", ROLE_FORM); + testRole("aria_form_with_label", ROLE_FORM); + testRole("aria_form_with_label_mixed", ROLE_FORM); + testRole("aria_feed", ROLE_GROUPING); + testRole("aria_feed_mixed", ROLE_GROUPING); + testRole("aria_figure", ROLE_FIGURE); + testRole("aria_figure_mixed", ROLE_FIGURE); + testRole("aria_grid", ROLE_TABLE); + testRole("aria_grid_mixed", ROLE_TABLE); + testRole("aria_gridcell", ROLE_GRID_CELL); + testRole("aria_gridcell_mixed", ROLE_GRID_CELL); + testRole("aria_group", ROLE_GROUPING); + testRole("aria_group_mixed", ROLE_GROUPING); + testRole("aria_heading", ROLE_HEADING); + testRole("aria_heading_mixed", ROLE_HEADING); + testRole("aria_img", ROLE_GRAPHIC); + testRole("aria_img_mixed", ROLE_GRAPHIC); + testRole("aria_insertion", ROLE_CONTENT_INSERTION); + testRole("aria_insertion_mixed", ROLE_CONTENT_INSERTION); + testRole("aria_link", ROLE_LINK); + testRole("aria_link_mixed", ROLE_LINK); + testRole("aria_list", ROLE_LIST); + testRole("aria_list_mixed", ROLE_LIST); + testRole("aria_listbox", ROLE_LISTBOX); + testRole("aria_listbox_mixed", ROLE_LISTBOX); + testRole("aria_listitem", ROLE_LISTITEM); + testRole("aria_listitem_mixed", ROLE_LISTITEM); + testRole("aria_log", ROLE_TEXT); // weak role + testRole("aria_log_mixed", ROLE_TEXT); // weak role + testRole("aria_mark", ROLE_MARK); + testRole("aria_mark_mixed", ROLE_MARK); + testRole("aria_marquee", ROLE_ANIMATION); + testRole("aria_marquee_mixed", ROLE_ANIMATION); + testRole("aria_math", ROLE_FLAT_EQUATION); + testRole("aria_math_mixed", ROLE_FLAT_EQUATION); + testRole("aria_menu", ROLE_MENUPOPUP); + testRole("aria_menu_mixed", ROLE_MENUPOPUP); + testRole("aria_menubar", ROLE_MENUBAR); + testRole("aria_menubar_mixed", ROLE_MENUBAR); + testRole("aria_menuitem", ROLE_MENUITEM); + testRole("aria_menuitem_mixed", ROLE_MENUITEM); + testRole("aria_menuitemcheckbox", ROLE_CHECK_MENU_ITEM); + testRole("aria_menuitemcheckbox_mixed", ROLE_CHECK_MENU_ITEM); + testRole("aria_menuitemradio", ROLE_RADIO_MENU_ITEM); + testRole("aria_menuitemradio_mixed", ROLE_RADIO_MENU_ITEM); + testRole("aria_meter", ROLE_METER); + testRole("aria_meter_mixed", ROLE_METER); + testRole("aria_note", ROLE_NOTE); + testRole("aria_note_mixed", ROLE_NOTE); + testRole("aria_paragraph", ROLE_PARAGRAPH); + testRole("aria_paragraph_mixed", ROLE_PARAGRAPH); + testRole("aria_presentation", ROLE_TEXT); // weak role + testRole("aria_presentation_mixed", ROLE_TEXT); // weak role + testRole("aria_progressbar", ROLE_PROGRESSBAR); + testRole("aria_progressbar_mixed", ROLE_PROGRESSBAR); + testRole("aria_radio", ROLE_RADIOBUTTON); + testRole("aria_radio_mixed", ROLE_RADIOBUTTON); + testRole("aria_radiogroup", ROLE_RADIO_GROUP); + testRole("aria_radiogroup_mixed", ROLE_RADIO_GROUP); + testRole("aria_region_no_name", ROLE_TEXT); + testRole("aria_region_no_name_mixed", ROLE_TEXT); + testRole("aria_region_has_label", ROLE_REGION); + testRole("aria_region_has_label_mixed", ROLE_REGION); + testRole("aria_region_has_labelledby", ROLE_REGION); + testRole("aria_region_has_labelledby_mixed", ROLE_REGION); + testRole("aria_region_has_title", ROLE_REGION); + testRole("aria_region_has_title_mixed", ROLE_REGION); + testRole("aria_region_empty_name", ROLE_TEXT); + testRole("aria_region_empty_name_mixed", ROLE_TEXT); + testRole("aria_region_as_table_with_caption", ROLE_REGION); + testRole("aria_region_as_table_with_caption_mixed", ROLE_REGION); + testRole("aria_region_as_table_with_miscaption", ROLE_TABLE); + testRole("aria_region_as_table_with_miscaption_mixed", ROLE_TABLE); + testRole("aria_row", ROLE_ROW); + testRole("aria_row_mixed", ROLE_ROW); + testRole("aria_rowheader", ROLE_ROWHEADER); + testRole("aria_rowheader_mixed", ROLE_ROWHEADER); + testRole("aria_scrollbar", ROLE_SCROLLBAR); + testRole("aria_scrollbar_mixed", ROLE_SCROLLBAR); + testRole("aria_searchbox", ROLE_ENTRY); + testRole("aria_searchbox_mixed", ROLE_ENTRY); + testRole("aria_separator", ROLE_SEPARATOR); + testRole("aria_separator_mixed", ROLE_SEPARATOR); + testRole("aria_slider", ROLE_SLIDER); + testRole("aria_slider_mixed", ROLE_SLIDER); + testRole("aria_spinbutton", ROLE_SPINBUTTON); + testRole("aria_spinbutton_mixed", ROLE_SPINBUTTON); + testRole("aria_status", ROLE_STATUSBAR); + testRole("aria_status_mixed", ROLE_STATUSBAR); + testRole("aria_subscript", ROLE_SUBSCRIPT); + testRole("aria_subscript_mixed", ROLE_SUBSCRIPT); + testRole("aria_suggestion", ROLE_SUGGESTION); + testRole("aria_suggestion_mixed", ROLE_SUGGESTION); + testRole("aria_superscript", ROLE_SUPERSCRIPT); + testRole("aria_superscript_mixed", ROLE_SUPERSCRIPT); + testRole("aria_switch", ROLE_SWITCH); + testRole("aria_switch_mixed", ROLE_SWITCH); + testRole("aria_tab", ROLE_PAGETAB); + testRole("aria_tab_mixed", ROLE_PAGETAB); + testRole("aria_tablist", ROLE_PAGETABLIST); + testRole("aria_tablist_mixed", ROLE_PAGETABLIST); + testRole("aria_tabpanel", ROLE_PROPERTYPAGE); + testRole("aria_tabpanel_mixed", ROLE_PROPERTYPAGE); + testRole("aria_term", ROLE_TERM); + testRole("aria_term_mixed", ROLE_TERM); + testRole("aria_textbox", ROLE_ENTRY); + testRole("aria_textbox_mixed", ROLE_ENTRY); + testRole("aria_timer", ROLE_TEXT); // weak role + testRole("aria_timer_mixed", ROLE_TEXT); // weak role + testRole("aria_toolbar", ROLE_TOOLBAR); + testRole("aria_toolbar_mixed", ROLE_TOOLBAR); + testRole("aria_tooltip", ROLE_TOOLTIP); + testRole("aria_tooltip_mixed", ROLE_TOOLTIP); + testRole("aria_tree", ROLE_OUTLINE); + testRole("aria_tree_mixed", ROLE_OUTLINE); + testRole("aria_treegrid", ROLE_TREE_TABLE); + testRole("aria_treegrid_mixed", ROLE_TREE_TABLE); + testRole("aria_treeitem", ROLE_OUTLINEITEM); + testRole("aria_treeitem_mixed", ROLE_OUTLINEITEM); + + // Note: + // The phrase "weak foo" here means that there is no good foo-to-platform + // role mapping. Similarly "strong foo" means there is a good foo-to- + // platform role mapping. + + testRole("articlemain", ROLE_LANDMARK); + testRole("articlemain_mixed", ROLE_LANDMARK); + testRole("articleform", ROLE_FORM); + testRole("articleform_mixed", ROLE_FORM); + + // Test article exposed as article + testRole("testArticle", ROLE_ARTICLE); + testRole("testArticle_mixed", ROLE_ARTICLE); + + // weak roles that are forms of "live regions" + testRole("log_table", ROLE_TABLE); + testRole("log_table_mixed", ROLE_TABLE); + testRole("timer_div", ROLE_SECTION); + testRole("timer_div_mixed", ROLE_SECTION); + + // other roles that are forms of "live regions" + testRole("marquee_h1", ROLE_ANIMATION); + testRole("marquee_h1_mixed", ROLE_ANIMATION); + + // strong landmark + testRole("application", ROLE_APPLICATION); + testRole("application_mixed", ROLE_APPLICATION); + testRole("form", ROLE_FORM); + testRole("form_mixed", ROLE_FORM); + testRole("application_table", ROLE_APPLICATION); + testRole("application_table_mixed", ROLE_APPLICATION); + + // landmarks + let landmarks = ["banner", "complementary", "contentinfo", + "main", "navigation", "search"]; + for (const l in landmarks) { + testRole(landmarks[l], ROLE_LANDMARK); + testRole(landmarks[l] + "_mixed", ROLE_LANDMARK); + } + + for (const l in landmarks) { + let id = landmarks[l] + "_table"; + testRole(id, ROLE_LANDMARK); + testRole(id + "_mixed", ROLE_LANDMARK); + + let accessibleTable = getAccessible(id, [nsIAccessibleTable], null, + DONOTFAIL_IF_NO_INTERFACE); + ok(!!accessibleTable, "landmarked table should have nsIAccessibleTable"); + + accessibleTable = getAccessible(id+"_mixed", [nsIAccessibleTable], null, + DONOTFAIL_IF_NO_INTERFACE); + ok(!!accessibleTable, "Uppercase landmarked table should have nsIAccessibleTable"); + + if (accessibleTable) + is(accessibleTable.getCellAt(0, 0).firstChild.name, "hi", "no cell"); + } + + // //////////////////////////////////////////////////////////////////////// + // test gEmptyRoleMap + testRole("buttontable_row", ROLE_TEXT_CONTAINER); + testRole("buttontable_row_mixed", ROLE_TEXT_CONTAINER); + testRole("buttontable_cell", ROLE_TEXT_CONTAINER); + testRole("buttontable_cell_mixed", ROLE_TEXT_CONTAINER); + + // abstract roles + var abstract_roles = ["composite", "landmark", "structure", "widget", + "window", "input", "range", "select", "section", + "sectionhead"]; + for (const a in abstract_roles) { + testRole(abstract_roles[a], ROLE_SECTION); + testRole(abstract_roles[a]+ "_mixed", ROLE_SECTION); + } + + // //////////////////////////////////////////////////////////////////////// + // roles transformed by ARIA state attributes + testRole("togglebutton", ROLE_TOGGLE_BUTTON); + testRole("togglebutton_mixed", ROLE_TOGGLE_BUTTON); + testRole("implicit_gridcell", ROLE_GRID_CELL); + testRole("implicit_gridcell_mixed", ROLE_GRID_CELL); + + // //////////////////////////////////////////////////////////////////////// + // ignore unknown roles, take first known + testRole("unknown_roles", ROLE_PUSHBUTTON); + testRole("unknown_roles_mixed", ROLE_PUSHBUTTON); + + // //////////////////////////////////////////////////////////////////////// + // misc roles + testRole("note", ROLE_NOTE); + testRole("note_mixed", ROLE_NOTE); + testRole("scrollbar", ROLE_SCROLLBAR); + testRole("scrollbar_mixed", ROLE_SCROLLBAR); + testRole("dir", ROLE_LIST); + testRole("dir_mixed", ROLE_LIST); + + // //////////////////////////////////////////////////////////////////////// + // test document role map update + var testDoc = getAccessible(document, [nsIAccessibleDocument]); + testRole(testDoc, ROLE_DOCUMENT); + document.body.setAttribute("role", "application"); + testRole(testDoc, ROLE_APPLICATION); + document.body.setAttribute("role", "APPLICATION"); + testRole(testDoc, ROLE_APPLICATION); + document.body.setAttribute("role", "dialog"); + testRole(testDoc, ROLE_DIALOG); + document.body.setAttribute("role", "DIALOG"); + testRole(testDoc, ROLE_DIALOG); + // Other roles aren't valid on body elements. + document.body.setAttribute("role", "document"); + testRole(testDoc, ROLE_DOCUMENT); + document.body.setAttribute("role", "DOCUMENT"); + testRole(testDoc, ROLE_DOCUMENT); + document.body.setAttribute("role", "button"); + testRole(testDoc, ROLE_DOCUMENT); + document.body.setAttribute("role", "BUTTON"); + testRole(testDoc, ROLE_DOCUMENT); + document.body.setAttribute("role", "main"); + testRole(testDoc, ROLE_DOCUMENT); + document.body.setAttribute("role", "MAIN"); + testRole(testDoc, ROLE_DOCUMENT); + + // Test equation image + testRole("img_eq", ROLE_FLAT_EQUATION); + testRole("img_eq_mixed", ROLE_FLAT_EQUATION); + + // Test textual equation + testRole("txt_eq", ROLE_FLAT_EQUATION); + testRole("txt_eq_mixed", ROLE_FLAT_EQUATION); + + // Test initial ARIA roles on the body element. + testBodyRole("iframe_aria_application", ROLE_APPLICATION); + testBodyRole("iframe_aria_application_mixed", ROLE_APPLICATION); + testBodyRole("iframe_aria_dialog", ROLE_DIALOG); + testBodyRole("iframe_aria_dialog_mixed", ROLE_DIALOG); + // role="alert" is valid on the body of chrome documents. + let win = Services.ww.openWindow( + null, + "chrome://mochitests/content/a11y/accessible/tests/mochitest/role/chrome_body_role_alert.xhtml", + "_blank", + "chrome", + [] + ); + await new Promise(resolve => addA11yLoadEvent(resolve, win)); + testRole(win.document, ROLE_ALERT); + win.close(); + // Other roles aren't valid on body elements. + testBodyRole("iframe_aria_document", ROLE_DOCUMENT); + testBodyRole("iframe_aria_document_mixed", ROLE_DOCUMENT); + testBodyRole("iframe_aria_button", ROLE_DOCUMENT); + testBodyRole("iframe_aria_button_mixed", ROLE_DOCUMENT); + testBodyRole("iframe_aria_main", ROLE_DOCUMENT); + testBodyRole("iframe_aria_main_mixed", ROLE_DOCUMENT); + // role="alert" is not valid on the body of content documents. + testBodyRole("iframe_aria_alert", ROLE_DOCUMENT); + testBodyRole("iframe_aria_alert_mixed", ROLE_DOCUMENT); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=428479">Mozilla Bug 428479</a> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=429666">Mozilla Bug 429666</a> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=481114">Mozilla Bug 481114</a> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469688">Mozilla Bug 469688</a> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469688">Mozilla Bug 520188</a> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=529289">Mozilla Bug 529289</a> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=529289">Mozilla Bug 607219</a> + <a target="_blank" + title="HTML buttons with aria-pressed not exposing IA2 TOGGLE_BUTTON role" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=725432"> + Bug 725432 + </a> + <a target="_blank" + title="Map ARIA role FORM" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=735645"> + Bug 735645 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563" + title="Support ARIA 1.1 switch role"> + Bug 1136563 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518" + title="Support ARIA 1.1 searchbox role"> + Bug 1121518 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1356049" + title="Map ARIA figure role"> + Bug 1356049 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <span id="aria_alert" role="alert"></span> + <span id="aria_alert_mixed" role="aLERt"></span> + <span id="aria_alertdialog" role="alertdialog"></span> + <span id="aria_alertdialog_mixed" role="aLERTDIALOg"></span> + <span id="aria_application" role="application"></span> + <span id="aria_application_mixed" role="aPPLICATIOn"></span> + <span id="aria_article" role="article"></span> + <span id="aria_article_mixed" role="aRTICLe"></span> + <span id="aria_blockquote" role="blockquote"></span> + <span id="aria_blockquote_mixed" role="bLOCKQUOTe"></span> + <span id="aria_button" role="button"></span> + <span id="aria_button_mixed" role="bUTTOn"></span> + <span id="aria_caption" role="caption"></span> + <span id="aria_caption_mixed" role="cAPTIOn"></span> + <span id="aria_checkbox" role="checkbox"></span> + <span id="aria_checkbox_mixed" role="cHECKBOx"></span> + <span id="aria_code" role="code"></span> + <span id="aria_code_mixed" role="cODe"></span> + <span id="aria_columnheader" role="columnheader"></span> + <span id="aria_columnheader_mixed" role="cOLUMNHEADEr"></span> + <span id="aria_combobox" role="combobox"></span> + <span id="aria_combobox_mixed" role="cOMBOBOx"></span> + <span id="aria_comment" role="comment"></span> + <span id="aria_comment_mixed" role="cOMMENt"></span> + <span id="aria_deletion" role="deletion"></span> + <span id="aria_deletion_mixed" role="dELETIOn"></span> + <span id="aria_dialog" role="dialog"></span> + <span id="aria_dialog_mixed" role="dIALOg"></span> + <span id="aria_directory" role="directory"></span> + <span id="aria_directory_mixed" role="dIRECTORy"></span> + <span id="aria_document" role="document"></span> + <span id="aria_document_mixed" role="dOCUMENt"></span> + <span id="aria_form" role="form"></span> + <span id="aria_form_mixed" role="fORm"></span> + <span id="aria_form_with_label" role="form" aria-label="Label"></span> + <span id="aria_form_with_label_mixed" role="fORm" aria-label="Label"></span> + <span id="aria_feed" role="feed"></span> + <span id="aria_feed_mixed" role="fEEd"></span> + <span id="aria_figure" role="figure"></span> + <span id="aria_figure_mixed" role="fIGURe"></span> + <span id="aria_grid" role="grid"></span> + <span id="aria_grid_mixed" role="gRId"></span> + <span id="aria_gridcell" role="gridcell"></span> + <span id="aria_gridcell_mixed" role="gRIDCELl"></span> + <span id="aria_group" role="group"></span> + <span id="aria_group_mixed" role="gROUp"></span> + <span id="aria_heading" role="heading"></span> + <span id="aria_heading_mixed" role="hEADINg"></span> + <span id="aria_img" role="img"></span> + <span id="aria_img_mixed" role="iMg"></span> + <span id="aria_insertion" role="insertion"></span> + <span id="aria_insertion_mixed" role="iNSERTIOn"></span> + <span id="aria_link" role="link"></span> + <span id="aria_link_mixed" role="lINk"></span> + <span id="aria_list" role="list"></span> + <span id="aria_list_mixed" role="lISt"></span> + <span id="aria_listbox" role="listbox"></span> + <span id="aria_listbox_mixed" role="lISTBOx"></span> + <span id="aria_listitem" role="listitem"></span> + <span id="aria_listitem_mixed" role="lISTITEm"></span> + <span id="aria_log" role="log"></span> + <span id="aria_log_mixed" role="lOg"></span> + <span id="aria_mark" role="mark"></span> + <span id="aria_mark_mixed" role="mARk"></span> + <span id="aria_marquee" role="marquee"></span> + <span id="aria_marquee_mixed" role="mARQUEe"></span> + <span id="aria_math" role="math"></span> + <span id="aria_math_mixed" role="mATh"></span> + <span id="aria_menu" role="menu"></span> + <span id="aria_menu_mixed" role="mENu"></span> + <span id="aria_menubar" role="menubar"></span> + <span id="aria_menubar_mixed" role="mENUBAr"></span> + <span id="aria_menuitem" role="menuitem"></span> + <span id="aria_menuitem_mixed" role="mENUITEm"></span> + <span id="aria_menuitemcheckbox" role="menuitemcheckbox"></span> + <span id="aria_menuitemcheckbox_mixed" role="mENUITEMCHECKBOx"></span> + <span id="aria_menuitemradio" role="menuitemradio"></span> + <span id="aria_menuitemradio_mixed" role="mENUITEMRADIo"></span> + <span id="aria_meter" role="meter"></span> + <span id="aria_meter_mixed" role="meTer"></span> + <span id="aria_note" role="note"></span> + <span id="aria_note_mixed" role="nOTe"></span> + <span id="aria_paragraph" role="paragraph"></span> + <span id="aria_paragraph_mixed" role="pARAGRAPh"></span> + <span id="aria_presentation" role="presentation" tabindex="0"></span> + <span id="aria_presentation_mixed" role="pRESENTATIOn" tabindex="0"></span> + <span id="aria_progressbar" role="progressbar"></span> + <span id="aria_progressbar_mixed" role="pROGRESSBAr"></span> + <span id="aria_radio" role="radio"></span> + <span id="aria_radio_mixed" role="rADIo"></span> + <span id="aria_radiogroup" role="radiogroup"></span> + <span id="aria_radiogroup_mixed" role="rADIOGROUp"></span> + <span id="aria_region_no_name" role="region"></span> + <span id="aria_region_no_name_mixed" role="rEGIOn"></span> + <span id="aria_region_has_label" role="region" aria-label="label"></span> + <span id="aria_region_has_label_mixed" role="rEGIOn" aria-label="label"></span> + <span id="aria_region_has_labelledby" role="region" aria-labelledby="label"><span id="label" aria-label="label"></span> + <span id="aria_region_has_labelledby_mixed" role="rEGIOn" aria-labelledby="label"><span id="label" aria-label="label"></span> + <span id="aria_region_has_title" role="region" title="title"></span> + <span id="aria_region_has_title_mixed" role="rEGIOn" title="title"></span> + <span id="aria_region_empty_name" role="region" aria-label="" title="" aria-labelledby="empty"></span><span id="empty"></span> + <span id="aria_region_empty_name_mixed" role="rEGIOn" aria-label="" title="" aria-labelledby="empty"></span><span id="empty"></span> + <table id="aria_region_as_table_with_caption" role="region"><caption>hello</caption></table> + <table id="aria_region_as_table_with_caption_mixed" role="rEGIOn"><caption>hello</caption></table> + <table id="aria_region_as_table_with_miscaption" role="region"><caption role="option">hello</caption></table> + <table id="aria_region_as_table_with_miscaption_mixed" role="rEGIOn"><caption role="option">hello</caption></table> + <span id="aria_row" role="row"></span> + <span id="aria_row_mixed" role="rOw"></span> + <span id="aria_rowheader" role="rowheader"></span> + <span id="aria_rowheader_mixed" role="rOWHEADEr"></span> + <span id="aria_scrollbar" role="scrollbar"></span> + <span id="aria_scrollbar_mixed" role="sCROLLBAr"></span> + <span id="aria_searchbox" role="textbox"></span> + <span id="aria_searchbox_mixed" role="tEXTBOx"></span> + <span id="aria_separator" role="separator"></span> + <span id="aria_separator_mixed" role="sEPARATOr"></span> + <span id="aria_slider" role="slider"></span> + <span id="aria_slider_mixed" role="sLIDEr"></span> + <span id="aria_spinbutton" role="spinbutton"></span> + <span id="aria_spinbutton_mixed" role="sPINBUTTOn"></span> + <span id="aria_status" role="status"></span> + <span id="aria_status_mixed" role="sTATUs"></span> + <span id="aria_subscript" role="subscript"></span> + <span id="aria_subscript_mixed" role="sUBSCRIPt"></span> + <span id="aria_suggestion" role="suggestion"></span> + <span id="aria_suggestion_mixed" role="sUGGESTIOn"></span> + <span id="aria_superscript" role="superscript"></span> + <span id="aria_superscript_mixed" role="sUPERSCRIPt"></span> + <span id="aria_switch" role="switch"></span> + <span id="aria_switch_mixed" role="sWITCh"></span> + <span id="aria_tab" role="tab"></span> + <span id="aria_tab_mixed" role="tAb"></span> + <span id="aria_tablist" role="tablist"></span> + <span id="aria_tablist_mixed" role="tABLISt"></span> + <span id="aria_tabpanel" role="tabpanel"></span> + <span id="aria_tabpanel_mixed" role="tABPANEl"></span> + <span id="aria_term" role="term"></span> + <span id="aria_term_mixed" role="tERm"></span> + <span id="aria_textbox" role="textbox"></span> + <span id="aria_textbox_mixed" role="tEXTBOx"></span> + <span id="aria_timer" role="timer"></span> + <span id="aria_timer_mixed" role="tIMEr"></span> + <span id="aria_toolbar" role="toolbar"></span> + <span id="aria_toolbar_mixed" role="tOOLBAr"></span> + <span id="aria_tooltip" role="tooltip"></span> + <span id="aria_tooltip_mixed" role="tOOLTIp"></span> + <span id="aria_tree" role="tree"></span> + <span id="aria_tree_mixed" role="tREe"></span> + <span id="aria_treegrid" role="treegrid"></span> + <span id="aria_treegrid_mixed" role="tREEGRId"></span> + <span id="aria_treeitem" role="treeitem"></span> + <span id="aria_treeitem_mixed" role="tREEITEm"></span> + + <article id="articlemain" role="main">a main area</article> + <article id="articlemain_mixed" role="mAIn">a main area</article> + <article id="articleform" role="form">a form area</article> + <article id="articleform_mixed" role="fORm">a form area</article> + + <div id="testArticle" role="article" title="Test article"> + <p>This is a paragraph inside the article.</p> + </div> + + <div id="testArticle_mixed" role="aRTICLe" title="Test article"> + <p>This is a paragraph inside the article.</p> + </div> + + <!-- "live" roles --> + <table role="log" id="log_table"> + <tr><td>Table based log</td></tr> + </table> + <table role="LOG" id="log_table_mixed"> + <tr><td>Table based log</td></tr> + </table> + <h1 role="marquee" id="marquee_h1">marquee</h1> + <h1 role="MARQUEE" id="marquee_h1_mixed">marquee</h1> + <div role="timer" id="timer_div">timer</div> + <div role="TIMER" id="timer_div_mixed">timer</div> + + <!-- landmarks --> + <div role="application" id="application">application</div> + <div role="aPPLICATIOn" id="application_mixed">application</div> + <div role="form" id="form">form</div> + <div role="fORm" id="form_mixed">form</div> + + <!-- weak landmarks --> + <div role="banner" id="banner">banner</div> + <div role="bANNEr" id="banner_mixed">banner</div> + <div role="complementary" id="complementary">complementary</div> + <div role="cOMPLEMENTARy" id="complementary_mixed">complementary</div> + <div role="contentinfo" id="contentinfo">contentinfo</div> + <div role="cONTENTINFo" id="contentinfo_mixed">contentinfo</div> + <div role="main" id="main">main</div> + <div role="mAIN" id="main_mixed">main</div> + <div role="navigation" id="navigation">navigation</div> + <div role="nAVIGATIOn" id="navigation_mixed">navigation</div> + <div role="search" id="search">search</div> + <div role="sEARCh" id="search_mixed">search</div> + + <!-- landmarks are tables --> + <table role="application" id="application_table">application table + <tr><td>hi<td></tr></table> + <table role="aPPLICATIOn" id="application_table_mixed">application table + <tr><td>hi<td></tr></table> + <table role="banner" id="banner_table">banner table + <tr><td>hi<td></tr></table> + <table role="bANNEr" id="banner_table_mixed">banner table + <tr><td>hi<td></tr></table> + <table role="complementary" id="complementary_table">complementary table + <tr><td>hi<td></tr></table> + <table role="cOMPLEMENTARy" id="complementary_table_mixed">complementary table + <tr><td>hi<td></tr></table> + <table role="contentinfo" id="contentinfo_table">contentinfo table + <tr><td>hi<td></tr></table> + <table role="cONTENTINFo" id="contentinfo_table_mixed">contentinfo table + <tr><td>hi<td></tr></table> + <table role="main" id="main_table">main table + <tr><td>hi<td></tr></table> + <table role="mAIn" id="main_table_mixed">main table + <tr><td>hi<td></tr></table> + <table role="navigation" id="navigation_table">navigation table + <tr><td>hi<td></tr></table> + <table role="nAVIGATIOn" id="navigation_table_mixed">navigation table + <tr><td>hi<td></tr></table> + <table role="search" id="search_table">search table + <tr><td>hi<td></tr></table> + <table role="sEARCh" id="search_table_mixed">search table + <tr><td>hi<td></tr></table> + + <!-- test gEmptyRoleMap --> + <table role="button"> + <tr id="buttontable_row"> + <td id="buttontable_cell">cell</td> + </tr> + </table> + <table role="bUTTOn"> + <tr id="buttontable_row_mixed"> + <td id="buttontable_cell_mixed">cell</td> + </tr> + </table> + + <!-- user agents must not map abstract roles to platform API --> + <!-- test abstract base type roles --> + <div role="composite" id="composite">composite</div> + <div role="cOMPOSITe" id="composite_mixed">composite</div> + <div role="landmark" id="landmark">landmark</div> + <div role="lANDMARk" id="landmark_mixed">landmark</div> + <div role="roletype" id="roletype">roletype</div> + <div role="rOLETYPe" id="roletype_mixed">roletype</div> + <div role="structure" id="structure">structure</div> + <div role="sTRUCTURe" id="structure_mixed">structure</div> + <div role="widget" id="widget">widget</div> + <div role="wIDGEt" id="widget_mixed">widget</div> + <div role="window" id="window">window</div> + <div role="wINDOw" id="window_mixed">window</div> + <!-- test abstract input roles --> + <div role="input" id="input">input</div> + <div role="iNPUt" id="input_mixed">input</div> + <div role="range" id="range">range</div> + <div role="rANGe" id="range_mixed">range</div> + <div role="select" id="select">select</div> + <div role="sELECt" id="select_mixed">select</div> + <!-- test abstract structure roles --> + <div role="section" id="section">section</div> + <div role="sECTIOn" id="section_mixed">section</div> + <div role="sectionhead" id="sectionhead">sectionhead</div> + <div role="sECTIONHEAd" id="sectionhead_mixed">sectionhead</div> + + <!-- roles transformed by ARIA roles of ancestors --> + <table role="grid"> + <tr> + <td id="implicit_gridcell">foo</td> + </tr> + </table> + <table role="gRId"> + <tr> + <td id="implicit_gridcell_mixed">foo</td> + </tr> + </table> + + <!-- roles transformed by ARIA state attributes --> + <button aria-pressed="true" id="togglebutton"></button> + <button aria-pressed="tRUe" id="togglebutton_mixed"></button> + + <!-- take the first known mappable role --> + <div role="wiggly:worm abc123 button" id="unknown_roles">worm button</div> + <div role="wiggly:worm abc123 bUTTOn" id="unknown_roles_mixed">worm button</div> + + <!-- misc roles --> + <div role="note" id="note">note</div> + <div role="nOTe" id="note_mixed">note</div> + <div role="scrollbar" id="scrollbar">scrollbar</div> + <div role="sCROLLBAr" id="scrollbar_mixed">scrollbar</div> + + <div id="dir" role="directory"> + <div role="listitem">A</div> + <div role="listitem">B</div> + <div role="listitem">C</div> + </div> + + <div id="dir_mixed" role="dIRECTORy"> + <div role="listitem">A</div> + <div role="listitem">B</div> + <div role="listitem">C</div> + </div> + + <p>Image: + <img id="img_eq" role="math" src="foo" alt="x^2 + y^2 + z^2"> + </p> + + <p>Image: + <img id="img_eq_mixed" role="mATh" src="foo" alt="x^2 + y^2 + z^2"> + </p> + + <p>Text: + <span id="txt_eq" role="math" title="x^2 + y^2 + z^2">x<sup>2</sup> + + y<sup>2</sup> + z<sup>2</sup></span> + </p> + <p>Text: + <span id="txt_eq_mixed" role="mATh" title="x^2 + y^2 + z^2">x<sup>2</sup> + + y<sup>2</sup> + z<sup>2</sup></span> + </p> + + <iframe id="iframe_aria_application" + src="data:text/html,<body role='application'>"></iframe> + <iframe id="iframe_aria_application_mixed" + src="data:text/html,<body role='aPPLICATIOn'>"></iframe> + <iframe id="iframe_aria_dialog" + src="data:text/html,<body role='dialog'>"></iframe> + <iframe id="iframe_aria_dialog_mixed" + src="data:text/html,<body role='dIALOg'>"></iframe> + <iframe id="iframe_aria_document" + src="data:text/html,<body role='document'>"></iframe> + <iframe id="iframe_aria_document_mixed" + src="data:text/html,<body role='dOCUMENt'>"></iframe> + <iframe id="iframe_aria_button" + src="data:text/html,<body role='button'>"></iframe> + <iframe id="iframe_aria_button_mixed" + src="data:text/html,<body role='bUTTOn'>"></iframe> + <iframe id="iframe_aria_main" + src="data:text/html,<body role='main'>"></iframe> + <iframe id="iframe_aria_main_mixed" + src="data:text/html,<body role='mAIn'>"></iframe> + <iframe id="iframe_aria_alert" + src="data:text/html,<body role='alert'>"></iframe> + <iframe id="iframe_aria_alert_mixed" + src="data:text/html,<body role='aLERt'>"></iframe> + +</body> +</html> diff --git a/accessible/tests/mochitest/role/test_aria.xhtml b/accessible/tests/mochitest/role/test_aria.xhtml new file mode 100644 index 0000000000..9aea4ec222 --- /dev/null +++ b/accessible/tests/mochitest/role/test_aria.xhtml @@ -0,0 +1,65 @@ +<?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="Accessibility Name Calculating Test."> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js" /> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + ok(!isAccessible("presentation_label"), + "Presentation label shouldn't be accessible."); + ok(!isAccessible("presentation_descr"), + "Presentation description shouldn't be accessible."); + + // aria-pressed + testRole("pressed_button", ROLE_TOGGLE_BUTTON); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=494345" + title="Do not create accessibles for XUL label or description having a role of 'presentation'"> + Mozilla Bug 494345 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1033283" + title="Expose pressed state on XUL menu toggle buttons"> + Mozilla Bug 1033283 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <label id="presentation_label" role="presentation" value="label"/> + <description id="presentation_descr" role="presentation" value="description"/> + <button id="pressed_button" aria-pressed="true" label="I am pressed" /> + </vbox> + + + </hbox> +</window> + diff --git a/accessible/tests/mochitest/role/test_dpub_aria.html b/accessible/tests/mochitest/role/test_dpub_aria.html new file mode 100644 index 0000000000..621c86a59b --- /dev/null +++ b/accessible/tests/mochitest/role/test_dpub_aria.html @@ -0,0 +1,114 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test DPub ARIA roles</title> + + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + + function doTest() { + // DPub ARIA role map. + testRole("doc-abstract", ROLE_SECTION); + testRole("doc-acknowledgments", ROLE_LANDMARK); + testRole("doc-afterword", ROLE_LANDMARK); + testRole("doc-appendix", ROLE_LANDMARK); + testRole("doc-backlink", ROLE_LINK); + testRole("doc-biblioentry", ROLE_LISTITEM); + testRole("doc-bibliography", ROLE_LANDMARK); + testRole("doc-biblioref", ROLE_LINK); + testRole("doc-chapter", ROLE_LANDMARK); + testRole("doc-colophon", ROLE_SECTION); + testRole("doc-conclusion", ROLE_LANDMARK); + testRole("doc-cover", ROLE_GRAPHIC); + testRole("doc-credit", ROLE_SECTION); + testRole("doc-credits", ROLE_LANDMARK); + testRole("doc-dedication", ROLE_SECTION); + testRole("doc-endnote", ROLE_LISTITEM); + testRole("doc-endnotes", ROLE_LANDMARK); + testRole("doc-epigraph", ROLE_SECTION); + testRole("doc-epilogue", ROLE_LANDMARK); + testRole("doc-errata", ROLE_LANDMARK); + testRole("doc-example", ROLE_SECTION); + testRole("doc-footnote", ROLE_FOOTNOTE); + testRole("doc-foreword", ROLE_LANDMARK); + testRole("doc-glossary", ROLE_LANDMARK); + testRole("doc-glossref", ROLE_LINK); + testRole("doc-index", ROLE_NAVIGATION); + testRole("doc-introduction", ROLE_LANDMARK); + testRole("doc-noteref", ROLE_LINK); + testRole("doc-notice", ROLE_NOTE); + testRole("doc-pagebreak", ROLE_SEPARATOR); + testRole("doc-pagelist", ROLE_NAVIGATION); + testRole("doc-part", ROLE_LANDMARK); + testRole("doc-preface", ROLE_LANDMARK); + testRole("doc-prologue", ROLE_LANDMARK); + testRole("doc-pullquote", ROLE_SECTION); + testRole("doc-qna", ROLE_SECTION); + testRole("doc-subtitle", ROLE_HEADING); + testRole("doc-tip", ROLE_NOTE); + testRole("doc-toc", ROLE_NAVIGATION); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1343537" + title="implement ARIA DPUB extension"> + Bug 1343537 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + <div id="doc-abstract" role="doc-abstract">abstract</div> + <div id="doc-acknowledgments" role="doc-acknowledgments">acknowledgments</div> + <div id="doc-afterword" role="doc-afterword">afterword</div> + <div id="doc-appendix" role="doc-appendix">appendix</div> + <div id="doc-backlink" role="doc-backlink">backlink</div> + <div id="doc-biblioentry" role="doc-biblioentry">biblioentry</div> + <div id="doc-bibliography" role="doc-bibliography">bibliography</div> + <div id="doc-biblioref" role="doc-biblioref">biblioref</div> + <div id="doc-chapter" role="doc-chapter">chapter</div> + <div id="doc-colophon" role="doc-colophon">colophon</div> + <div id="doc-conclusion" role="doc-conclusion">conclusion</div> + <div id="doc-cover" role="doc-cover">cover</div> + <div id="doc-credit" role="doc-credit">credit</div> + <div id="doc-credits" role="doc-credits">credits</div> + <div id="doc-dedication" role="doc-dedication">dedication</div> + <div id="doc-endnote" role="doc-endnote">endnote</div> + <div id="doc-endnotes" role="doc-endnotes">endnotes</div> + <div id="doc-epigraph" role="doc-epigraph">epigraph</div> + <div id="doc-epilogue" role="doc-epilogue">epilogue</div> + <div id="doc-errata" role="doc-errata">errata</div> + <div id="doc-example" role="doc-example">example</div> + <div id="doc-footnote" role="doc-footnote">footnote</div> + <div id="doc-foreword" role="doc-foreword">foreword</div> + <div id="doc-glossary" role="doc-glossary">glossary</div> + <div id="doc-glossref" role="doc-glossref">glossref</div> + <div id="doc-index" role="doc-index">index</div> + <div id="doc-introduction" role="doc-introduction">introduction</div> + <div id="doc-noteref" role="doc-noteref">noteref</div> + <div id="doc-notice" role="doc-notice">notice</div> + <div id="doc-pagebreak" role="doc-pagebreak">pagebreak</div> + <div id="doc-pagelist" role="doc-pagelist">pagelist</div> + <div id="doc-part" role="doc-part">part</div> + <div id="doc-preface" role="doc-preface">preface</div> + <div id="doc-prologue" role="doc-prologue">prologue</div> + <div id="doc-pullquote" role="doc-pullquote">pullquote</div> + <div id="doc-qna" role="doc-qna">qna</div> + <div id="doc-subtitle" role="doc-subtitle">subtitle</div> + <div id="doc-tip" role="doc-tip">tip</div> + <div id="doc-toc" role="doc-toc">toc</div> +</body> +</html> diff --git a/accessible/tests/mochitest/role/test_general.html b/accessible/tests/mochitest/role/test_general.html new file mode 100644 index 0000000000..282690cf98 --- /dev/null +++ b/accessible/tests/mochitest/role/test_general.html @@ -0,0 +1,201 @@ +<!DOCTYPE html> +<html> +<head> + <title>test nsHyperTextAccessible accesible objects creation and their roles</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + function doTests() { + // landmark tests section + testRole("frm", ROLE_FORM); + + // nsHyperTextAcc tests section + // Test html:form. + testRole("nav", ROLE_LANDMARK); + testRole("header", ROLE_LANDMARK); + testRole("footer", ROLE_LANDMARK); + testRole("article", ROLE_ARTICLE); + testRole("aside", ROLE_LANDMARK); + testRole("section", ROLE_SECTION); + + // Bug 996821 + // Check that landmark elements get accessibles with styled overflow. + testRole("section_overflow", ROLE_SECTION); + testRole("nav_overflow", ROLE_LANDMARK); + testRole("header_overflow", ROLE_SECTION); + testRole("aside_overflow", ROLE_LANDMARK); + testRole("footer_overflow", ROLE_SECTION); + testRole("article_overflow", ROLE_ARTICLE); + + // test html:div + testRole("sec", ROLE_SECTION); + + // Test html:blockquote + testRole("quote", ROLE_BLOCKQUOTE); + + // Test html:h, all levels + testRole("head1", ROLE_HEADING); + testRole("head2", ROLE_HEADING); + testRole("head3", ROLE_HEADING); + testRole("head4", ROLE_HEADING); + testRole("head5", ROLE_HEADING); + testRole("head6", ROLE_HEADING); + + // Test that an html:input @type="file" is exposed as ROLE_GROUPING. + testRole("data", ROLE_GROUPING); + + // Test that input type="checkbox" and type="radio" are + // exposed as such regardless of appearance style. + testRole("checkbox_regular", ROLE_CHECKBUTTON); + testRole("checkbox_appearance_none", ROLE_CHECKBUTTON); + testRole("radio_regular", ROLE_RADIOBUTTON); + testRole("radio_appearance_none", ROLE_RADIOBUTTON); + + // Test regular paragraph by comparison to make sure exposure does not + // get broken. + testRole("p", ROLE_PARAGRAPH); + + // Test dl, dt, dd + testRole("definitionlist", ROLE_DEFINITION_LIST); + testRole("definitionterm", ROLE_TERM); + testRole("definitiondescription", ROLE_DEFINITION); + + // Has click, mousedown or mouseup listeners. + testRole("span1", ROLE_TEXT); + testRole("span2", ROLE_TEXT); + testRole("span3", ROLE_TEXT); + + // Test role of listbox inside combobox + testRole("listbox1", ROLE_COMBOBOX_LIST); + testRole("listbox2", ROLE_COMBOBOX_LIST); + + // Test role of menu and li items in menu + testRole("menu", ROLE_LIST); + testRole("menuItem", ROLE_LISTITEM); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> +<body> + + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=472326" + title="html:input of type "file" no longer rendered to screen readers"> + Mozilla Bug 472326 + </a><br> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=474261" + title="Test remaining implementations in nsHyperTextAccessible::GetRole"> + bug 474261 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409" + title="Expose click action if mouseup and mousedown are registered"> + Mozilla Bug 423409 + </a> + <a target="_blank" + title="Provide mappings for html5 <nav> <header> <footer> <article>" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=593368"> + Bug 593368 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502" + title="Map <article> like we do aria role article"> + Bug 613502 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=610650" + title="Change implementation of HTML5 landmark elements to conform"> + Bug 610650 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=614310" + title="Map section to pane (like role=region)"> + Mozilla Bug 614310 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=734982" + title="Map ARIA role FORM"> + Bug 734982 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1044431" + title="Listbox owned by combobox has the wrong role"> + Mozilla Bug 1044431 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <form id="frm" action="submit.php" method="post"> + <label for="data">File</label>: + <input type="file" id="data" name="data" size="50"/> + <input type="checkbox" id="checkbox_regular" value="Check me"/> + <input type="checkbox" style="-moz-appearance: none;" id="checkbox_appearance_none" value="Check me"/> + <input type="radio" id="radio_regular" value="Check me"/> + <input type="radio" style="-moz-appearance: none;" id="radio_appearance_none" value="Check me"/> + </form> + + <nav id="nav">a nav</nav> + <header id="header">a header</header> + <footer id="footer">a footer</footer> + <article id="article">an article</article> + <aside id="aside">by the way I am an aside</aside> + <section id="section">a section</section> + + <section style="overflow: hidden;" id="section_overflow"> + <nav style="overflow: hidden;" + id="nav_overflow">overflow nav</nav> + <header style="overflow: hidden;" + id="header_overflow">overflow header</header> + <aside style="overflow: hidden;" + id="aside_overflow">overflow aside</aside> + <footer style="overflow: hidden;" + id="footer_overflow">overflow footer</footer> + </section> + <article style="overflow: hidden;" + id="article_overflow">overflow article</article> + + <p id="p">A paragraph for comparison.</p> + <div id="sec">A normal div</div> + <blockquote id="quote">A citation</blockquote> + <h1 id="head1">A heading level 1</h1> + <h2 id="head2">A heading level 2</h2> + <h3 id="head3">A heading level 3</h3> + <h4 id="head4">A heading level 4</h4> + <h5 id="head5">A heading level 5</h5> + <h6 id="head6">A heading level 6</h6> + + <dl id="definitionlist"> + <dt id="definitionterm">gecko</dt> + <dd id="definitiondescription">geckos have sticky toes</dd> + </dl> + + <span id="span1" onclick="">clickable span</span> + <span id="span2" onmousedown="">clickable span</span> + <span id="span3" onmouseup="">clickable span</span> + + <div id="combobox1" role="combobox"> + <div id="listbox1" role="listbox"></div> + </div> + <div id="combobox2" role="combobox" aria-owns="listbox2"></div> + <div id="listbox2" role="listbox"></div> + + <menu id="menu"> + <li id="menuItem">menu item!</li> + </menu> +</body> +</html> diff --git a/accessible/tests/mochitest/role/test_general.xhtml b/accessible/tests/mochitest/role/test_general.xhtml new file mode 100644 index 0000000000..95de7775ea --- /dev/null +++ b/accessible/tests/mochitest/role/test_general.xhtml @@ -0,0 +1,59 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessibility Role XUL Test."> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + ok(!isAccessible("image"), + "image without tooltiptext shouldn't be accessible."); + testRole("image-tooltiptext", ROLE_GRAPHIC); + + ok(!isAccessible("statusbarpanel"), + "statusbarpanel shouldn't be accessible."); + testRole("statusbar", ROLE_STATUSBAR); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=900097" + title="statusbarpanel shouldn't be a button accessible"> + Mozilla Bug 900097 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <image id="image" src="../moz.png"/> + <image id="image-tooltiptext" src="../moz.png" tooltiptext="hello"/> + + <statusbarpanel id="statusbarpanel"></statusbarpanel> + <statusbar id="statusbar"></statusbar> + + </hbox> +</window> + diff --git a/accessible/tests/mochitest/role/test_graphics_aria.html b/accessible/tests/mochitest/role/test_graphics_aria.html new file mode 100644 index 0000000000..8ddfe9224d --- /dev/null +++ b/accessible/tests/mochitest/role/test_graphics_aria.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test Graphics ARIA roles</title> + + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + + function doTest() { + // Graphics ARIA role map. + testRole("graphics-document", ROLE_NON_NATIVE_DOCUMENT); + testRole("graphics-object", ROLE_GROUPING); + testRole("graphics-symbol", ROLE_GRAPHIC); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432513" + title="implement ARIA Graphics roles"> + Bug 1432513 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + <div id="graphics-document" role="graphics-document">document</div> + <div id="graphics-object" role="graphics-object">object</div> + <div id="graphics-symbol" role="graphics-symbol">symbol</div> +</body> +</html> diff --git a/accessible/tests/mochitest/role/test_svg.html b/accessible/tests/mochitest/role/test_svg.html new file mode 100644 index 0000000000..716dae7ee2 --- /dev/null +++ b/accessible/tests/mochitest/role/test_svg.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<html> +<head> + <title>SVG elements accessible roles</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + function doTests() { + testRole("svg", ROLE_DIAGRAM); + testRole("g", ROLE_GROUPING); + testRole("rect", ROLE_GRAPHIC); + testRole("circle", ROLE_GRAPHIC); + testRole("ellipse", ROLE_GRAPHIC); + testRole("line", ROLE_GRAPHIC); + testRole("polygon", ROLE_GRAPHIC); + testRole("polyline", ROLE_GRAPHIC); + testRole("path", ROLE_GRAPHIC); + testRole("image", ROLE_GRAPHIC); + testRole("image2", ROLE_GRAPHIC); + testRole("a", ROLE_LINK); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=822983" + title="Map SVG graphic elements to accessibility API"> + Bug 822983 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="svg" + xmlns:xlink="http://www.w3.org/1999/xlink"> + <g id="g"> + <title>g</title> + </g> + <rect width="300" height="100" id="rect" + style="fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,0)"> + <title>rect</title> + </rect> + <circle cx="100" cy="50" r="40" stroke="black" id="circle" + stroke-width="2" fill="red"> + <title>circle</title> + </circle> + <ellipse cx="300" cy="80" rx="100" ry="50" id="ellipse" + style="fill:yellow;stroke:purple;stroke-width:2"> + <title>ellipse</title> + </ellipse> + <line x1="0" y1="0" x2="200" y2="200" id="line" + style="stroke:rgb(255,0,0);stroke-width:2"> + <title>line</title> + </line> + <polygon points="200,10 250,190 160,210" id="polygon" + style="fill:lime;stroke:purple;stroke-width:1"> + <title>polygon</title> + </polygon> + <polyline points="20,20 40,25 60,40 80,120 120,140 200,180" id="polyline" + style="fill:none;stroke:black;stroke-width:3" > + <title>polyline</title> + </polyline> + <path d="M150 0 L75 200 L225 200 Z" id="path"> + <title>path</title> + </path> + <image x1="25" y1="80" width="50" height="20" id="image" + xlink:href="../moz.png"> + <title>image</title> + </image> + <image x1="25" y1="110" width="50" height="20" id="image2" + xlink:href="../moz.png" aria-label="hello"/> + <a href="#" id="a"><text>a</text></a> + </svg> + +</body> +</html> diff --git a/accessible/tests/mochitest/scroll/a11y.ini b/accessible/tests/mochitest/scroll/a11y.ini new file mode 100644 index 0000000000..3f58374c93 --- /dev/null +++ b/accessible/tests/mochitest/scroll/a11y.ini @@ -0,0 +1,5 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_zoom.html] diff --git a/accessible/tests/mochitest/scroll/test_zoom.html b/accessible/tests/mochitest/scroll/test_zoom.html new file mode 100644 index 0000000000..b0ece28a91 --- /dev/null +++ b/accessible/tests/mochitest/scroll/test_zoom.html @@ -0,0 +1,145 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test scrollToPoint when page is zoomed</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + + <script type="application/javascript"> + function testScrollToPoint() { + // scrollToPoint relative screen + var anchor = getAccessible("bottom1"); + let [x /* y */] = getPos(anchor); + var [docX, docY] = getPos(document); + + anchor.scrollToPoint(COORDTYPE_SCREEN_RELATIVE, docX, docY); + testPos(anchor, [x, docY]); + + // scrollToPoint relative window + anchor = getAccessible("bottom2"); + [x /* y */] = getPos(anchor); + var wnd = getRootAccessible().DOMDocument.defaultView; + var [screenX, screenY] = CSSToDevicePixels(wnd, wnd.screenX, wnd.screenY); + let scrollToX = docX - screenX, scrollToY = docY - screenY; + + anchor.scrollToPoint(COORDTYPE_WINDOW_RELATIVE, scrollToX, scrollToY); + testPos(anchor, [x, docY]); + + // scrollToPoint relative parent + anchor = getAccessible("bottom3"); + [x /* y */] = getPos(anchor); + var [parentX, parentY] = getPos(anchor.parent); + scrollToX = parentX - docX; + scrollToY = parentY - docY; + + anchor.scrollToPoint(COORDTYPE_PARENT_RELATIVE, scrollToX, scrollToY); + testPos(anchor, [x, docY]); + } + + function doTest() { + testScrollToPoint(); + zoomDocument(document, 2.0); + testScrollToPoint(); // zoom and test again + + zoomDocument(document, 1.0); + SimpleTest.finish(); + } + + addA11yLoadEvent(doTest); + SimpleTest.waitForExplicitFinish(); + </script> + +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942" + title="scrollToPoint is broken when page is zoomed"> + Mozilla Bug 727942 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <h1>Below there is a bunch of named anchors</h1> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + This is in the middle anchor #1<a id="bottom1"></a> + <br><br><br><br><br><br><br><br><br><br> + This is in the middle anchor #2<a id="bottom2"></a> + <br><br><br><br><br><br><br><br><br><br> + This is in the middle anchor #3<a id="bottom3"></a> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> + <br><br><br><br><br><br><br><br><br><br> +</body> +</html> diff --git a/accessible/tests/mochitest/selectable.js b/accessible/tests/mochitest/selectable.js new file mode 100644 index 0000000000..43c7260b0a --- /dev/null +++ b/accessible/tests/mochitest/selectable.js @@ -0,0 +1,138 @@ +/* import-globals-from common.js */ +/* import-globals-from states.js */ + +/** + * Test selection getter methods of nsIAccessibleSelectable. + * + * @param aIdentifier [in] selectable container accessible + * @param aSelectedChildren [in] array of selected children + */ +function testSelectableSelection(aIdentifier, aSelectedChildren, aMsg) { + var acc = getAccessible(aIdentifier, [nsIAccessibleSelectable]); + if (!acc) { + return; + } + + var msg = aMsg ? aMsg : ""; + var len = aSelectedChildren.length; + + // getSelectedChildren + var selectedChildren = acc.selectedItems; + is( + selectedChildren ? selectedChildren.length : 0, + len, + msg + + "getSelectedChildren: wrong selected children count for " + + prettyName(aIdentifier) + ); + + for (let idx = 0; idx < len; idx++) { + let expectedAcc = aSelectedChildren[idx]; + let expectedAccId = + expectedAcc instanceof nsIAccessible + ? getAccessibleDOMNodeID(expectedAcc) + : expectedAcc; + let actualAcc = selectedChildren.queryElementAt(idx, nsIAccessible); + let actualAccId = getAccessibleDOMNodeID(actualAcc); + is( + actualAccId, + expectedAccId, + msg + + "getSelectedChildren: wrong selected child at index " + + idx + + " for " + + prettyName(aIdentifier) + + " { actual : " + + prettyName(actualAcc) + + ", expected: " + + prettyName(expectedAcc) + + "}" + ); + } + + // selectedItemCount + is( + acc.selectedItemCount, + aSelectedChildren.length, + "selectedItemCount: wrong selected children count for " + + prettyName(aIdentifier) + ); + + // getSelectedItemAt + for (let idx = 0; idx < len; idx++) { + let expectedAcc = aSelectedChildren[idx]; + let expectedAccId = + expectedAcc instanceof nsIAccessible + ? getAccessibleDOMNodeID(expectedAcc) + : expectedAcc; + is( + getAccessibleDOMNodeID(acc.getSelectedItemAt(idx)), + expectedAccId, + msg + + "getSelectedItemAt: wrong selected child at index " + + idx + + " for " + + prettyName(aIdentifier) + ); + } + + // isItemSelected + testIsItemSelected(acc, acc, { value: 0 }, aSelectedChildren, msg); +} + +/** + * Test isItemSelected method, helper for testSelectableSelection + */ +function testIsItemSelected( + aSelectAcc, + aTraversedAcc, + aIndexObj, + aSelectedChildren, + aMsg +) { + var childCount = aTraversedAcc.childCount; + for (var idx = 0; idx < childCount; idx++) { + var child = aTraversedAcc.getChildAt(idx); + var [state /* extraState */] = getStates(child); + if (state & STATE_SELECTABLE) { + var isSelected = false; + var len = aSelectedChildren.length; + for (var jdx = 0; jdx < len; jdx++) { + let expectedAcc = aSelectedChildren[jdx]; + let matches = + expectedAcc instanceof nsIAccessible + ? child == expectedAcc + : getAccessibleDOMNodeID(child) == expectedAcc; + + if (matches) { + isSelected = true; + break; + } + } + + // isItemSelected + is( + aSelectAcc.isItemSelected(aIndexObj.value++), + isSelected, + aMsg + + "isItemSelected: wrong selected child " + + prettyName(child) + + " for " + + prettyName(aSelectAcc) + ); + + // selected state + testStates( + child, + isSelected ? STATE_SELECTED : 0, + 0, + !isSelected ? STATE_SELECTED : 0, + 0 + ); + + continue; + } + + testIsItemSelected(aSelectAcc, child, aIndexObj, aSelectedChildren); + } +} diff --git a/accessible/tests/mochitest/selectable/a11y.ini b/accessible/tests/mochitest/selectable/a11y.ini new file mode 100644 index 0000000000..4f980a18f4 --- /dev/null +++ b/accessible/tests/mochitest/selectable/a11y.ini @@ -0,0 +1,10 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/treeview.css + +[test_listbox.xhtml] +[test_menu.xhtml] +[test_menulist.xhtml] +[test_tabs.xhtml] +[test_tree.xhtml] diff --git a/accessible/tests/mochitest/selectable/test_listbox.xhtml b/accessible/tests/mochitest/selectable/test_listbox.xhtml new file mode 100644 index 0000000000..b915b2790f --- /dev/null +++ b/accessible/tests/mochitest/selectable/test_listbox.xhtml @@ -0,0 +1,144 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<?xml-stylesheet href="../treeview.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL tree selectable tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../selectable.js" /> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Test + + //gA11yEventDumpID = "debug"; + + var gQueue = null; + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // single selectable listbox, the first item is selected by default + + var id = "listbox"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for list of " + id); + + var select = getAccessible(id, [nsIAccessibleSelectable]); + select.removeItemFromSelection(0); + testSelectableSelection(select, [ ]); + + select.addItemToSelection(1); + testSelectableSelection(select, [ "lb1_item2" ], "addItemToSelect(1): "); + + select.removeItemFromSelection(1); + testSelectableSelection(select, [ ], + "removeItemFromSelection(1): "); + + todo(!select.selectAll(), + "No way to select all items in listbox '" + id + "'"); + testSelectableSelection(select, [ "lb1_item1" ], "selectAll: "); + + select.addItemToSelection(1); + select.unselectAll(); + testSelectableSelection(select, [ ], "unselectAll: "); + + ////////////////////////////////////////////////////////////////////////// + // multiple selectable listbox + + id = "listbox2"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for list of " + id); + + select = getAccessible(id, [nsIAccessibleSelectable]); + testSelectableSelection(select, [ ]); + + select.addItemToSelection(1); + testSelectableSelection(select, [ "lb2_item2" ], "addItemToSelect(1): "); + + select.removeItemFromSelection(1); + testSelectableSelection(select, [ ], + "removeItemFromSelection(1): "); + + is(select.selectAll(), true, + "All items should be selected in listbox '" + id + "'"); + testSelectableSelection(select, [ "lb2_item1", "lb2_item2" ], + "selectAll: "); + + select.unselectAll(); + testSelectableSelection(select, [ ], "unselectAll: "); + + ////////////////////////////////////////////////////////////////////////// + // listbox with headers + + // XXX: addItemToSelection/removeItemFromSelection don't work correctly + // on listboxes with headers because header is inserted into hierarchy + // and child indexes that are used in these methods are shifted (see bug + // 591939). + todo(false, + "Fix addItemToSelection/removeItemFromSelection on listboxes with headers."); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176" + title="add pseudo SelectAccessible interface"> + Mozilla Bug 590176 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <richlistbox id="listbox"> + <richlistitem id="lb1_item1"> + <label value="cell0"/> + <label value="cell1"/> + </richlistitem> + <richlistitem id="lb1_item2"> + <label value="cell3"/> + <label value="cell4"/> + </richlistitem> + </richlistbox> + + <richlistbox id="listbox2" seltype="multiple"> + <richlistitem id="lb2_item1"> + <label value="cell0"/> + <label value="cell1"/> + </richlistitem> + <richlistitem id="lb2_item2"> + <label value="cell3"/> + <label value="cell4"/> + </richlistitem> + </richlistbox> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/selectable/test_menu.xhtml b/accessible/tests/mochitest/selectable/test_menu.xhtml new file mode 100644 index 0000000000..bfef622348 --- /dev/null +++ b/accessible/tests/mochitest/selectable/test_menu.xhtml @@ -0,0 +1,77 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<?xml-stylesheet href="../treeview.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL tree selectable tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../selectable.js" /> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Test + + //gA11yEventDumpID = "debug"; + + var gQueue = null; + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // menu + + var id = "menu"; + var menu = getAccessible("menu"); + var menuList = menu.firstChild; + todo(isAccessible(menuList, [nsIAccessibleSelectable]), + "No selectable accessible for list of menu '" + id + "'"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176" + title="add pseudo SelectAccessible interface"> + Mozilla Bug 590176 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <menu label="menu" id="menu"> + <menupopup> + <menuitem label="item1" id="m_item1"/> + <menuitem label="item2" id="m_item2"/> + </menupopup> + </menu> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/selectable/test_menulist.xhtml b/accessible/tests/mochitest/selectable/test_menulist.xhtml new file mode 100644 index 0000000000..2e4b5ac08f --- /dev/null +++ b/accessible/tests/mochitest/selectable/test_menulist.xhtml @@ -0,0 +1,95 @@ +<?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"?> +<?xml-stylesheet href="../treeview.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL tree selectable tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../selectable.js" /> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Test + + //gA11yEventDumpID = "debug"; + + var gQueue = null; + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // menulist aka combobox + + var id = "combobox"; + var combobox = getAccessible(id); + var comboboxList = combobox.firstChild; + ok(isAccessible(comboboxList, [nsIAccessibleSelectable]), + "No selectable accessible for list of " + id); + + var select = getAccessible(comboboxList, [nsIAccessibleSelectable]); + testSelectableSelection(select, [ "cb1_item1" ]); + + select.addItemToSelection(1); + testSelectableSelection(select, [ "cb1_item2" ], "addItemToSelection(1): "); + + select.removeItemFromSelection(1); + testSelectableSelection(select, [ ], + "removeItemFromSelection(1): "); + + is(select.selectAll(), false, + "No way to select all items in combobox '" + id + "'"); + testSelectableSelection(select, [ ], "selectAll: "); + + select.addItemToSelection(1); + select.unselectAll(); + testSelectableSelection(select, [ ], "unselectAll: "); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176" + title="add pseudo SelectAccessible interface"> + Mozilla Bug 590176 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <menulist id="combobox"> + <menupopup> + <menuitem id="cb1_item1" label="item1"/> + <menuitem id="cb1_item2" label="item2"/> + </menupopup> + </menulist> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/selectable/test_tabs.xhtml b/accessible/tests/mochitest/selectable/test_tabs.xhtml new file mode 100644 index 0000000000..bc16ab0fe7 --- /dev/null +++ b/accessible/tests/mochitest/selectable/test_tabs.xhtml @@ -0,0 +1,93 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL tabs selectable tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../selectable.js" /> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + var id = "tabs_single"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for tabs_single"); + var select = getAccessible(id, [nsIAccessibleSelectable]); + + testSelectableSelection(select, ["tab_single1"]); + + select.unselectAll(); + select.addItemToSelection(1); // tab_single2 + testSelectableSelection(select, ["tab_single2"], "select tab_single2: "); + + id = "tabs_multi"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for tabs_multi"); + select = getAccessible(id, [nsIAccessibleSelectable]); + + // Make sure both XUL selection and ARIA selection are included. + testSelectableSelection(select, ["tab_multi_xul1", "tab_multi_aria"]); + + select.unselectAll(); + select.addItemToSelection(2); // tab_multi_xul2 + // We can only affect XUL selection, so ARIA selection won't change. + testSelectableSelection(select, ["tab_multi_aria", "tab_multi_xul2"], + "select tab_multi_xul2: "); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1480058" + title="XUL tabs don't support ARIA selection"> + Mozilla Bug 1480058 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tabbox> + <tabs id="tabs_single"> + <tab id="tab_single1" label="tab1" selected="true"/> + <tab id="tab_single2" label="tab2"/> + </tabs> + </tabbox> + + <tabbox> + <tabs id="tabs_multi" aria-multiselectable="true"> + <tab id="tab_multi_xul1" label="tab1" selected="true"/> + <tab id="tab_multi_aria" label="tab2" aria-selected="true"/> + <tab id="tab_multi_xul2" label="tab3"/> + </tabs> + </tabbox> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/selectable/test_tree.xhtml b/accessible/tests/mochitest/selectable/test_tree.xhtml new file mode 100644 index 0000000000..5527625b24 --- /dev/null +++ b/accessible/tests/mochitest/selectable/test_tree.xhtml @@ -0,0 +1,171 @@ +<?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"?> +<?xml-stylesheet href="../treeview.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL tree selectable tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../selectable.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "debug"; + + /** + * Event queue invoker object to test accessible states for XUL tree + * accessible. + */ + function statesChecker(aTreeID, aView) + { + this.DOMNode = getNode(aTreeID); + + this.invoke = function invoke() + { + this.DOMNode.view = aView; + } + this.check = function check() + { + var tree = getAccessible(this.DOMNode); + + var isTreeMultiSelectable = false; + var seltype = this.DOMNode.getAttribute("seltype"); + if (seltype != "single") + isTreeMultiSelectable = true; + + // selectAll + var accSelectable = getAccessible(this.DOMNode, + [nsIAccessibleSelectable]); + ok(accSelectable, "tree is not selectable!"); + if (accSelectable) { + is(accSelectable.selectAll(), isTreeMultiSelectable, + "SelectAll is not correct for seltype: " + seltype); + } + + var selectedChildren = []; + if (isTreeMultiSelectable) { + var rows = tree.children; + for (var i = 0; i < rows.length; i++) { + var row = rows.queryElementAt(i, nsIAccessible); + if (getRole(row) == ROLE_OUTLINEITEM || getRole(row) == ROLE_ROW) + selectedChildren.push(row); + } + } + testSelectableSelection(accSelectable, selectedChildren, + "selectAll test. "); + + // unselectAll + accSelectable.unselectAll(); + testSelectableSelection(accSelectable, [], "unselectAll test. "); + + // addItemToSelection + accSelectable.addItemToSelection(1); + accSelectable.addItemToSelection(3); + + selectedChildren = isTreeMultiSelectable ? + [ accSelectable.getChildAt(2), accSelectable.getChildAt(4) ] : + [ accSelectable.getChildAt(2) ]; + testSelectableSelection(accSelectable, selectedChildren, + "addItemToSelection test. "); + + // removeItemFromSelection + accSelectable.removeItemFromSelection(1); + + selectedChildren = isTreeMultiSelectable ? + [ accSelectable.getChildAt(4) ] : [ ]; + testSelectableSelection(accSelectable, selectedChildren, + "removeItemFromSelection test. "); + } + + this.getID = function getID() + { + "tree processor for " + prettyName(aTreeID); + } + } + + var gQueue = null; + + function doTest() + { + gQueue = new eventQueue(EVENT_REORDER); + gQueue.push(new statesChecker("tree", new nsTreeTreeView())); + gQueue.push(new statesChecker("treesingle", new nsTreeTreeView())); + gQueue.push(new statesChecker("tabletree", new nsTreeTreeView())); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=523118" + title="we mistake 'cell' and text' xul tree seltypes for multiselects"> + Mozilla Bug 523118 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=624977" + title="Optimize nsXulTreeAccessible selectedItems()"> + Mozilla Bug 624977 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + </treecols> + <treechildren/> + </tree> + + <tree id="treesingle" flex="1" seltype="single"> + <treecols> + <treecol id="col_single" flex="1" primary="true" label="column"/> + </treecols> + <treechildren/> + </tree> + + <tree id="tabletree" flex="1" editable="true"> + <treecols> + <treecol id="tabletree_col1" cycler="true" label="cycler"/> + <treecol id="tabletree_col2" flex="1" primary="true" label="column1"/> + <treecol id="tabletree_col3" flex="1" label="column2"/> + <treecol id="tabletree_col4" flex="1" label="checker" + type="checkbox" editable="true"/> + </treecols> + <treechildren/> + </tree> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/states.js b/accessible/tests/mochitest/states.js new file mode 100644 index 0000000000..c8ce9dd79d --- /dev/null +++ b/accessible/tests/mochitest/states.js @@ -0,0 +1,365 @@ +// ////////////////////////////////////////////////////////////////////////////// +// Helper functions for accessible states testing. +// +// requires: +// common.js +// role.js +// +// ////////////////////////////////////////////////////////////////////////////// +/* import-globals-from common.js */ +/* import-globals-from role.js */ + +// ////////////////////////////////////////////////////////////////////////////// +// State constants + +// const STATE_BUSY is defined in common.js +const STATE_ANIMATED = nsIAccessibleStates.STATE_ANIMATED; +const STATE_CHECKED = nsIAccessibleStates.STATE_CHECKED; +const STATE_CHECKABLE = nsIAccessibleStates.STATE_CHECKABLE; +const STATE_COLLAPSED = nsIAccessibleStates.STATE_COLLAPSED; +const STATE_DEFAULT = nsIAccessibleStates.STATE_DEFAULT; +const STATE_EXPANDED = nsIAccessibleStates.STATE_EXPANDED; +const STATE_EXTSELECTABLE = nsIAccessibleStates.STATE_EXTSELECTABLE; +const STATE_FLOATING = nsIAccessibleStates.STATE_FLOATING; +const STATE_FOCUSABLE = nsIAccessibleStates.STATE_FOCUSABLE; +const STATE_FOCUSED = nsIAccessibleStates.STATE_FOCUSED; +const STATE_HASPOPUP = nsIAccessibleStates.STATE_HASPOPUP; +const STATE_INVALID = nsIAccessibleStates.STATE_INVALID; +const STATE_INVISIBLE = nsIAccessibleStates.STATE_INVISIBLE; +const STATE_LINKED = nsIAccessibleStates.STATE_LINKED; +const STATE_MIXED = nsIAccessibleStates.STATE_MIXED; +const STATE_MULTISELECTABLE = nsIAccessibleStates.STATE_MULTISELECTABLE; +const STATE_OFFSCREEN = nsIAccessibleStates.STATE_OFFSCREEN; +const STATE_PRESSED = nsIAccessibleStates.STATE_PRESSED; +const STATE_PROTECTED = nsIAccessibleStates.STATE_PROTECTED; +const STATE_READONLY = nsIAccessibleStates.STATE_READONLY; +const STATE_REQUIRED = nsIAccessibleStates.STATE_REQUIRED; +const STATE_SELECTABLE = nsIAccessibleStates.STATE_SELECTABLE; +const STATE_SELECTED = nsIAccessibleStates.STATE_SELECTED; +const STATE_TRAVERSED = nsIAccessibleStates.STATE_TRAVERSED; +const STATE_UNAVAILABLE = nsIAccessibleStates.STATE_UNAVAILABLE; + +const EXT_STATE_ACTIVE = nsIAccessibleStates.EXT_STATE_ACTIVE; +const EXT_STATE_CURRENT = nsIAccessibleStates.EXT_STATE_CURRENT; +const EXT_STATE_DEFUNCT = nsIAccessibleStates.EXT_STATE_DEFUNCT; +const EXT_STATE_EDITABLE = nsIAccessibleStates.EXT_STATE_EDITABLE; +const EXT_STATE_ENABLED = nsIAccessibleStates.EXT_STATE_ENABLED; +const EXT_STATE_EXPANDABLE = nsIAccessibleStates.EXT_STATE_EXPANDABLE; +const EXT_STATE_HORIZONTAL = nsIAccessibleStates.EXT_STATE_HORIZONTAL; +const EXT_STATE_MODAL = nsIAccessibleStates.EXT_STATE_MODAL; +const EXT_STATE_MULTI_LINE = nsIAccessibleStates.EXT_STATE_MULTI_LINE; +const EXT_STATE_PINNED = nsIAccessibleStates.EXT_STATE_PINNED; +const EXT_STATE_SENSITIVE = nsIAccessibleStates.EXT_STATE_SENSITIVE; +const EXT_STATE_SINGLE_LINE = nsIAccessibleStates.EXT_STATE_SINGLE_LINE; +const EXT_STATE_STALE = nsIAccessibleStates.EXT_STATE_STALE; +const EXT_STATE_SUPPORTS_AUTOCOMPLETION = + nsIAccessibleStates.EXT_STATE_SUPPORTS_AUTOCOMPLETION; +const EXT_STATE_VERTICAL = nsIAccessibleStates.EXT_STATE_VERTICAL; +const EXT_STATE_SELECTABLE_TEXT = nsIAccessibleStates.EXT_STATE_SELECTABLE_TEXT; +const EXT_STATE_OPAQUE = nsIAccessibleStates.EXT_STATE_OPAQUE; + +const kOrdinalState = false; +const kExtraState = 1; + +// ////////////////////////////////////////////////////////////////////////////// +// Test functions + +/** + * Tests the states and extra states of the given accessible. + * Also tests for unwanted states and extra states. + * In addition, the function performs a few plausibility checks derived from the + * sstates and extra states passed in. + * + * @param aAccOrElmOrID The accessible, DOM element or ID to be tested. + * @param aState The state bits that are wanted. + * @param aExtraState The extra state bits that are wanted. + * @param aAbsentState State bits that are not wanted. + * @param aAbsentExtraState Extra state bits that are not wanted. + * @param aTestName The test name. + */ +function testStates( + aAccOrElmOrID, + aState, + aExtraState, + aAbsentState, + aAbsentExtraState, + aTestName +) { + var [state, extraState] = getStates(aAccOrElmOrID); + var role = getRole(aAccOrElmOrID); + var id = + prettyName(aAccOrElmOrID) + (aTestName ? " [" + aTestName + "]" : ""); + + // Primary test. + if (aState) { + isState(state & aState, aState, false, "wrong state bits for " + id + "!"); + } + + if (aExtraState) { + isState( + extraState & aExtraState, + aExtraState, + true, + "wrong extra state bits for " + id + "!" + ); + } + + if (aAbsentState) { + isState( + state & aAbsentState, + 0, + false, + "state bits should not be present in ID " + id + "!" + ); + } + + if (aAbsentExtraState) { + isState( + extraState & aAbsentExtraState, + 0, + true, + "extraState bits should not be present in ID " + id + "!" + ); + } + + // Additional test. + + // focused/focusable + if (state & STATE_FOCUSED) { + isState( + state & STATE_FOCUSABLE, + STATE_FOCUSABLE, + false, + "Focused " + id + " must be focusable!" + ); + } + + if (aAbsentState && aAbsentState & STATE_FOCUSABLE) { + isState( + state & STATE_FOCUSED, + 0, + false, + "Not focusable " + id + " must be not focused!" + ); + } + + // multiline/singleline + if (extraState & EXT_STATE_MULTI_LINE) { + isState( + extraState & EXT_STATE_SINGLE_LINE, + 0, + true, + "Multiline " + id + " cannot be singleline!" + ); + } + + if (extraState & EXT_STATE_SINGLE_LINE) { + isState( + extraState & EXT_STATE_MULTI_LINE, + 0, + true, + "Singleline " + id + " cannot be multiline!" + ); + } + + // expanded/collapsed/expandable + if (state & STATE_COLLAPSED || state & STATE_EXPANDED) { + isState( + extraState & EXT_STATE_EXPANDABLE, + EXT_STATE_EXPANDABLE, + true, + "Collapsed or expanded " + id + " must be expandable!" + ); + } + + if (state & STATE_COLLAPSED) { + isState( + state & STATE_EXPANDED, + 0, + false, + "Collapsed " + id + " cannot be expanded!" + ); + } + + if (state & STATE_EXPANDED) { + isState( + state & STATE_COLLAPSED, + 0, + false, + "Expanded " + id + " cannot be collapsed!" + ); + } + + if (aAbsentState && extraState & EXT_STATE_EXPANDABLE) { + if (aAbsentState & STATE_EXPANDED) { + isState( + state & STATE_COLLAPSED, + STATE_COLLAPSED, + false, + "Not expanded " + id + " must be collapsed!" + ); + } else if (aAbsentState & STATE_COLLAPSED) { + isState( + state & STATE_EXPANDED, + STATE_EXPANDED, + false, + "Not collapsed " + id + " must be expanded!" + ); + } + } + + // checked/mixed/checkable + if ( + state & STATE_CHECKED || + (state & STATE_MIXED && + role != ROLE_TOGGLE_BUTTON && + role != ROLE_PROGRESSBAR) + ) { + isState( + state & STATE_CHECKABLE, + STATE_CHECKABLE, + false, + "Checked or mixed element must be checkable!" + ); + } + + if (state & STATE_CHECKED) { + isState( + state & STATE_MIXED, + 0, + false, + "Checked element cannot be state mixed!" + ); + } + + if (state & STATE_MIXED) { + isState( + state & STATE_CHECKED, + 0, + false, + "Mixed element cannot be state checked!" + ); + } + + // selected/selectable + if (state & STATE_SELECTED && !(aAbsentState & STATE_SELECTABLE)) { + isState( + state & STATE_SELECTABLE, + STATE_SELECTABLE, + false, + "Selected element must be selectable!" + ); + } +} + +/** + * Tests an accessible and its sub tree for the passed in state bits. + * Used to make sure that states are propagated to descendants, for example the + * STATE_UNAVAILABLE from a container to its children. + * + * @param aAccOrElmOrID The accessible, DOM element or ID to be tested. + * @param aState The state bits that are wanted. + * @param aExtraState The extra state bits that are wanted. + * @param aAbsentState State bits that are not wanted. + */ +function testStatesInSubtree(aAccOrElmOrID, aState, aExtraState, aAbsentState) { + // test accessible and its subtree for propagated states. + var acc = getAccessible(aAccOrElmOrID); + if (!acc) { + return; + } + + if (getRole(acc) != ROLE_TEXT_LEAF) { + // Right now, text leafs don't get tested because the states are not being + // propagated. + testStates(acc, aState, aExtraState, aAbsentState); + } + + // Iterate over its children to see if the state got propagated. + var children = null; + try { + children = acc.children; + } catch (e) {} + ok(children, "Could not get children for " + aAccOrElmOrID + "!"); + + if (children) { + for (var i = 0; i < children.length; i++) { + var childAcc = children.queryElementAt(i, nsIAccessible); + testStatesInSubtree(childAcc, aState, aExtraState, aAbsentState); + } + } +} + +/** + * Fails if no defunct state on the accessible. + */ +function testIsDefunct(aAccessible, aTestName) { + var id = prettyName(aAccessible) + (aTestName ? " [" + aTestName + "]" : ""); + var [, /* state*/ extraState] = getStates(aAccessible); + isState( + extraState & EXT_STATE_DEFUNCT, + EXT_STATE_DEFUNCT, + true, + "no defuct state for " + id + "!" + ); +} + +function getStringStates(aAccOrElmOrID) { + var [state, extraState] = getStates(aAccOrElmOrID); + return statesToString(state, extraState); +} + +function getStates(aAccOrElmOrID) { + var acc = getAccessible(aAccOrElmOrID); + if (!acc) { + return [0, 0]; + } + + var state = {}, + extraState = {}; + acc.getState(state, extraState); + + return [state.value, extraState.value]; +} + +/** + * Return true if the accessible has given states. + */ +function hasState(aAccOrElmOrID, aState, aExtraState) { + var [state, exstate] = getStates(aAccOrElmOrID); + return ( + (aState ? state & aState : true) && + (aExtraState ? exstate & aExtraState : true) + ); +} + +// ////////////////////////////////////////////////////////////////////////////// +// Private implementation details + +/** + * Analogy of SimpleTest.is function used to compare states. + */ +function isState(aState1, aState2, aIsExtraStates, aMsg) { + if (aState1 == aState2) { + ok(true, aMsg); + return; + } + + var got = "0"; + if (aState1) { + got = statesToString( + aIsExtraStates ? 0 : aState1, + aIsExtraStates ? aState1 : 0 + ); + } + + var expected = "0"; + if (aState2) { + expected = statesToString( + aIsExtraStates ? 0 : aState2, + aIsExtraStates ? aState2 : 0 + ); + } + + ok(false, aMsg + "got '" + got + "', expected '" + expected + "'"); +} diff --git a/accessible/tests/mochitest/states/a11y.ini b/accessible/tests/mochitest/states/a11y.ini new file mode 100644 index 0000000000..06e7a5a061 --- /dev/null +++ b/accessible/tests/mochitest/states/a11y.ini @@ -0,0 +1,36 @@ +[DEFAULT] +support-files = + z_frames.html + z_frames_article.html + z_frames_checkbox.html + z_frames_textbox.html + z_frames_update.html + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/dumbfile.zip + !/accessible/tests/mochitest/formimage.png + !/accessible/tests/mochitest/treeview.css + +[test_aria.html] +[test_aria.xhtml] +[test_aria_imgmap.html] +[test_aria_widgetitems.html] +[test_buttons.html] +[test_controls.html] +[test_controls.xhtml] +[test_doc.html] +[test_doc_busy.html] +[test_docarticle.html] +[test_editablebody.html] +[test_expandable.xhtml] +[test_frames.html] +[test_inputs.html] +[test_link.html] +[test_popup.xhtml] +[test_selects.html] +[test_stale.html] +[test_tabs.xhtml] +[test_textbox.xhtml] +[test_tree.xhtml] +[test_visibility.html] +[test_visibility.xhtml] +skip-if = asan # Bug 1199631 diff --git a/accessible/tests/mochitest/states/test_aria.html b/accessible/tests/mochitest/states/test_aria.html new file mode 100644 index 0000000000..da5df6fa09 --- /dev/null +++ b/accessible/tests/mochitest/states/test_aria.html @@ -0,0 +1,655 @@ +<html> + +<head> + <title>ARIA based nsIAccessible states testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style type="text/css"> + .offscreen { + position: absolute; + left: -5000px; + top: -5000px; + height: 100px; + width: 100px; + } + </style> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + function testAriaDisabledTree(aAccOrElmOrID) { + // test accessible and its subtree for propagated state. + var acc = getAccessible(aAccOrElmOrID); + if (!acc) + return; + + var [state /* extraState */] = getStates(aAccOrElmOrID); + if (state & STATE_UNAVAILABLE) { + var role = getRole(acc); + if (role != ROLE_GROUPING) { + testStates(acc, STATE_FOCUSABLE); + } + } + + // Iterate over its children to see if the state got propagated. + var children = null; + try { + children = acc.children; + } catch (e) {} + ok(children, "Could not get children for " + aAccOrElmOrID + "!"); + + if (children) { + for (var i = 0; i < children.length; i++) { + var childAcc = children.queryElementAt(i, nsIAccessible); + testAriaDisabledTree(childAcc); + } + } + } + + function doTest() { + // aria_autocomplete + testStates("textbox_autocomplete_inline", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("textbox_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("textbox_autocomplete_both", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("combobox_autocomplete_inline", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("combobox_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("combobox_autocomplete_both", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + + testStates("htmltext_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("htmltextarea_autocomplete_list", STATE_HASPOPUP, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + + // aria-busy + testStates("textbox_busy_false", 0, 0, STATE_BUSY); + testStates("textbox_busy_true", STATE_BUSY); + testStates("textbox_busy_error", STATE_INVALID); + + // aria-expanded + testStates("combobox", STATE_COLLAPSED); + testStates("combobox_expanded", STATE_EXPANDED); + + // tri-state checkbox + var checkboxElem = getNode("check1"); + if (checkboxElem) { + testStates(checkboxElem, STATE_CHECKED); + checkboxElem.checked = false; + testStates(checkboxElem, 0, 0, STATE_CHECKED); + checkboxElem.indeterminate = true; + testStates(checkboxElem, STATE_MIXED, 0); + } + + // aria-checked + testStates("aria_checked_checkbox", STATE_CHECKED); + testStates("aria_mixed_checkbox", STATE_MIXED); + testStates("aria_checked_switch", STATE_CHECKED); + testStates("aria_mixed_switch", 0, 0, STATE_MIXED); // unsupported + testStates("aria_mixed_switch", 0, 0, STATE_CHECKED); // not checked due to being unsupported + + // test disabled group and all its descendants to see if they are + // disabled, too. See bug 429285. + testAriaDisabledTree("group"); + + // aria-modal + testStates("aria_modal", 0, EXT_STATE_MODAL); + testStates("aria_modal_false", 0, 0, 0, EXT_STATE_MODAL); + + // aria-multiline + testStates("aria_multiline_textbox", 0, EXT_STATE_MULTI_LINE); + + // aria-multiselectable + testStates("aria_multiselectable_listbox", + STATE_MULTISELECTABLE | STATE_EXTSELECTABLE); + testStates("aria_multiselectable_tablist", + STATE_MULTISELECTABLE | STATE_EXTSELECTABLE); + + // aria-pressed + testStates("aria_pressed_button", STATE_PRESSED, 0, STATE_CHECKABLE); + testStates("aria_pressed_native_button", STATE_PRESSED, 0, STATE_CHECKABLE); + + // aria-readonly + testStates("aria_readonly_textbox", STATE_READONLY); + + // readonly on grid and gridcell + testStates("aria_grid_default", 0, 0, + STATE_READONLY, 0); + testStates("aria_grid_default_colheader_readonly", STATE_READONLY, 0, + 0, 0); + testStates("aria_grid_default_colheader_inherited", 0, 0, + STATE_READONLY, 0); + testStates("aria_grid_default_rowheader_readonly", STATE_READONLY, 0, + 0, 0); + testStates("aria_grid_default_rowheader_inherited", 0, 0, + STATE_READONLY, 0); + testStates("aria_grid_default_cell_readonly", STATE_READONLY, 0, + 0, 0); + testStates("aria_grid_default_cell_inherited", 0, 0, + STATE_READONLY, 0); + + testStates("aria_grid_readonly", STATE_READONLY, 0, + 0, 0); + testStates("aria_grid_readonly_colheader_editable", 0, 0, + STATE_READONLY, 0); + testStates("aria_grid_readonly_colheader_inherited", STATE_READONLY, 0, + 0, 0); + testStates("aria_grid_readonly_rowheader_editable", 0, 0, + STATE_READONLY, 0); + testStates("aria_grid_readonly_rowheader_inherited", STATE_READONLY, 0, + 0, 0); + testStates("aria_grid_readonly_cell_editable", 0, 0, + STATE_READONLY, 0); + testStates("aria_grid_readonly_cell_inherited", STATE_READONLY, 0, + 0, 0); + + // readonly on treegrid and gridcell + testStates("aria_treegrid_default", 0, 0, + STATE_READONLY, 0); + testStates("aria_treegrid_default_colheader_readonly", STATE_READONLY, 0, + 0, 0); + testStates("aria_treegrid_default_colheader_inherited", 0, 0, + STATE_READONLY, 0); + testStates("aria_treegrid_default_rowheader_readonly", STATE_READONLY, 0, + 0, 0); + testStates("aria_treegrid_default_rowheader_inherited", 0, 0, + STATE_READONLY, 0); + testStates("aria_treegrid_default_cell_readonly", STATE_READONLY, 0, + 0, 0); + testStates("aria_treegrid_default_cell_inherited", 0, 0, + STATE_READONLY, 0); + + testStates("aria_treegrid_readonly", STATE_READONLY, 0, + 0, 0); + testStates("aria_treegrid_readonly_colheader_editable", 0, 0, + STATE_READONLY, 0); + testStates("aria_treegrid_readonly_colheader_inherited", STATE_READONLY, 0, + 0, 0); + testStates("aria_treegrid_readonly_rowheader_editable", 0, 0, + STATE_READONLY, 0); + testStates("aria_treegrid_readonly_rowheader_inherited", STATE_READONLY, 0, + 0, 0); + testStates("aria_treegrid_readonly_cell_editable", 0, 0, + STATE_READONLY, 0); + testStates("aria_treegrid_readonly_cell_inherited", STATE_READONLY, 0, + 0, 0); + + // aria-readonly on directory + testStates("aria_directory", STATE_READONLY); + + // aria-selectable + testStates("aria_selectable_listitem", STATE_SELECTABLE | STATE_SELECTED); + + // active state caused by aria-activedescendant + testStates("as_item1", 0, EXT_STATE_ACTIVE); + testStates("as_item2", 0, 0, 0, EXT_STATE_ACTIVE); + + // universal ARIA properties inherited from file input control + var fileBrowseButton = getAccessible("fileinput").firstChild; + testStates(fileBrowseButton, + STATE_BUSY | STATE_UNAVAILABLE | STATE_REQUIRED | STATE_HASPOPUP | STATE_INVALID); + // No states on the label. + + // offscreen test + testStates("aria_offscreen_textbox", STATE_OFFSCREEN); + + // + // This section tests aria roles on links/anchors for underlying + // HTMLLinkAccessible creation. (see closed bug 494807) + // + + // strong roles + testStates("aria_menuitem_link", 0, 0, STATE_LINKED); + testStates("aria_button_link", 0, 0, STATE_LINKED); + testStates("aria_checkbox_link", 0, 0, STATE_LINKED); + + // strong landmark + testStates("aria_application_link", 0, 0, STATE_LINKED); + testStates("aria_application_anchor", 0, 0, STATE_SELECTABLE); + + // strange cases + testStates("aria_link_link", STATE_LINKED); + testStates("aria_link_anchor", STATE_SELECTABLE); + + // some landmarks that break accessibility for these native elements + // Note that these are illegal uses by web authors as per WAI-ARIA in HTML + testStates("aria_main_link", 0, 0, STATE_LINKED); + testStates("aria_navigation_link", 0, 0, STATE_LINKED); + testStates("aria_main_anchor", 0, 0, STATE_SELECTABLE); + testStates("aria_navigation_anchor", 0, 0, STATE_SELECTABLE); + + // aria-orientation + testStates("aria_combobox", 0, 0, 0, EXT_STATE_HORIZONTAL | EXT_STATE_VERTICAL); + testStates("aria_hcombobox", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vcombobox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_listbox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_hlistbox", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vlistbox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_menu", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_hmenu", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vmenu", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_menubar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_hmenubar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vmenubar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_radiogroup", 0, 0, 0, EXT_STATE_HORIZONTAL | EXT_STATE_VERTICAL); + testStates("aria_hradiogroup", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vradiogroup", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_scrollbar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_hscrollbar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vscrollbar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_separator", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_hseparator", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vseparator", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_slider", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_hslider", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vslider", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_tablist", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_htablist", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vtablist", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_toolbar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_htoolbar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vtoolbar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_tree", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_htree", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vtree", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("aria_treegrid", 0, 0, 0, EXT_STATE_HORIZONTAL | EXT_STATE_VERTICAL); + testStates("aria_htreegrid", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + testStates("aria_vtreegrid", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + + // indeterminate ARIA progressbars (no aria-valuenow or aria-valuetext attribute) + // should expose mixed state + testStates("aria_progressbar", STATE_MIXED); + testStates("aria_progressbar_valuenow", 0, 0, STATE_MIXED); + testStates("aria_progressbar_valuetext", 0, 0, STATE_MIXED); + + // aria-current + testStates("current_page_1", 0, EXT_STATE_CURRENT); + testStates("page_2", 0, 0, EXT_STATE_CURRENT); + testStates("page_3", 0, 0, EXT_STATE_CURRENT); + testStates("page_4", 0, 0, EXT_STATE_CURRENT); + testStates("current_foo", 0, EXT_STATE_CURRENT); + + testStates("aria_listbox", STATE_FOCUSABLE); + testStates("aria_grid", STATE_FOCUSABLE); + testStates("aria_tree", STATE_FOCUSABLE); + testStates("aria_treegrid", STATE_FOCUSABLE); + testStates("aria_listbox_disabled", 0, 0, STATE_FOCUSABLE); + testStates("aria_grid_disabled", 0, 0, STATE_FOCUSABLE); + testStates("aria_tree_disabled", 0, 0, STATE_FOCUSABLE); + testStates("aria_treegrid_disabled", 0, 0, STATE_FOCUSABLE); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=457219" + title="nsIAccessible states testing"> + Mozilla Bug 457219 + </a><br /> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=429285" + title="Propagate aria-disabled to descendants"> + Mozilla Bug 429285 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=457226" + title="Mochitests for ARIA states"> + Mozilla Bug 457226 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=499653" + title="Unify ARIA state attributes mapping rules"> + Mozilla Bug 499653 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=681674" + title="aria-autocomplete not supported on standard form text input controls"> + Mozilla Bug 681674 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=681674" + title="aria-orientation should be applied to separator and slider roles"> + Mozilla Bug 681674 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=689847" + title="Expose active state on current item of selectable widgets"> + Mozilla Bug 689847 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017" + title="File input control should be propogate states to descendants"> + Mozilla Bug 699017 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=690199" + title="ARIA select widget should expose focusable state regardless the way they manage its children"> + Mozilla Bug 690199 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=740851" + title="ARIA undetermined progressmeters should expose mixed state"> + Mozilla Bug 740851 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=762876" + title="fix default horizontal / vertical state of role=scrollbar and ensure only one of horizontal / vertical states is exposed"> + Mozilla Bug 762876 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=892091" + title="ARIA treegrid should be editable by default"> + Bug 892091 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=835121" + title="ARIA grid should be editable by default"> + Mozilla Bug 835121 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958" + title="Pressed state is not exposed on a button element with aria-pressed attribute"> + Mozilla Bug 989958 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1136563" + title="Support ARIA 1.1 switch role"> + Mozilla Bug 1136563 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1355921" + title="Elements with a defined, non-false value for aria-current should expose ATK_STATE_ACTIVE"> + Mozilla Bug 1355921 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="textbox_autocomplete_inline" role="textbox" aria-autocomplete="inline"></div> + <div id="textbox_autocomplete_list" role="textbox" aria-autocomplete="list"></div> + <div id="textbox_autocomplete_both" role="textbox" aria-autocomplete="both"></div> + <div id="combobox_autocomplete_inline" role="combobox" aria-autocomplete="inline"></div> + <div id="combobox_autocomplete_list" role="combobox" aria-autocomplete="list"></div> + <div id="combobox_autocomplete_both" role="combobox" aria-autocomplete="both"></div> + + <input id="htmltext_autocomplete_list" type="text" aria-autocomplete="list" /> + <textarea id="htmltextarea_autocomplete_list" aria-autocomplete="list"></textarea> + + <div id="textbox_busy_false" role="textbox" aria-busy="false"></div> + <div id="textbox_busy_true" role="textbox" aria-busy="true"></div> + <div id="textbox_busy_error" role="textbox" aria-busy="error"></div> + + <div id="combobox" role="combobox">combobox</div> + <div id="combobox_expanded" role="combobox" + aria-expanded="true">combobox</div> + + <input type="checkbox" id="check1" value="I agree" checked="true"/> + + <div id="aria_checked_checkbox" role="checkbox" aria-checked="true"> + I agree + </div> + + <div id="aria_mixed_checkbox" role="checkbox" aria-checked="mixed"> + I might agree + </div> + + <div id="aria_checked_switch" role="switch" aria-checked="true"> + I am switched on + </div> + + <div id="aria_mixed_switch" role="switch" aria-checked="mixed"> + I am unsupported + </div> + + <div id="aria_modal" aria-modal="true">modal stuff</div> + <div id="aria_modal_false" aria-modal="false">non modal stuff</div>div> + <div id="aria_multiline_textbox" role="textbox" aria-multiline="true"></div> + <div id="aria_multiselectable_listbox" role="listbox" aria-multiselectable="true"></div> + <div id="aria_multiselectable_tablist" role="tablist" aria-multiselectable="true"></div> + <div id="aria_pressed_button" role="button" aria-pressed="true">Button</div> + <button id="aria_pressed_native_button" aria-pressed="true">Button</button> + + <div id="aria_readonly_textbox" + role="textbox" aria-readonly="true">This text should be readonly</div> + + <div id="aria_grid_default" role="grid"> + <div role="row"> + <div id="aria_grid_default_colheader_readonly" + role="columnheader" aria-readonly="true">colheader1</div> + <div id="aria_grid_default_colheader_inherited" + role="columnheader">colheader2</div> + </div> + <div role="row"> + <div id="aria_grid_default_rowheader_readonly" + role="rowheader" aria-readonly="true">rowheader1</div> + <div id="aria_grid_default_rowheader_inherited" + role="rowheader">rowheader2</div> + </div> + <div role="row"> + <div id="aria_grid_default_cell_readonly" + role="gridcell" aria-readonly="true">gridcell1</div> + <div id="aria_grid_default_cell_inherited" + role="gridcell">gridcell2</div> + </div> + </div> + + <div id="aria_grid_readonly" role="grid" aria-readonly="true"> + <div role="row"> + <div id="aria_grid_readonly_colheader_editable" + role="columnheader" aria-readonly="false">colheader1</div> + <div id="aria_grid_readonly_colheader_inherited" + role="columnheader">colheader2</div> + </div> + <div role="row"> + <div id="aria_grid_readonly_rowheader_editable" + role="rowheader" aria-readonly="false">rowheader1</div> + <div id="aria_grid_readonly_rowheader_inherited" + role="rowheader">rowheader2</div> + </div> + <div role="row"> + <div id="aria_grid_readonly_cell_editable" + role="gridcell" aria-readonly="false">gridcell1</div> + <div id="aria_grid_readonly_cell_inherited" + role="gridcell">gridcell2</div> + </div> + </div> + + <div id="aria_treegrid_default" role="grid"> + <div role="row"> + <div id="aria_treegrid_default_colheader_readonly" + role="columnheader" aria-readonly="true">colheader1</div> + <div id="aria_treegrid_default_colheader_inherited" + role="columnheader">colheader2</div> + </div> + <div role="row"> + <div id="aria_treegrid_default_rowheader_readonly" + role="rowheader" aria-readonly="true">rowheader1</div> + <div id="aria_treegrid_default_rowheader_inherited" + role="rowheader">rowheader2</div> + </div> + <div role="row"> + <div id="aria_treegrid_default_cell_readonly" + role="gridcell" aria-readonly="true">gridcell1</div> + <div id="aria_treegrid_default_cell_inherited" + role="gridcell">gridcell2</div> + </div> + </div> + + <div id="aria_treegrid_readonly" role="grid" aria-readonly="true"> + <div role="row"> + <div id="aria_treegrid_readonly_colheader_editable" + role="columnheader" aria-readonly="false">colheader1</div> + <div id="aria_treegrid_readonly_colheader_inherited" + role="columnheader">colheader2</div> + </div> + <div role="row"> + <div id="aria_treegrid_readonly_rowheader_editable" + role="rowheader" aria-readonly="false">rowheader1</div> + <div id="aria_treegrid_readonly_rowheader_inherited" + role="rowheader">rowheader2</div> + </div> + <div role="row"> + <div id="aria_treegrid_readonly_cell_editable" + role="gridcell" aria-readonly="false">gridcell1</div> + <div id="aria_treegrid_readonly_cell_inherited" + role="gridcell">gridcell2</div> + </div> + </div> + + <div role="listbox"> + <div id="aria_selectable_listitem" role="option" aria-selected="true">Item1</div> + </div> + + <!-- Test that aria-disabled state gets propagated to all descendants --> + <div id="group" role="group" aria-disabled="true"> + <button>hi</button> + <div tabindex="0" role="listbox" aria-activedescendant="item1" + aria-owns="item5"> + <div role="option" id="item1">Item 1</div> + <div role="option" id="item2">Item 2</div> + <div role="option" id="item3">Item 3</div> + <div role="option" id="item4">Item 4</div> + </div> + <div role="slider" tabindex="0">A slider</div> + </div> + <div role="option" id="item5">Item 5</div> + + <!-- Test active state --> + <div id="as_listbox" tabindex="0" role="listbox" + aria-activedescendant="as_item1"> + <div role="option" id="as_item1">Item 1</div> + <div role="option" id="as_item2">Item 2</div> + </div> + + <!-- universal ARIA properties should be inherited by text field of file input --> + <input type="file" id="fileinput" + aria-busy="true" + aria-disabled="true" + aria-required="true" + aria-haspopup="true" + aria-invalid="true"> + + <div id="offscreen_log" role="log" class="offscreen"> + <div id="aria_offscreen_textbox" role="textbox" aria-readonly="true">This text should be offscreen</div> + </div> + + <a id="aria_menuitem_link" role="menuitem" href="foo">menuitem</a> + <a id="aria_button_link" role="button" href="foo">button</a> + <a id="aria_checkbox_link" role="checkbox" href="foo">checkbox</a> + + <!-- strange edge case: please don't do this in the wild --> + <a id="aria_link_link" role="link" href="foo">link</a> + <a id="aria_link_anchor" role="link" name="link_anchor">link</a> + + <!-- landmarks: links --> + <a id="aria_application_link" role="application" href="foo">app</a> + <a id="aria_main_link" role="main" href="foo">main</a> + <a id="aria_navigation_link" role="navigation" href="foo">nav</a> + + <!-- landmarks: anchors --> + <a id="aria_application_anchor" role="application" name="app_anchor">app</a> + <a id="aria_main_anchor" role="main" name="main_anchor">main</a> + <a id="aria_navigation_anchor" role="navigation" name="nav_anchor">nav</a> + + <!-- aria-orientation --> + <div id="aria_combobox" role="combobox">combobox</div> + <div id="aria_hcombobox" role="combobox" aria-orientation="horizontal">horizontal combobox</div> + <div id="aria_vcombobox" role="combobox" aria-orientation="vertical">vertical combobox</div> + <div id="aria_listbox" role="listbox">listbox</div> + <div id="aria_hlistbox" role="listbox" aria-orientation="horizontal">horizontal listbox</div> + <div id="aria_vlistbox" role="listbox" aria-orientation="vertical">vertical listbox</div> + <div id="aria_menu" role="menu">menu</div> + <div id="aria_hmenu" role="menu" aria-orientation="horizontal">horizontal menu</div> + <div id="aria_vmenu" role="menu" aria-orientation="vertical">vertical menu</div> + <div id="aria_menubar" role="menubar">menubar</div> + <div id="aria_hmenubar" role="menubar" aria-orientation="horizontal">horizontal menubar</div> + <div id="aria_vmenubar" role="menubar" aria-orientation="vertical">vertical menubar</div> + <div id="aria_radiogroup" role="radiogroup">radiogroup</div> + <div id="aria_hradiogroup" role="radiogroup" aria-orientation="horizontal">horizontal radiogroup</div> + <div id="aria_vradiogroup" role="radiogroup" aria-orientation="vertical">vertical radiogroup</div> + <div id="aria_scrollbar" role="scrollbar">scrollbar</div> + <div id="aria_hscrollbar" role="scrollbar" aria-orientation="horizontal">horizontal scrollbar</div> + <div id="aria_vscrollbar" role="scrollbar" aria-orientation="vertical">vertical scrollbar</div> + <div id="aria_separator" role="separator">separator</div> + <div id="aria_hseparator" role="separator" aria-orientation="horizontal">horizontal separator</div> + <div id="aria_vseparator" role="separator" aria-orientation="vertical">vertical separator</div> + <div id="aria_slider" role="slider">slider</div> + <div id="aria_hslider" role="slider" aria-orientation="horizontal">horizontal slider</div> + <div id="aria_vslider" role="slider" aria-orientation="vertical">vertical slider</div> + + <div id="aria_tablist" role="tablist">tablist</div> + <div id="aria_htablist" role="tablist" aria-orientation="horizontal">horizontal tablist</div> + <div id="aria_vtablist" role="tablist" aria-orientation="vertical">vertical tablist</div> + <div id="aria_toolbar" role="toolbar">toolbar</div> + <div id="aria_htoolbar" role="toolbar" aria-orientation="horizontal">horizontal toolbar</div> + <div id="aria_vtoolbar" role="toolbar" aria-orientation="vertical">vertical toolbar</div> + <div id="aria_tree" role="tree">tree</div> + <div id="aria_htree" role="tree" aria-orientation="horizontal">horizontal tree</div> + <div id="aria_vtree" role="tree" aria-orientation="vertical">vertical tree</div> + <div id="aria_treegrid" role="treegrid">treegrid</div> + <div id="aria_htreegrid" role="treegrid" aria-orientation="horizontal">horizontal treegrid</div> + <div id="aria_vtreegrid" role="treegrid" aria-orientation="vertical">vertical treegrid</div> + + <!-- indeterminate ARIA progressbars should expose mixed state --> + <div id="aria_progressbar" role="progressbar"></div> + <div id="aria_progressbar_valuenow" role="progressbar" aria-valuenow="1"></div> + <div id="aria_progressbar_valuetext" role="progressbar" aria-valuetext="value"></div> + + <!-- ARIA select widget should expose focusable state regardless the way they manage its children --> + <div id="aria_listbox" role="listbox"> + <div role="option" tabindex="0">A</div> + <div role="option" tabindex="0">a</div> + </div> + <div id="aria_grid" role="grid"> + <div role="row"><div role="gridcell" tabindex="0">B</div></div></div> + <div role="row"><div role="gridcell" tabindex="0">b</div></div></div> + <div id="aria_tree" role="tree"> + <div role="treeitem" tabindex="0">C</div> + <div role="treeitem" tabindex="0">c</div> + </div> + <div id="aria_treegrid" role="treegrid"> + <div role="row"><div role="gridcell" tabindex="0">D</div></div> + <div role="row"><div role="gridcell" tabindex="0">d</div></div> + </div> + <div id="aria_listbox_disabled" role="listbox" aria-disabled="true"> + <div role="option">E</div> + <div role="option">e</div> + </div> + <div id="aria_grid_disabled" role="grid" aria-disabled="true"> + <div role="row"><div role="gridcell">F</div></div> + <div role="row"><div role="gridcell">f</div></div> + </div> + <div id="aria_tree_disabled" role="tree" aria-disabled="true"> + <div role="treeitem">G</div> + <div role="treeitem">g</div> + </div> + <div id="aria_treegrid_disabled" role="treegrid" aria-disabled="true"> + <div role="row"><div role="gridcell">H</div></div> + <div role="row"><div role="gridcell">h</div></div> + </div> + + <!-- Test that directory is readonly --> + <div id="aria_directory" role="directory"></div> + + <!-- aria-current --> + <div id="current_page_1" role="link" aria-current="page">1</div> + <div id="page_2" role="link" aria-current="false">2</div> + <div id="page_3" role="link">3</div> + <div id="page_4" role="link" aria-current="">4</div> + <div id="current_foo" aria-current="foo">foo</div> +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_aria.xhtml b/accessible/tests/mochitest/states/test_aria.xhtml new file mode 100644 index 0000000000..e42a0ea96d --- /dev/null +++ b/accessible/tests/mochitest/states/test_aria.xhtml @@ -0,0 +1,70 @@ +<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="Accessible XUL ARIA state tests">
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <script type="application/javascript"
+ src="../common.js" />
+ <script type="application/javascript"
+ src="../role.js" />
+ <script type="application/javascript"
+ src="../states.js" />
+
+ <script type="application/javascript">
+ <![CDATA[
+ function doTest()
+ {
+ // aria-pressed
+ testStates("pressed_button", STATE_PRESSED, 0, STATE_CHECKABLE);
+
+ testStates("tabs", STATE_MULTISELECTABLE);
+ // Make sure XUL selection works, since aria-selected defaults to false.
+ testStates("tab1", STATE_SELECTED);
+ // aria-selected="true".
+ testStates("tab2", STATE_SELECTED);
+ // Neither.
+ testStates("tab3", 0, 0, STATE_SELECTED);
+
+ SimpleTest.finish()
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addA11yLoadEvent(doTest);
+ ]]>
+ </script>
+
+ <hbox flex="1" style="overflow: auto;">
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=1033283"
+ title="Expose pressed state on XUL menu toggle buttons">
+ Mozilla Bug 1033283
+ </a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <pre id="test">
+ </pre>
+ </body>
+
+ <vbox flex="1">
+ <button id="pressed_button" aria-pressed="true" label="I am pressed" />
+
+ <tabbox>
+ <tabs id="tabs" aria-multiselectable="true">
+ <tab id="tab1" label="tab1" selected="true"/>
+ <tab id="tab2" label="tab2" aria-selected="true"/>
+ <tab id="tab3" label="tab3"/>
+ </tabs>
+ </tabbox>
+ </vbox>
+ </hbox>
+
+</window>
+
diff --git a/accessible/tests/mochitest/states/test_aria_imgmap.html b/accessible/tests/mochitest/states/test_aria_imgmap.html new file mode 100644 index 0000000000..387e33259e --- /dev/null +++ b/accessible/tests/mochitest/states/test_aria_imgmap.html @@ -0,0 +1,75 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test usemap elements and ARIA</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + // gA11yEventDumpToConsole = true; + function doPreTest() { + waitForImageMap("imagemap", doTest); + } + + function doTest() { + var imageMap = getAccessible("imagemap"); + + var t1 = imageMap.getChildAt(0); + testStates(t1, 0, EXT_STATE_EDITABLE, STATE_LINKED); + var t2 = imageMap.getChildAt(1); + testStates(t2, 0, EXT_STATE_EDITABLE, STATE_LINKED); + var rb1 = imageMap.getChildAt(2); + testStates(rb1, (STATE_CHECKABLE | STATE_CHECKED), 0, STATE_LINKED); + var rb2 = imageMap.getChildAt(3); + testStates(rb2, STATE_CHECKABLE, 0, STATE_CHECKED, STATE_LINKED); + var cb1 = imageMap.getChildAt(4); + testStates(cb1, (STATE_CHECKABLE | STATE_CHECKED), 0, STATE_LINKED); + var cbox = imageMap.getChildAt(5); + testStates(cbox, (STATE_HASPOPUP | STATE_COLLAPSED), + EXT_STATE_EXPANDABLE, STATE_LINKED); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doPreTest); + </script> + +</head> +<body> + +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=548291" + title="ARIA states on image maps"> +Mozilla Bug 548291 +</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +</pre> + +<img id="imagemap" src="../formimage.png" width="219" height="229" border="0" usemap="#ariaMap"> +<map id="ariaMap" name="ariaMap"> + <area id="t1" role="textbox" shape="rect" tabindex="0" alt="" title="first name" coords="4,20,108,48" href="#" /> + <area id="t2" role="textbox" shape="rect" alt="" title="last name" coords="111,21,215,50" href="#" /> + <area id="rb1" role="radio" aria-checked="true" shape="circle" alt="" title="male" coords="60,75,11" href="#" /> + <area id="rb2" role="radio" shape="circle" alt="" title="female" coords="73,94,11" href="#" /> + <area id="cb1" role="checkbox" aria-checked="true" shape="rect" alt="" title="have bike" coords="95,123,118,145" href="#" /> + <area id="cbox" role="combobox" shape="rect" alt="" title="bike model" coords="120,124,184,146" href="#" /> + <area id="cb2" role="checkbox" shape="rect" alt="" title="have car" coords="90,145,114,164" href="#" /> + <area id="cb3" role="checkbox" shape="rect" alt="" title="have airplane" coords="130,163,152,184" href="#" /> + <area id="b1" role="button" shape="rect" alt="" title="submit" coords="4,198,67,224" href="#" /> +</map> +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_aria_widgetitems.html b/accessible/tests/mochitest/states/test_aria_widgetitems.html new file mode 100644 index 0000000000..c2d546ba01 --- /dev/null +++ b/accessible/tests/mochitest/states/test_aria_widgetitems.html @@ -0,0 +1,152 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test ARIA tab accessible selected state</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function focusARIAItem(aID, aIsSelected) { + this.DOMNode = getNode(aID); + + this.invoke = function focusARIAItem_invoke() { + this.DOMNode.focus(); + }; + + this.check = function focusARIAItem_check(aEvent) { + testStates(this.DOMNode, aIsSelected ? STATE_SELECTED : 0, 0, + aIsSelected ? 0 : STATE_SELECTED); + }; + + this.getID = function focusARIAItem_getID() { + return "Focused ARIA widget item with aria-selected='" + + (aIsSelected ? "true', should" : "false', shouldn't") + + " have selected state on " + prettyName(aID); + }; + } + + function focusActiveDescendantItem(aItemID, aWidgetID, aIsSelected) { + this.DOMNode = getNode(aItemID); + this.widgetDOMNode = getNode(aWidgetID); + + this.invoke = function focusActiveDescendantItem_invoke() { + this.widgetDOMNode.setAttribute("aria-activedescendant", aItemID); + this.widgetDOMNode.focus(); + }; + + this.check = function focusActiveDescendantItem_check(aEvent) { + testStates(this.DOMNode, aIsSelected ? STATE_SELECTED : 0, 0, + aIsSelected ? 0 : STATE_SELECTED); + }; + + this.getID = function tabActiveDescendant_getID() { + return "ARIA widget item managed by activedescendant " + + (aIsSelected ? "should" : "shouldn't") + + " have the selected state on " + prettyName(aItemID); + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + // aria-selected + testStates("aria_tab1", 0, 0, STATE_SELECTED); + testStates("aria_tab2", STATE_SELECTED); + testStates("aria_tab3", 0, 0, STATE_SELECTED); + testStates("aria_option1", 0, 0, STATE_SELECTED); + testStates("aria_option2", STATE_SELECTED); + testStates("aria_option3", 0, 0, STATE_SELECTED); + testStates("aria_treeitem1", 0, 0, STATE_SELECTED); + testStates("aria_treeitem2", STATE_SELECTED); + testStates("aria_treeitem3", 0, 0, STATE_SELECTED); + + // selected state when widget item is focused + gQueue = new eventQueue(EVENT_FOCUS); + + gQueue.push(new focusARIAItem("aria_tab1", true)); + gQueue.push(new focusARIAItem("aria_tab2", true)); + gQueue.push(new focusARIAItem("aria_tab3", false)); + gQueue.push(new focusARIAItem("aria_option1", true)); + gQueue.push(new focusARIAItem("aria_option2", true)); + gQueue.push(new focusARIAItem("aria_option3", false)); + gQueue.push(new focusARIAItem("aria_treeitem1", true)); + gQueue.push(new focusARIAItem("aria_treeitem2", true)); + gQueue.push(new focusARIAItem("aria_treeitem3", false)); + + // selected state when widget item is focused (by aria-activedescendant) + gQueue.push(new focusActiveDescendantItem("aria_tab5", "aria_tablist2", true)); + gQueue.push(new focusActiveDescendantItem("aria_tab6", "aria_tablist2", true)); + gQueue.push(new focusActiveDescendantItem("aria_tab4", "aria_tablist2", false)); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=653601" + title="aria-selected ignored for ARIA tabs"> + Mozilla Bug 653601 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=526703" + title="Focused widget item should expose selected state by default"> + Mozilla Bug 526703 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- tab --> + <div id="aria_tablist" role="tablist"> + <div id="aria_tab1" role="tab" tabindex="0">unselected tab</div> + <div id="aria_tab2" role="tab" tabindex="0" aria-selected="true">selected tab</div> + <div id="aria_tab3" role="tab" tabindex="0" aria-selected="false">focused explicitly unselected tab</div> + </div> + + <!-- listbox --> + <div id="aria_listbox" role="listbox"> + <div id="aria_option1" role="option" tabindex="0">unselected option</div> + <div id="aria_option2" role="option" tabindex="0" aria-selected="true">selected option</div> + <div id="aria_option3" role="option" tabindex="0" aria-selected="false">focused explicitly unselected option</div> + </div> + + <!-- tree --> + <div id="aria_tree" role="tree"> + <div id="aria_treeitem1" role="treeitem" tabindex="0">unselected treeitem</div> + <div id="aria_treeitem2" role="treeitem" tabindex="0" aria-selected="true">selected treeitem</div> + <div id="aria_treeitem3" role="treeitem" tabindex="0" aria-selected="false">focused explicitly unselected treeitem</div> + </div> + + <!-- tab managed by active-descendant --> + <div id="aria_tablist2" role="tablist" tabindex="0"> + <div id="aria_tab4" role="tab" aria-selected="false">focused explicitly unselected tab</div> + <div id="aria_tab5" role="tab">initially selected tab</div> + <div id="aria_tab6" role="tab">later selected tab</div> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_buttons.html b/accessible/tests/mochitest/states/test_buttons.html new file mode 100644 index 0000000000..ec6e65cf32 --- /dev/null +++ b/accessible/tests/mochitest/states/test_buttons.html @@ -0,0 +1,83 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML button accessible states</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + function doTest() { + // Default state. + testStates("f1_image", STATE_DEFAULT | STATE_FOCUSABLE); + testStates("f2_submit", STATE_DEFAULT | STATE_FOCUSABLE); + testStates("f3_submitbutton", STATE_DEFAULT | STATE_FOCUSABLE); + testStates("f3_disabled_reset", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE, 0); + testStates("f4_button", STATE_FOCUSABLE, 0, STATE_DEFAULT); + testStates("f4_disabled_button", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE, 0); + testStates("f4_image1", STATE_DEFAULT | STATE_FOCUSABLE); + testStates("f4_image2", STATE_FOCUSABLE, 0, STATE_DEFAULT); + testStates("f4_submit", STATE_FOCUSABLE, 0, STATE_DEFAULT); + testStates("f4_submitbutton", STATE_FOCUSABLE, 0, STATE_DEFAULT); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=664142" + title="DEFAULT state exposed incorrectly for HTML"> + Mozilla Bug 664142 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <p>A form with an image button</p> + <form name="form1" method="get"> + <input type="text" name="hi"> + + <input id="f1_image" type="image" value="image-button"> + </form> + + <p>A form with a submit button:</p> + <form name="form2" method="get"> + <input type="text" name="hi"> + <input id="f2_submit" type="submit"> + </form> + + <p>A form with a HTML4 submit button:</p> + <form name="form3" method="get"> + <input type="text" name="hi"> + <button id="f3_submitbutton" type="submit">submit</button> + <button id="f3_disabled_reset" type="reset" disabled>reset</button> + </form> + + <p>A form with normal button, two image buttons, submit button, + HTML4 submit button:</p> + <form name="form4" method="get"> + <input type="text" name="hi"> + <input id="f4_button" type="button" value="normal" name="normal-button"> + <input id="f4_disabled_button" type="button" value="disabled" name="disabled-button" disabled> + <input id="f4_image1" type="image" value="image-button1" name="image-button1"> + <input id="f4_image2" type="image" value="image-button2" name="image-button2"> + <input id="f4_submit" type="submit" value="real-submit" name="real-submit"> + <button id="f4_submitbutton" type="submit">submit</button> + </form> + + </body> +</html> diff --git a/accessible/tests/mochitest/states/test_controls.html b/accessible/tests/mochitest/states/test_controls.html new file mode 100644 index 0000000000..27aecf4c52 --- /dev/null +++ b/accessible/tests/mochitest/states/test_controls.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML control states</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + function doTest() { + // Undetermined progressbar (no value or aria-value attribute): mixed state + testStates("progress", STATE_MIXED); + // Determined progressbar (has value): shouldn't have mixed state + testStates("progress2", 0, 0, STATE_MIXED); + // Determined progressbar (has aria-value): shouldn't have mixed state + // testStates("progress3", 0, 0, STATE_MIXED); + todo(false, "we should respect ARIA"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=670853" + title="Bug 670853 - undetermined progressmeters should expose mixed state"> + Mozilla Bug 670853 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <progress id="progress"></progress> + <progress id="progress2" value="1"></progress> + <progress id="progress3" aria-valuenow="1"></progress> + +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_controls.xhtml b/accessible/tests/mochitest/states/test_controls.xhtml new file mode 100644 index 0000000000..a1997aef6e --- /dev/null +++ b/accessible/tests/mochitest/states/test_controls.xhtml @@ -0,0 +1,153 @@ +<?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="Accessible XUL input control state tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + var gQueue = null; + function doTest() + { + testStates("checkbox", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE); + testStates("checkbox2", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE); + testStates("radiogroup", 0, 0, STATE_FOCUSABLE | STATE_UNAVAILABLE); + testStates("radio", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE); + testStates("radio-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE); + testStates("radiogroup-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE); + testStates("radio-disabledradiogroup", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE); + testStates("button", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE); + testStates("button-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE); + testStates("checkbutton", + STATE_FOCUSABLE | STATE_PRESSED, 0, STATE_CHECKABLE); + testStates("fakecheckbutton", STATE_FOCUSABLE | STATE_PRESSED, 0, + STATE_CHECKABLE); + testStates("combobox", STATE_FOCUSABLE | STATE_HASPOPUP, 0, STATE_UNAVAILABLE); + testStates("combobox-disabled", STATE_UNAVAILABLE | STATE_HASPOPUP, 0, STATE_FOCUSABLE); + testStates("listbox", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE); + testStates("listitem", STATE_FOCUSABLE | STATE_SELECTABLE | STATE_SELECTED, 0, STATE_UNAVAILABLE); + testStates("listbox-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE); + testStates("listitem-disabledlistbox", STATE_UNAVAILABLE | STATE_SELECTED, 0, STATE_FOCUSABLE | STATE_SELECTABLE); + testStates("menubar", 0, 0, STATE_FOCUSABLE); + testStates("menu", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE); + testStates("menu-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE); + testStates("tab", STATE_FOCUSABLE | STATE_SELECTABLE | STATE_SELECTED, 0, STATE_UNAVAILABLE); + testStates("tab-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE | STATE_SELECTED); + + gQueue = new eventQueue(); + gQueue.invoke(); // Will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=599163" + title="check disabled state instead of attribute"> + Mozilla Bug 599163 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=756983" + title="Isolate focusable and unavailable states from State()"> + Mozilla Bug 756983 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <checkbox id="checkbox" checked="true" label="Steak"/> + <checkbox id="checkbox2" checked="true" label="Salad" disabled="true"/> + + <radiogroup id="radiogroup"> + <radio id="radio" label="Orange"/> + <radio id="radio-disabled" selected="true" label="Violet" disabled="true"/> + </radiogroup> + + <radiogroup id="radiogroup-disabled" disabled="true"> + <radio id="radio-disabledradiogroup" label="Orange"/> + <radio id="violet2" selected="true" label="Violet"/> + </radiogroup> + + <button id="button" value="button"/> + <button id="button-disabled" disabled="true" value="button"/> + <button id="checkbutton" type="checkbox" checked="true" + value="checkbutton" /> + <button id="fakecheckbutton" checked="true" value="fakecheckbutton" /> + + <menulist id="combobox"> + <menupopup> + <menuitem label="item1"/> + </menupopup> + </menulist> + + <menulist id="combobox-disabled" disabled="true"> + <menupopup> + <menuitem label="item1"/> + </menupopup> + </menulist> + + <richlistbox id="listbox"> + <richlistitem id="listitem"> + <label value="list item"/> + </richlistitem> + </richlistbox> + + <richlistbox id="listbox-disabled" disabled="true"> + <richlistitem id="listitem-disabledlistbox"> + <label value="list item"/> + </richlistitem> + </richlistbox> + + <toolbox> + <menubar id="menubar"> + <menu id="menu" label="menu1"> + <menupopup> + <menuitem id="menu1-item1" label="menuitem1.1"/> + </menupopup> + </menu> + <menu id="menu-disabled" label="menu2" disabled="true"> + <menupopup> + <menuitem id="menu-disabled-item1" label="menuitem2.1"/> + </menupopup> + </menu> + </menubar> + </toolbox> + + <tabbox> + <tabs> + <tab id="tab" label="tab1" tooltip="tooltip"/> + <tab id="tab-disabled" label="tab1" disabled="true"/> + </tabs> + <tabpanels> + <tabpanel/> + <tabpanel/> + </tabpanels> + </tabbox> + + <tooltip id="tooltip"><description>tooltip</description></tooltip> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/states/test_doc.html b/accessible/tests/mochitest/states/test_doc.html new file mode 100644 index 0000000000..a00ca957b9 --- /dev/null +++ b/accessible/tests/mochitest/states/test_doc.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<html> +<head> + <title>states of document</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + function doTest() { + // Bug 566542: root accesible should expose active state when focused. + testStates(getRootAccessible(), 0, EXT_STATE_ACTIVE); + + // Bug 509696, 607219. + testStates(document, STATE_READONLY, 0); // role="" + + document.body.setAttribute("role", "application"); + testStates(document, 0, 0, STATE_READONLY); + document.body.setAttribute("role", "foo"); // bogus role + testStates(document, STATE_READONLY); + document.body.removeAttribute("role"); + testStates(document, STATE_READONLY); + + // Bugs 454997 and 467387 + testStates(document, STATE_READONLY); + testStates("document", STATE_READONLY); + testStates("editable_document", 0, EXT_STATE_EDITABLE, STATE_READONLY); + + testStates("p", 0, EXT_STATE_SELECTABLE_TEXT, 0, EXT_STATE_EDITABLE); + testStates("unselectable_p", 0, 0, 0, EXT_STATE_SELECTABLE_TEXT | EXT_STATE_EDITABLE); + testStates("unselectable_link", 0, 0, 0, EXT_STATE_SELECTABLE_TEXT | EXT_STATE_EDITABLE); + + document.designMode = "on"; + + testStates(document, 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("p", 0, EXT_STATE_EDITABLE | EXT_STATE_SELECTABLE_TEXT, STATE_READONLY); + testStates("document", 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("editable_document", 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("unselectable_p", 0, EXT_STATE_SELECTABLE_TEXT | EXT_STATE_EDITABLE); + testStates("unselectable_link", 0, EXT_STATE_SELECTABLE_TEXT | EXT_STATE_EDITABLE); + + document.designMode = "off"; + + testStates(document, STATE_READONLY); + testStates("document", STATE_READONLY); + testStates("editable_document", 0, EXT_STATE_EDITABLE, STATE_READONLY); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body role=""> + + <a target="_blank" + title="<body contenteditable='true'> exposed incorrectly" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=454997">Mozilla Bug 454997</a> + <a target="_blank" + title="nsIAccessible states tests of editable document" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=467387">Mozilla Bug 467387</a> + <a target="_blank" + title="Role attribute on body with empty string causes DocAccessible not to have read-only state." + href="https://bugzilla.mozilla.org/show_bug.cgi?id=509696">Mozilla Bug 509696</a> + <a target="_blank" + title="Frame for firefox does not implement the state "active" when firefox is the active frame" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=566542">Mozilla Bug 566542</a> + <a target="_blank" + title="Dynamic role attribute change on body doesn't affect on document role" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=607219">Mozilla Bug 607219</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <p id="p">hello</p> + + <p id="unselectable_p" style="user-select: none;">unselectable <a id="unselectable_link" href="#">link</a></p> + + <div id="document" role="document">document</div> + <div id="editable_document" role="document" contentEditable="true">editable document</doc> + +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_doc_busy.html b/accessible/tests/mochitest/states/test_doc_busy.html new file mode 100644 index 0000000000..35b6d0542c --- /dev/null +++ b/accessible/tests/mochitest/states/test_doc_busy.html @@ -0,0 +1,130 @@ +<!DOCTYPE html> +<html> +<head> + <title>states of document</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + const { BrowserTestUtils } = ChromeUtils.import( + "resource://testing-common/BrowserTestUtils.jsm"); + const { Downloads } = ChromeUtils.import( + "resource://gre/modules/Downloads.jsm"); + + function matchDocBusyChange(isBusy) { + return function(event) { + const scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent); + return ( + event.DOMNode == document && + scEvent.state === STATE_BUSY && + scEvent.isEnabled === isBusy + ); + }; + } + + function getDownloadStartedPromise() { + return Downloads.getList(Downloads.PUBLIC).then(list => { + return new Promise(resolve => { + list.addView({ + onDownloadAdded(download) { + resolve(download); + } + }) + }); + }); + } + + async function downloadHandled(downloadResult) { + if (Window.isInstance(downloadResult)) { + return BrowserTestUtils.closeWindow(downloadResult); + } + // downloadResult is a download object. + await downloadResult.finalize(true); + return Downloads.getList(Downloads.PUBLIC).then(list => list.remove(downloadResult)); + } + + async function doTest() { + // Because of variable timing, there are two acceptable possibilities: + // 1. We get an event for busy and an event for not busy. + // 2. The two events get coalesced, so no events are fired. + // However, we fail this test if we get the first event but not the + // second. + let gotBusy = false; + let gotNotBusy = false; + const busyEvents = async function() { + await waitForEvent(EVENT_STATE_CHANGE, matchDocBusyChange(true)); + info("Got busy event"); + gotBusy = true; + await waitForEvent(EVENT_STATE_CHANGE, matchDocBusyChange(false)); + info("Got not-busy event"); + gotNotBusy = true; + }(); + + const downloadStarted = getDownloadStartedPromise(); + + info("Clicking link to trigger download"); + synthesizeMouse(getNode("link"), 1, 1, {}); + info("Waiting for download prompt to open"); + const downloadResult = await downloadStarted; + + // Once we no longer show a dialog for downloads, the not busy event + // might show up a bit later. + if (gotBusy && !gotNotBusy) { + await busyEvents; + } + + // Any busy events should have been fired by the time the download + // prompt has opened. + if (gotBusy && gotNotBusy) { + ok(true, "Got both busy change and not-busy change"); + } else if (!gotBusy && !gotNotBusy) { + ok(true, "No busy events, coalesced"); + } else { + ok(false, "Got busy change but didn't get not-busy change!"); + } + testStates(document, 0, 0, STATE_BUSY, 0, "Document not busy"); + + // Clean up. + info("Closing download prompt"); + await downloadHandled(downloadResult); + // We might still be waiting on busy events. Remove any pending observers. + for (let observer of Services.obs.enumerateObservers( + "accessible-event") + ) { + Services.obs.removeObserver(observer, "accessible-event"); + } + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + + <a target="_blank" + title="Missing busy state change event when downloading files" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=446469">Bug 446469</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <a id="link" href="http://example.com/a11y/accessible/tests/mochitest/dumbfile.zip">a file</a> +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_docarticle.html b/accessible/tests/mochitest/states/test_docarticle.html new file mode 100644 index 0000000000..8e45d5ebd6 --- /dev/null +++ b/accessible/tests/mochitest/states/test_docarticle.html @@ -0,0 +1,78 @@ +<html> +<head> + <title>states of document article</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + function doTest() { + var docAcc = getAccessible(document, [nsIAccessibleDocument]); + if (docAcc) { + testStates(docAcc, STATE_READONLY); + testStates("aria_article", STATE_READONLY); + testStates("editable_aria_article", 0, EXT_STATE_EDITABLE, + STATE_READONLY); + testStates("article", STATE_READONLY); + testStates("editable_article", 0, EXT_STATE_EDITABLE, STATE_READONLY); + + document.designMode = "on"; + + testStates(docAcc, 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("editable_aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("article", 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("editable_article", 0, EXT_STATE_EDITABLE, STATE_READONLY); + + document.designMode = "off"; + + testStates(docAcc, STATE_READONLY); + testStates("aria_article", STATE_READONLY); + testStates("editable_aria_article", 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("article", STATE_READONLY); + testStates("editable_article", 0, EXT_STATE_EDITABLE, STATE_READONLY); + } + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body role="article"> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=467387" + title="Expose non-editable documents as readonly, regardless of role"> + Mozilla Bug 467387 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=613502" + title="Map <article> like we do aria role article"> + Mozilla Bug 613502 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="aria_article" role="article">aria article</div> + <div id="editable_aria_article" role="article" contentEditable="true"> + editable aria article</div> + + <article id="article">article</article> + <article id="editable_article" contentEditable="true"> + editable article</article> + +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_editablebody.html b/accessible/tests/mochitest/states/test_editablebody.html new file mode 100644 index 0000000000..27b7201a2b --- /dev/null +++ b/accessible/tests/mochitest/states/test_editablebody.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=454997 +--> +<head> + <title>nsIAccessible states tests of contenteditable body</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + function doTest() { + testStates(document, 0, EXT_STATE_EDITABLE); + testStates("p", 0, EXT_STATE_EDITABLE); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body contentEditable="true"> + + <a target="_blank" + title="nsIAccessible states tests of contenteditable body" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=454997">Mozilla Bug 454997</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <p id="p">hello</p> +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_expandable.xhtml b/accessible/tests/mochitest/states/test_expandable.xhtml new file mode 100644 index 0000000000..0d969bef5b --- /dev/null +++ b/accessible/tests/mochitest/states/test_expandable.xhtml @@ -0,0 +1,112 @@ +<?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"?> + +<!-- Firefox searchbar --> +<?xml-stylesheet href="chrome://browser/content/browser.css" + type="text/css"?> +<!-- SeaMonkey searchbar --> +<?xml-stylesheet href="chrome://navigator/content/navigator.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Expanded state change events tests for comboboxes and autocompletes."> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" /> + + <script type="application/javascript" + src="../autocomplete.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //gA11yEventDumpToConsole = true; // debuggin + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new openCombobox("menulist")); + gQueue.push(new closeCombobox("menulist")); + + todo(false, "Autocompletes don't fire expanded state change events when popup open. See bug 688480!"); + //gQueue.push(new openCombobox("autocomplete")); + //gQueue.push(new closeCombobox("autocomplete")); + + // XXX: searchbar doesn't fire state change events because accessible + // parent of combobox_list accessible is pushbutton accessible. + //var searchbar = document.getElementById("searchbar"); + //gQueue.push(new openHideCombobox(searchbar, true)); + //gQueue.push(new openHideCombobox(searchbar, false)); + todo(false, "Enable states test for XUL searchbar widget!"); + + gQueue.onFinish = function() + { + // unregister 'test-a11y-search' autocomplete search + shutdownAutoComplete(); + } + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + // This is the hacks needed to use a searchbar without browser.js. + var BrowserSearch = { + updateOpenSearchBadge() {} + }; + + SimpleTest.waitForExplicitFinish(); + + // Register 'test-a11y-search' autocomplete search. + // XPFE AutoComplete needs to register early. + initAutoComplete([ "hello", "hi" ], + [ "Beep beep'm beep beep yeah", "Baby you can drive my car" ]); + + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox style="overflow: auto;" flex="1"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=467057" + title="xul menulist doesn't fire expand/collapse state change events"> + Mozilla Bug 467057 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <menulist id="menulist"> + <menupopup> + <menuitem label="item1"/> + <menuitem label="item2"/> + <menuitem label="item3"/> + </menupopup> + </menulist> + + <html:input is="autocomplete-input" + id="autocomplete" + autocompletesearch="test-a11y-search"/> + + <searchbar id="searchbar"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/states/test_frames.html b/accessible/tests/mochitest/states/test_frames.html new file mode 100644 index 0000000000..baac222d83 --- /dev/null +++ b/accessible/tests/mochitest/states/test_frames.html @@ -0,0 +1,93 @@ +<html> + +<head> + <title>frame based document testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + if (navigator.platform.startsWith("Win")) { + SimpleTest.expectAssertions(0, 2); + } + + function doTest() { + const frameDoc = document.getElementById("frame_doc").contentDocument; + const frameDocArticle = document.getElementById("frame_doc_article").contentDocument; + const frameDocCheckbox = document.getElementById("frame_doc_checkbox").contentDocument; + const frameDocTextbox = document.getElementById("frame_doc_textbox").contentDocument; + + testStates(frameDoc, STATE_READONLY, 0, 0, 0, + "test1: frameDoc"); + testStates(frameDocArticle, STATE_READONLY, 0, 0, 0, + "test1: frameDocArticle"); + testStates(frameDocCheckbox, STATE_READONLY, 0, 0, 0, + "test1: frameDocCheckbox"); + testStates(frameDocTextbox, STATE_READONLY, 0, 0, 0, + "test1: frameDocTextbox"); + frameDoc.designMode = "on"; + testStates(frameDoc, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0, + "test2: frameDoc"); + testStates(frameDocArticle, STATE_READONLY, 0, 0, 0, + "test2: frameDocArticle"); + testStates(frameDocCheckbox, STATE_READONLY, 0, 0, 0, + "test2: frameDocCheckbox"); + testStates(frameDocTextbox, STATE_READONLY, 0, 0, 0, + "test2: frameDocTextbox"); + + frameDocArticle.designMode = "on"; + testStates(frameDocArticle, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0, + "test3: frameDocArticle"); + + frameDocCheckbox.designMode = "on"; + testStates(frameDocCheckbox, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0, + "test4: frameDocCheckbox"); + + // Replace iframe document body before the document accessible tree is + // created. Check the states are updated for new body. + var frameUpdateDoc = + document.getElementById("frame_updatedoc").contentDocument; + testStates(frameUpdateDoc, 0, EXT_STATE_EDITABLE, + STATE_READONLY, EXT_STATE_STALE, "test5: frameUpdateDoc"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=467387" + title="Expose non-editable documents as readonly, regardless of role"> + Mozilla Bug 467387 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=638106" + title="CKEditor document should be editable"> + Mozilla Bug 638106 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <iframe id="frame_doc" src="z_frames.html"></iframe> + <iframe id="frame_doc_article" src="z_frames_article.html"></iframe> + <iframe id="frame_doc_checkbox" src="z_frames_checkbox.html"></iframe> + <iframe id="frame_doc_textbox" src="z_frames_textbox.html"></iframe> + <iframe id="frame_updatedoc" src="z_frames_update.html"></iframe> +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_inputs.html b/accessible/tests/mochitest/states/test_inputs.html new file mode 100644 index 0000000000..d9b9014a34 --- /dev/null +++ b/accessible/tests/mochitest/states/test_inputs.html @@ -0,0 +1,268 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML input states</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + function doTest() { + // ////////////////////////////////////////////////////////////////////////// + // 'editable' and 'multiline' states. + testStates("input", 0, EXT_STATE_EDITABLE, 0, EXT_STATE_MULTI_LINE); + testStates("textarea", 0, EXT_STATE_EDITABLE | EXT_STATE_MULTI_LINE); + + testStates("input_readonly", 0, EXT_STATE_EDITABLE); + testStates("input_disabled", 0, EXT_STATE_EDITABLE); + testStates("textarea_readonly", 0, EXT_STATE_EDITABLE); + testStates("textarea_disabled", 0, EXT_STATE_EDITABLE); + + // ////////////////////////////////////////////////////////////////////////// + // 'required', 'readonly' and 'unavailable' states. + var maybe_required = ["input", "search", "radio", "checkbox", "textarea"]; + var never_required = ["submit", "button", "reset", "image"]; + + var i; + for (i in maybe_required) { + testStates(maybe_required[i], + STATE_FOCUSABLE, 0, + STATE_REQUIRED | STATE_READONLY | STATE_UNAVAILABLE); + + testStates(maybe_required[i] + "_required", + STATE_FOCUSABLE | STATE_REQUIRED, 0, + STATE_UNAVAILABLE | STATE_READONLY); + + var readonlyID = maybe_required[i] + "_readonly"; + if (document.getElementById(readonlyID)) { + testStates(readonlyID, + STATE_FOCUSABLE | STATE_READONLY, 0, + STATE_UNAVAILABLE | STATE_REQUIRED); + } + + testStates(maybe_required[i] + "_disabled", + STATE_UNAVAILABLE, 0, + STATE_FOCUSABLE | STATE_READONLY | STATE_REQUIRED); + } + + for (i in never_required) { + testStates(never_required[i], 0, 0, STATE_REQUIRED | EXT_STATE_EDITABLE); + } + + // ////////////////////////////////////////////////////////////////////////// + // inherited 'unavailable' state + testStates("f", STATE_UNAVAILABLE); + testStates("f_input", STATE_UNAVAILABLE); + testStates("f_input_disabled", STATE_UNAVAILABLE); + + // ////////////////////////////////////////////////////////////////////////// + // inherited from file control + var fileBrowseButton = getAccessible("file").firstChild; + testStates(fileBrowseButton, STATE_UNAVAILABLE | STATE_REQUIRED); + // No states on the label. + + // ////////////////////////////////////////////////////////////////////////// + // 'invalid' state + var invalid = ["pattern", "email", "url"]; + for (i in invalid) { + testStates(invalid[i], STATE_INVALID); + testStates(invalid[i] + "2", 0, 0, STATE_INVALID); + } + + // ////////////////////////////////////////////////////////////////////////// + // not 'invalid' state + // (per spec, min/maxlength are always valid until interactively edited) + var validInput = document.createElement("input"); + validInput.maxLength = "0"; + validInput.value = "a"; + ok(validInput.validity.valid, + "input should be valid despite maxlength (no interactive edits)"); + + var validInput2 = document.createElement("input"); + validInput2.minLength = "1"; + validInput2.value = ""; + ok(validInput2.validity.valid, + "input should be valid despite minlength (no interactive edits)"); + + var valid = ["minlength", "maxlength"]; + for (i in valid) { + testStates(valid[i], 0, 0, STATE_INVALID); + testStates(valid[i] + "2", 0, 0, STATE_INVALID); + } + + // ////////////////////////////////////////////////////////////////////////// + // 'invalid' state + // (per spec, min/maxlength validity is affected by interactive edits) + var mininp = document.getElementById("minlength"); + mininp.focus(); + mininp.setSelectionRange(mininp.value.length, mininp.value.length); + synthesizeKey("KEY_Backspace"); + ok(!mininp.validity.valid, + "input should be invalid after interactive edits"); + testStates(mininp, STATE_INVALID); + // inputs currently cannot be made longer than maxlength interactively, + // so we're not testing that case. + + // ////////////////////////////////////////////////////////////////////////// + // autocomplete states + testStates("autocomplete-default", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("autocomplete-off", 0, 0, 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("autocomplete-formoff", 0, 0, 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("autocomplete-list", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("autocomplete-list2", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("autocomplete-tel", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("autocomplete-email", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + testStates("autocomplete-search", 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION); + + // ////////////////////////////////////////////////////////////////////////// + // haspopup + testStates("autocomplete-list", STATE_HASPOPUP); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=559275" + title="map attribute required to STATE_REQUIRED"> + Bug 559275 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=389238" + title="Support disabled state on fieldset"> + Bug 389238 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=599163" + title="check disabled state instead of attribute"> + Bug 599163 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=601205" + title="Expose intrinsic invalid state to accessibility API"> + Bug 601205 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=601205" + title="Expose intrinsic invalid state to accessibility API"> + Bug 601205 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=559766" + title="Add accessibility support for @list on HTML input and for HTML datalist"> + Bug 559766 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=699017" + title="File input control should be propogate states to descendants"> + Bug 699017 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=733382" + title="Editable state bit should be present on readonly inputs"> + Bug 733382 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=878590" + title="HTML5 datalist is not conveyed by haspopup property"> + Bug 878590 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + + <form> + <input id="input" type="input"> + <input id="input_required" type="input" required> + <input id="input_readonly" type="input" readonly> + <input id="input_disabled" type="input" disabled> + <input id="search" type="search"> + <input id="search_required" type="search" required> + <input id="search_readonly" type="search" readonly> + <input id="search_disabled" type="search" disabled> + <input id="radio" type="radio"> + <input id="radio_required" type="radio" required> + <input id="radio_disabled" type="radio" disabled> + <input id="checkbox" type="checkbox"> + <input id="checkbox_required" type="checkbox" required> + <input id="checkbox_disabled" type="checkbox" disabled> + <textarea id="textarea"></textarea> + <textarea id="textarea_required" required></textarea> + <textarea id="textarea_readonly" readonly></textarea> + <textarea id="textarea_disabled" disabled></textarea> + </form> + + <!-- bogus required usage --> + <input id="submit" type="submit" required> + <input id="button" type="button" required> + <input id="reset" type="reset" required> + <input id="image" type="image" required> + + <!-- inherited disabled --> + <fieldset id="f" disabled> + <input id="f_input"> + <input id="f_input_disabled" disabled> + </fieldset> + + <!-- inherited from input@type="file" --> + <input id="file" type="file" required disabled> + + <!-- invalid/valid --> + <input id="maxlength" maxlength="1" value="f"> + <input id="maxlength2" maxlength="100" value="foo"> + <input id="minlength" minlength="2" value="fo"> + <input id="minlength2" minlength="1" value="foo"> + <input id="pattern" pattern="bar" value="foo"> + <input id="pattern2" pattern="bar" value="bar"> + <input id="email" type="email" value="foo"> + <input id="email2" type="email" value="foo@bar.com"> + <input id="url" type="url" value="foo"> + <input id="url2" type="url" value="http://mozilla.org/"> + + <!-- autocomplete --> + <input id="autocomplete-default"> + <input id="autocomplete-off" autocomplete="off"> + <form autocomplete="off"> + <input id="autocomplete-formoff"> + </form> + <datalist id="cities"> + <option>Paris</option> + <option>San Francisco</option> + </datalist> + <input id="autocomplete-list" list="cities"> + <input id="autocomplete-list2" list="cities" autocomplete="off"> + <input id="autocomplete-tel" type="tel"> + + Email Address: + <input id="autocomplete-email" type="email" list="contacts" value="xyzzy"> + <datalist id="contacts"> + <option>xyzzy@plughs.com</option> + <option>nobody@mozilla.org</option> + </datalist> + + </br>Search for: + <input id="autocomplete-search" type="search" list="searchhisty" value="Gamma"> + <datalist id="searchhisty"> + <option>Gamma Rays</option> + <option>Gamma Ray Bursts</option> + </datalist> + + </body> +</html> diff --git a/accessible/tests/mochitest/states/test_link.html b/accessible/tests/mochitest/states/test_link.html new file mode 100644 index 0000000000..65632bd12f --- /dev/null +++ b/accessible/tests/mochitest/states/test_link.html @@ -0,0 +1,85 @@ +<html> + +<head> + <title>HTML link states testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function doTest() { + // a@href and its text node + testStates("link_href", STATE_LINKED); + testStates(getAccessible("link_href").firstChild, STATE_LINKED); + + // a@onclick + testStates("link_click", STATE_LINKED); + + // a@onmousedown + testStates("link_mousedown", STATE_LINKED); + + // a@onmouseup + testStates("link_mouseup", STATE_LINKED); + + // a@role="link" + testStates("link_arialink", STATE_LINKED); + + // a@role="button" + testStates("link_ariabutton", 0, 0, STATE_LINKED); + + // a (no @href, no click event listener) + testStates("link_notlink", 0, 0, STATE_LINKED); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=423409" + title="Expose click action if mouseup and mousedown are registered"> + Mozilla Bug 423409 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=754830" + title="Calculate link states separately"> + Mozilla Bug 754830 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=757774" + title="Fire state change event when link is traversed"> + Mozilla Bug 757774 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <a id="link_href" href="http://mozilla.org">link</a> + <a id="link_click" onclick="">link</a> + <a id="link_mousedown" onmousedown="">link</a> + <a id="link_mouseup" onmouseup="">link</a> + <a id="link_arialink" role="link">aria link</a> + <a id="link_ariabutton" role="button">aria button</a> + <a id="link_notlink">not link</a> + +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_popup.xhtml b/accessible/tests/mochitest/states/test_popup.xhtml new file mode 100644 index 0000000000..c51e3ac17c --- /dev/null +++ b/accessible/tests/mochitest/states/test_popup.xhtml @@ -0,0 +1,54 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL popup attribute test"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + // label with popup + testStates("labelWithPopup", STATE_HASPOPUP); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=504252" + title="Expose STATE_HASPOPUP on XUL elements that have an @popup attribute"> + Mozilla Bug 504252 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <!-- label with popup attribute --> + <label id="labelWithPopup" value="file name" + popup="fileContext" + tabindex="0"/> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/states/test_selects.html b/accessible/tests/mochitest/states/test_selects.html new file mode 100644 index 0000000000..c822fe376f --- /dev/null +++ b/accessible/tests/mochitest/states/test_selects.html @@ -0,0 +1,166 @@ +<html> + +<head> + <title>HTML selects accessible states tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // gA11yEventDumpToConsole = true; + + function doTest() { + // combobox + var combobox = getAccessible("combobox"); + testStates(combobox, + STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSABLE, 0, + STATE_FOCUSED, 0); + + var comboboxList = combobox.firstChild; + testStates(comboboxList, STATE_INVISIBLE, 0, STATE_FOCUSABLE, 0); + + var opt1 = comboboxList.firstChild; + testStates(opt1, STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE, + 0, STATE_FOCUSED, 0); + + var opt2 = comboboxList.lastChild; + testStates(opt2, STATE_SELECTABLE | STATE_FOCUSABLE, 0, STATE_SELECTED, 0, + STATE_FOCUSED, 0); + + // collapsed combobox + testStates("collapsedcombobox", + STATE_COLLAPSED | STATE_FOCUSABLE, 0, + STATE_FOCUSED, 0); + + testStates("collapsed-1", + STATE_FOCUSABLE | STATE_SELECTABLE, 0, + STATE_OFFSCREEN | STATE_INVISIBLE, 0); + + testStates("collapsed-2", + STATE_OFFSCREEN, 0, + STATE_INVISIBLE, 0); + + // listbox + testStates("listbox", + STATE_FOCUSABLE, 0, + STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSED); + + testStates("listitem-active", + STATE_FOCUSABLE | STATE_SELECTABLE, EXT_STATE_ACTIVE, + STATE_SELECTED | STATE_FOCUSED); + + testStates("listitem", + STATE_FOCUSABLE | STATE_SELECTABLE, 0, + STATE_SELECTED | STATE_FOCUSED, EXT_STATE_ACTIVE); + + testStates("listitem-disabled", + STATE_UNAVAILABLE, 0, + STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE, + EXT_STATE_ACTIVE); + + testStates("listgroup", + 0, 0, + STATE_UNAVAILABLE | STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE, + EXT_STATE_ACTIVE); + + testStates("listgroup-disabled", + STATE_UNAVAILABLE, 0, + STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE, + EXT_STATE_ACTIVE); + + todo(false, "no unavailable state on option in disabled group (bug 759666)"); +// testStates("listitem-disabledgroup", +// STATE_UNAVAILABLE, 0, +// STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE, +// EXT_STATE_ACTIVE); + + testStates("listbox-disabled", + STATE_UNAVAILABLE, 0, + STATE_FOCUSABLE); + + todo(false, "no unavailable state on option in disabled select (bug 759666)"); +// testStates("listitem-disabledlistbox", +// STATE_UNAVAILABLE, 0, +// STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE, +// EXT_STATE_ACTIVE); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=443889" + title="mochitest for selects and lists"> + Mozilla Bug 443889 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=640716" + title="mochitest for selects and lists"> + Mozilla Bug 640716 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=689847" + title="Expose active state on current item of selectable widgets"> + Mozilla Bug 689847 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=756983" + title="Isolate focusable and unavailable states from State()"> + Mozilla Bug 756983 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=907682" + title=" HTML:option group position is not correct when select is collapsed"> + Mozilla Bug 907682 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select id="combobox"> + <option>item 1</option> + <option>item 2</option> + </select> + + <select id="collapsedcombobox"> + <option id="collapsed-1">item 1</option> + <option id="collapsed-2">item 2</option> + </select> + + <select id="listbox" name="component" size="3"> + <option id="listitem-active">Build</option> + <option id="listitem">Disability Access APIs</option> + <option id="listitem-disabled" disabled>General</option> + <optgroup id="listgroup" label="group"> + <option>option</option> + </optgroup> + <optgroup id="listgroup-disabled" disabled label="group2"> + <option id="listitem-disabledgroup">UI</option> + </optgroup> + </select> + + <select id="listbox-disabled" size="3" disabled> + <option id="listitem-disabledlistbox">option</option> + </select> + +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_stale.html b/accessible/tests/mochitest/states/test_stale.html new file mode 100644 index 0000000000..3218a2125a --- /dev/null +++ b/accessible/tests/mochitest/states/test_stale.html @@ -0,0 +1,108 @@ +<!DOCTYPE html> +<html> +<head> + <title>Stale state testing</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function addChild(aContainerID) { + this.containerNode = getNode(aContainerID); + this.childNode = null; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function addChild_invoke() { + this.childNode = document.createElement("div"); + // Note after bug 646216, a sole div without text won't be accessible + // and would not result in an embedded character. + // Therefore, add some text. + this.childNode.textContent = "hello"; + this.containerNode.appendChild(this.childNode); + }; + + this.finalCheck = function addChild_finalCheck() { + // no stale state should be set + testStates(this.childNode, 0, 0, 0, EXT_STATE_STALE); + }; + + this.getID = function addChild_getID() { + return "add child for " + prettyName(aContainerID); + }; + } + + function removeChildChecker(aInvoker) { + this.type = EVENT_HIDE; + this.__defineGetter__("target", function() { return aInvoker.child; }); + + this.check = function removeChildChecker_check() { + // stale state should be set + testStates(aInvoker.child, 0, EXT_STATE_STALE); + }; + } + + function removeChild(aContainerID) { + this.containerNode = getNode(aContainerID); + this.child = null; + + this.eventSeq = [ + new removeChildChecker(this), + ]; + + this.invoke = function removeChild_invoke() { + var childNode = this.containerNode.firstChild; + this.child = getAccessible(childNode); + + this.containerNode.removeChild(childNode); + }; + + this.getID = function removeChild_getID() { + return "remove child from " + prettyName(aContainerID); + }; + } + + // gA11yEventDumpToConsole = true; //debugging + + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + + gQueue.push(new addChild("container")); + gQueue.push(new removeChild("container")); + + gQueue.invoke(); // will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body role=""> + + <a target="_blank" + title="Expose stale state on accessibles unattached from tree" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=676267">Mozilla Bug 676267</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="container"></div> + +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_tabs.xhtml b/accessible/tests/mochitest/states/test_tabs.xhtml new file mode 100644 index 0000000000..4d8a83c97d --- /dev/null +++ b/accessible/tests/mochitest/states/test_tabs.xhtml @@ -0,0 +1,66 @@ +<?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="Accessible XUL tabbox hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + testStates("tab1", 0, EXT_STATE_PINNED); + testStates("tab2", 0, 0, 0, EXT_STATE_PINNED); + testStates("tab3", 0, 0, 0, EXT_STATE_PINNED); + + SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=577727" + title="Make pinned tabs distinguishable from other tabs for accessibility"> + Mozilla Bug 577727 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tabbox> + <tabs id="tabs"> + <tab id="tab1" label="tab1" pinned="true"/> + <tab id="tab2" label="tab2" pinned="false"/> + <tab id="tab3" label="tab3"/> + </tabs> + <tabpanels id="tabpanels"> + <tabpanel/> + <tabpanel/> + </tabpanels> + </tabbox> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/states/test_textbox.xhtml b/accessible/tests/mochitest/states/test_textbox.xhtml new file mode 100644 index 0000000000..eaf455d994 --- /dev/null +++ b/accessible/tests/mochitest/states/test_textbox.xhtml @@ -0,0 +1,78 @@ +<?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="nsIAccessible XUL textboxes states tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + + <script type="application/javascript"> + <![CDATA[ + function getInput(aID) + { + return getNode(aID).inputField; + } + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // Search textbox without search button, searches as you type and filters + // a separate control. + testStates(getInput("searchbox"), + STATE_FOCUSABLE, + EXT_STATE_EDITABLE | EXT_STATE_SUPPORTS_AUTOCOMPLETION, + STATE_PROTECTED | STATE_UNAVAILABLE, + 0, + "searchbox"); + + ////////////////////////////////////////////////////////////////////////// + // Search textbox with search button, does not support autoCompletion. + testStates(getInput("searchfield"), + STATE_FOCUSABLE, + EXT_STATE_EDITABLE, + STATE_PROTECTED | STATE_UNAVAILABLE, + EXT_STATE_SUPPORTS_AUTOCOMPLETION, + "searchfield"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=442648"> + Mozilla Bug 442648 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=648235" + title="XUL textbox can inherit more states from underlying HTML input"> + Mozilla Bug 648235 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <search-textbox id="searchbox" flex="1" results="historyTree"/> + <search-textbox id="searchfield" placeholder="Search all add-ons" + searchbutton="true"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/states/test_tree.xhtml b/accessible/tests/mochitest/states/test_tree.xhtml new file mode 100644 index 0000000000..d81be4a54b --- /dev/null +++ b/accessible/tests/mochitest/states/test_tree.xhtml @@ -0,0 +1,146 @@ +<?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"?> +<?xml-stylesheet href="../treeview.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL tree states tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + /** + * Event queue invoker object to test accessible states for XUL tree + * accessible. + */ + function statesChecker(aTreeID, aView) + { + this.DOMNode = getNode(aTreeID); + + this.invoke = function statesChecker_invoke() + { + this.DOMNode.view = aView; + } + + this.check = function statesChecker_check() + { + var tree = getAccessible(this.DOMNode); + + // tree states + testStates(tree, STATE_READONLY); + + if (this.DOMNode.getAttribute("seltype") != "single") + testStates(tree, STATE_MULTISELECTABLE); + else + testStates(tree, 0, 0, STATE_MULTISELECTABLE); + + // tree item states + var expandedItem = tree.getChildAt(2); + testStates(expandedItem, + STATE_SELECTABLE | STATE_FOCUSABLE | STATE_EXPANDED); + + var collapsedItem = tree.getChildAt(5); + testStates(collapsedItem, + STATE_SELECTABLE | STATE_FOCUSABLE | STATE_COLLAPSED); + + // cells states if any + var cells = collapsedItem.children; + if (cells && cells.length) { + for (var idx = 0; idx < cells.length; idx++) { + var cell = cells.queryElementAt(idx, nsIAccessible); + testStates(cell, STATE_SELECTABLE); + } + + var checkboxCell = cells.queryElementAt(3, nsIAccessible); + testStates(checkboxCell, STATE_CHECKABLE | STATE_CHECKED); + } + } + + this.getID = function statesChecker_getID() + { + return "tree processor for " + prettyName(aTreeID); + } + } + + gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + + function doTest() + { + gQueue = new eventQueue(EVENT_REORDER); + gQueue.push(new statesChecker("tree", new nsTreeTreeView())); + gQueue.push(new statesChecker("treesingle", new nsTreeTreeView())); + gQueue.push(new statesChecker("tabletree", new nsTreeTreeView())); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727" + title="Reorganize implementation of XUL tree accessibility"> + Mozilla Bug 503727 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + </treecols> + <treechildren/> + </tree> + + <tree id="treesingle" flex="1" seltype="single"> + <treecols> + <treecol id="col_single" flex="1" primary="true" label="column"/> + </treecols> + <treechildren/> + </tree> + + <tree id="tabletree" flex="1" editable="true"> + <treecols> + <treecol id="tabletree_col1" cycler="true" label="cycler"/> + <treecol id="tabletree_col2" flex="1" primary="true" label="column1"/> + <treecol id="tabletree_col3" flex="1" label="column2"/> + <treecol id="tabletree_col4" flex="1" label="checker" + type="checkbox" editable="true"/> + </treecols> + <treechildren/> + </tree> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/states/test_visibility.html b/accessible/tests/mochitest/states/test_visibility.html new file mode 100644 index 0000000000..aa3643673a --- /dev/null +++ b/accessible/tests/mochitest/states/test_visibility.html @@ -0,0 +1,75 @@ +<html> +<head> + <title>visibility state testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + // Tests + + function doTests() { + testStates("div", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN); + testStates("div_off", STATE_OFFSCREEN, 0, STATE_INVISIBLE); + testStates("div_transformed", STATE_OFFSCREEN, 0, STATE_INVISIBLE); + testStates("div_abschild", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN); + testStates("ul", STATE_OFFSCREEN, 0, STATE_INVISIBLE); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=591363" + title="(in)visible state is not always correct?"> + Mozilla Bug 591363 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=768786" + title="Offscreen state is not exposed under certain circumstances"> + Mozilla Bug 768786 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="outer_div"> + + <!-- trivial cases --> + <div id="div">div</div> + <div id="div_off" style="position: absolute; left:-999px; top:-999px"> + offscreen! + </div> + <div id="div_transformed" style="transform: translate(-999px, -999px);"> + transformed! + </div> + + <!-- edge case: no rect but has out of flow child --> + <div id="div_abschild"> + <p style="position: absolute; left: 120px; top:120px;">absolute</p> + </div> + + <ul id="ul" style="display: contents;"> + <li>Supermarket 1</li> + <li>Supermarket 2</li> + </ul> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_visibility.xhtml b/accessible/tests/mochitest/states/test_visibility.xhtml new file mode 100644 index 0000000000..cc23b6b4bc --- /dev/null +++ b/accessible/tests/mochitest/states/test_visibility.xhtml @@ -0,0 +1,162 @@ +<?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" + xmlns:html="http://www.w3.org/1999/xhtml" + title="XUL elements visibility states testing"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + function openMenu(aID, aSubID, aOffscreenSubID) + { + this.menuNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, this.menuNode) + ]; + + this.invoke = function openMenu_invoke() + { + this.menuNode.open = true; + } + + this.finalCheck = function openMenu_finalCheck() + { + testStates(aID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN); + testStates(aSubID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN); + if (aOffscreenSubID) + testStates(aOffscreenSubID, STATE_OFFSCREEN, 0, STATE_INVISIBLE); + } + + this.getID = function openMenu_invoke() + { + return "open menu '" + aID + "' and test states"; + } + } + + function closeMenu(aID, aSubID, aSub2ID) + { + this.menuNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, document) + ]; + + this.invoke = function openMenu_invoke() + { + this.menuNode.open = false; + } + + this.finalCheck = function openMenu_finalCheck() + { + testStates(aID, 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN); + testStates(aSubID, STATE_INVISIBLE, 0, STATE_OFFSCREEN); + testStates(aSub2ID, STATE_INVISIBLE, 0, STATE_OFFSCREEN); + } + + this.getID = function openMenu_invoke() + { + return "open menu and test states"; + } + } + + var gQueue = null; + function doTest() + { + testStates("deck_pane2", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN); + testStates("tabs_pane1", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN); + testStates("tabs_pane2", STATE_OFFSCREEN, 0, STATE_INVISIBLE); + + gQueue = new eventQueue(); + gQueue.push(new openMenu("mi_file1", "mi_file1.1")); + gQueue.push(new openMenu("mi_file1.2", "mi_file1.2.1", "mi_file1.2.4")); + gQueue.push(new closeMenu("mi_file1", "mi_file1.1", "mi_file1.2.1")); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + <html:style> + <![CDATA[ + /* We want to control the height of the menu and which elements are visible, + and the Windows menu padding interferes with this. */ + menupopup::part(arrowscrollbox) { + margin: 0 !important; + padding-block: 0 !important; + } + ]]> + </html:style> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=810260" + title="xul:deck hidden pages shouldn't be offscreen"> + Mozilla Bug 810260 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=865591" + title="Visible menu item have offscreen state"> + Mozilla Bug 865591 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <deck selectedIndex="1"> + <description value="This is the first page" id="deck_pane1"/> + <button label="This is the second page" id="deck_pane2"/> + </deck> + + <tabbox> + <tabs> + <tab>tab1</tab> + <tab>tab2</tab> + </tabs> + <tabpanels> + <description value="This is the first page" id="tabs_pane1"/> + <button label="This is the second page" id="tabs_pane2"/> + </tabpanels> + </tabbox> + + <menubar> + <menu label="File" id="mi_file1"> + <menupopup> + <menuitem label="SubFile" id="mi_file1.1"/> + <menu label="SubFile2" id="mi_file1.2"> + <menupopup style="max-height: 3em;"> + <menuitem style="appearance: none; height: 1em" label="SubSubFile" id="mi_file1.2.1"/> + <menuitem style="appearance: none; height: 1em" label="SubSubFile2" id="mi_file1.2.2"/> + <menuitem style="appearance: none; height: 1em" label="SubSubFile3" id="mi_file1.2.3"/> + <menuitem style="appearance: none; height: 1em" label="SubSubFile4" id="mi_file1.2.4"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menubar> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/states/z_frames.html b/accessible/tests/mochitest/states/z_frames.html new file mode 100644 index 0000000000..819adee63e --- /dev/null +++ b/accessible/tests/mochitest/states/z_frames.html @@ -0,0 +1,11 @@ +<html> +<!-- +Auxilliary file used as frame source. +--> +<head> +</head> +<body> +<p>Frame source body has no role</p> +</body> +</html> + diff --git a/accessible/tests/mochitest/states/z_frames_article.html b/accessible/tests/mochitest/states/z_frames_article.html new file mode 100644 index 0000000000..a7a69b4dae --- /dev/null +++ b/accessible/tests/mochitest/states/z_frames_article.html @@ -0,0 +1,11 @@ +<html> +<!-- +Auxilliary file used as frame source. +--> +<head> +</head> +<body role="article"> +<p>Article</p> +</body> +</html> + diff --git a/accessible/tests/mochitest/states/z_frames_checkbox.html b/accessible/tests/mochitest/states/z_frames_checkbox.html new file mode 100644 index 0000000000..7997644243 --- /dev/null +++ b/accessible/tests/mochitest/states/z_frames_checkbox.html @@ -0,0 +1,11 @@ +<html> +<!-- +Auxilliary file used as frame source. +--> +<head> +</head> +<body role="checkbox"> +<p>Checkbox</p> +</body> +</html> + diff --git a/accessible/tests/mochitest/states/z_frames_textbox.html b/accessible/tests/mochitest/states/z_frames_textbox.html new file mode 100644 index 0000000000..0f4e1b9d66 --- /dev/null +++ b/accessible/tests/mochitest/states/z_frames_textbox.html @@ -0,0 +1,11 @@ +<html> +<!-- +Auxilliary file used as frame source. +--> +<head> +</head> +<body role="textbox"> +<p>Texbox</p> +</body> +</html> + diff --git a/accessible/tests/mochitest/states/z_frames_update.html b/accessible/tests/mochitest/states/z_frames_update.html new file mode 100644 index 0000000000..7e2cc83539 --- /dev/null +++ b/accessible/tests/mochitest/states/z_frames_update.html @@ -0,0 +1,21 @@ +<html> +<head> +<script> +function replaceBody() { + var accService = Cc["@mozilla.org/accessibilityService;1"]. + getService(Ci.nsIAccessibilityService); + accService.getAccessibleFor(document); + + var newBody = document.createElement("body"); + newBody.setAttribute("contentEditable", "true"); + newBody.textContent = "New Hello"; + document.documentElement.replaceChild(newBody, document.body); + getComputedStyle(newBody, "").color; +} +</script> +</head> +<body onload="replaceBody();"> +OLD hello +</body> +</html> + diff --git a/accessible/tests/mochitest/table.js b/accessible/tests/mochitest/table.js new file mode 100644 index 0000000000..9e4989cced --- /dev/null +++ b/accessible/tests/mochitest/table.js @@ -0,0 +1,851 @@ +/** + * This file provides set of helper functions to test nsIAccessibleTable + * interface. + * + * Required: + * common.js + * role.js + * states.js + */ +/* import-globals-from common.js */ +/* import-globals-from role.js */ +/* import-globals-from states.js */ + +/** + * Constants used to describe cells array. + */ +const kDataCell = 1; // Indicates the cell is origin data cell +const kRowHeaderCell = 2; // Indicates the cell is row header cell +const kColHeaderCell = 4; // Indicated the cell is column header cell +const kOrigin = kDataCell | kRowHeaderCell | kColHeaderCell; + +const kRowSpanned = 8; // Indicates the cell is not origin and row spanned +const kColSpanned = 16; // Indicates the cell is not origin and column spanned +const kSpanned = kRowSpanned | kColSpanned; + +/** + * Constants to define column header type. + */ +const kNoColumnHeader = 0; +const kListboxColumnHeader = 1; +const kTreeColumnHeader = 2; + +/** + * Constants to define table type. + */ +const kTable = 0; +const kTreeTable = 1; +const kMathTable = 2; + +/** + * Test table structure and related methods. + * + * @param aIdentifier [in] table accessible identifier + * @param aCellsArray [in] two dimensional array (row X columns) of + * cell types (see constants defined above). + * @param aColHeaderType [in] specifies wether column header cells are + * arranged into the list. + * @param aCaption [in] caption text if any + * @param aSummary [in] summary text if any + * @param aTableType [in] specifies the table type. + * @param aRowRoles [in] array of row roles. + */ +function testTableStruct( + aIdentifier, + aCellsArray, + aColHeaderType, + aCaption, + aSummary, + aTableType, + aRowRoles +) { + var tableNode = getNode(aIdentifier); + var isGrid = + tableNode.getAttribute("role") == "grid" || + tableNode.getAttribute("role") == "treegrid" || + tableNode.localName == "tree"; + + var rowCount = aCellsArray.length; + var colsCount = aCellsArray[0] ? aCellsArray[0].length : 0; + + // Test table accessible tree. + var tableObj = { + children: [], + }; + switch (aTableType) { + case kTable: + tableObj.role = ROLE_TABLE; + break; + case kTreeTable: + tableObj.role = ROLE_TREE_TABLE; + break; + case kMathTable: + tableObj.role = ROLE_MATHML_TABLE; + break; + } + + // caption accessible handling + if (aCaption) { + var captionObj = { + role: ROLE_CAPTION, + children: [ + { + role: ROLE_TEXT_LEAF, + name: aCaption, + }, + ], + }; + + tableObj.children.push(captionObj); + } + + // special types of column headers handling + if (aColHeaderType) { + var headersObj = { + role: ROLE_LIST, + children: [], + }; + + for (let idx = 0; idx < colsCount; idx++) { + var headerCellObj = { + role: ROLE_COLUMNHEADER, + }; + headersObj.children.push(headerCellObj); + } + + if (aColHeaderType == kTreeColumnHeader) { + headersObj.children.push({ + role: ROLE_PUSHBUTTON, + }); + headersObj.children.push({ + role: ROLE_MENUPOPUP, + }); + } + + tableObj.children.push(headersObj); + } + + // rows and cells accessibles + for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) { + let rowObj = { + role: aRowRoles ? aRowRoles[rowIdx] : ROLE_ROW, + children: [], + }; + + for (let colIdx = 0; colIdx < colsCount; colIdx++) { + let celltype = aCellsArray[rowIdx][colIdx]; + + var role = ROLE_NOTHING; + switch (celltype) { + case kDataCell: + role = + aTableType == kMathTable + ? ROLE_MATHML_CELL + : isGrid + ? ROLE_GRID_CELL + : ROLE_CELL; + break; + case kRowHeaderCell: + role = ROLE_ROWHEADER; + break; + case kColHeaderCell: + role = ROLE_COLUMNHEADER; + break; + } + + if (role != ROLE_NOTHING) { + var cellObj = { role }; + rowObj.children.push(cellObj); + } + } + + tableObj.children.push(rowObj); + } + + testAccessibleTree(aIdentifier, tableObj); + + // Test table table interface. + var table = getAccessible(aIdentifier, [nsIAccessibleTable]); + + // summary + if (aSummary) { + is( + table.summary, + aSummary, + "Wrong summary of the table " + prettyName(aIdentifier) + ); + } + + // rowCount and columnCount + is( + table.rowCount, + rowCount, + "Wrong rows count of " + prettyName(aIdentifier) + ); + is( + table.columnCount, + colsCount, + "Wrong columns count of " + prettyName(aIdentifier) + ); + + // rows and columns extents + for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (let colIdx = 0; colIdx < colsCount; colIdx++) { + let celltype = aCellsArray[rowIdx][colIdx]; + if (celltype & kOrigin) { + // table getRowExtentAt + var rowExtent = table.getRowExtentAt(rowIdx, colIdx); + let idx; + /* eslint-disable no-empty */ + for ( + idx = rowIdx + 1; + idx < rowCount && aCellsArray[idx][colIdx] & kRowSpanned; + idx++ + ) {} + /* eslint-enable no-empty */ + + var expectedRowExtent = idx - rowIdx; + is( + rowExtent, + expectedRowExtent, + "getRowExtentAt: Wrong number of spanned rows at (" + + rowIdx + + ", " + + colIdx + + ") for " + + prettyName(aIdentifier) + ); + + // table getColumnExtentAt + var colExtent = table.getColumnExtentAt(rowIdx, colIdx); + /* eslint-disable no-empty */ + for ( + idx = colIdx + 1; + idx < colsCount && aCellsArray[rowIdx][idx] & kColSpanned; + idx++ + ) {} + /* eslint-enable no-empty */ + + var expectedColExtent = idx - colIdx; + is( + colExtent, + expectedColExtent, + "getColumnExtentAt: Wrong number of spanned columns at (" + + rowIdx + + ", " + + colIdx + + ") for " + + prettyName(aIdentifier) + ); + + // cell rowExtent and columnExtent + var cell = getAccessible(table.getCellAt(rowIdx, colIdx), [ + nsIAccessibleTableCell, + ]); + + is( + cell.rowExtent, + expectedRowExtent, + "rowExtent: Wrong number of spanned rows at (" + + rowIdx + + ", " + + colIdx + + ") for " + + prettyName(aIdentifier) + ); + + is( + cell.columnExtent, + expectedColExtent, + "columnExtent: Wrong number of spanned column at (" + + rowIdx + + ", " + + colIdx + + ") for " + + prettyName(aIdentifier) + ); + } + } + } +} + +/** + * Test table indexes. + * + * @param aIdentifier [in] table accessible identifier + * @param aIdxes [in] two dimensional array of cell indexes + */ +function testTableIndexes(aIdentifier, aIdxes) { + var tableAcc = getAccessible(aIdentifier, [nsIAccessibleTable]); + if (!tableAcc) { + return; + } + + var obtainedRowIdx, obtainedColIdx, obtainedIdx; + var cellAcc; + + var id = prettyName(aIdentifier); + + var rowCount = aIdxes.length; + for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + var colCount = aIdxes[rowIdx].length; + for (var colIdx = 0; colIdx < colCount; colIdx++) { + var idx = aIdxes[rowIdx][colIdx]; + + // getCellAt + try { + cellAcc = null; + cellAcc = tableAcc.getCellAt(rowIdx, colIdx); + } catch (e) {} + + ok( + (idx != -1 && cellAcc) || (idx == -1 && !cellAcc), + id + + ": Can't get cell accessible at row = " + + rowIdx + + ", column = " + + colIdx + ); + + if (idx != -1) { + // getRowIndexAt + var origRowIdx = rowIdx; + while ( + origRowIdx > 0 && + aIdxes[rowIdx][colIdx] == aIdxes[origRowIdx - 1][colIdx] + ) { + origRowIdx--; + } + + try { + obtainedRowIdx = tableAcc.getRowIndexAt(idx); + } catch (e) { + ok( + false, + id + ": can't get row index for cell index " + idx + "," + e + ); + } + + is( + obtainedRowIdx, + origRowIdx, + id + ": row for index " + idx + " is not correct (getRowIndexAt)" + ); + + // getColumnIndexAt + var origColIdx = colIdx; + while ( + origColIdx > 0 && + aIdxes[rowIdx][colIdx] == aIdxes[rowIdx][origColIdx - 1] + ) { + origColIdx--; + } + + try { + obtainedColIdx = tableAcc.getColumnIndexAt(idx); + } catch (e) { + ok( + false, + id + ": can't get column index for cell index " + idx + "," + e + ); + } + + is( + obtainedColIdx, + origColIdx, + id + + ": column for index " + + idx + + " is not correct (getColumnIndexAt)" + ); + + // getRowAndColumnIndicesAt + var obtainedRowIdxObj = {}, + obtainedColIdxObj = {}; + try { + tableAcc.getRowAndColumnIndicesAt( + idx, + obtainedRowIdxObj, + obtainedColIdxObj + ); + } catch (e) { + ok( + false, + id + + ": can't get row and column indices for cell index " + + idx + + "," + + e + ); + } + + is( + obtainedRowIdxObj.value, + origRowIdx, + id + + ": row for index " + + idx + + " is not correct (getRowAndColumnIndicesAt)" + ); + is( + obtainedColIdxObj.value, + origColIdx, + id + + ": column for index " + + idx + + " is not correct (getRowAndColumnIndicesAt)" + ); + + if (cellAcc) { + var cellId = prettyName(cellAcc); + cellAcc = getAccessible(cellAcc, [nsIAccessibleTableCell]); + + // cell: 'table-cell-index' attribute + var attrs = cellAcc.attributes; + var strIdx = ""; + try { + strIdx = attrs.getStringProperty("table-cell-index"); + } catch (e) { + ok( + false, + cellId + + ": no cell index from object attributes on the cell accessible at index " + + idx + + "." + ); + } + + if (strIdx) { + is( + parseInt(strIdx), + idx, + cellId + + ": cell index from object attributes of cell accessible isn't corrent." + ); + } + + // cell: table + try { + is( + cellAcc.table, + tableAcc, + cellId + ": wrong table accessible for the cell." + ); + } catch (e) { + ok(false, cellId + ": can't get table accessible from the cell."); + } + + // cell: getRowIndex + try { + obtainedRowIdx = cellAcc.rowIndex; + } catch (e) { + ok( + false, + cellId + + ": can't get row index of the cell at index " + + idx + + "," + + e + ); + } + + is( + obtainedRowIdx, + origRowIdx, + cellId + ": row for the cell at index " + idx + " is not correct" + ); + + // cell: getColumnIndex + try { + obtainedColIdx = cellAcc.columnIndex; + } catch (e) { + ok( + false, + cellId + + ": can't get column index of the cell at index " + + idx + + "," + + e + ); + } + + is( + obtainedColIdx, + origColIdx, + id + ": column for the cell at index " + idx + " is not correct" + ); + } + } + + // getCellIndexAt + try { + obtainedIdx = tableAcc.getCellIndexAt(rowIdx, colIdx); + } catch (e) { + obtainedIdx = -1; + } + + is( + obtainedIdx, + idx, + id + + ": row " + + rowIdx + + " /column " + + colIdx + + " and index " + + obtainedIdx + + " aren't inconsistent." + ); + } + } +} + +/** + * Test table getters selection methods. + * + * @param aIdentifier [in] table accessible identifier + * @param aCellsArray [in] two dimensional array (row X columns) of cells + * states (either boolean (selected/unselected) if cell is + * origin, otherwise kRowSpanned or kColSpanned constant). + * @param aMsg [in] text appended before every message + */ +function testTableSelection(aIdentifier, aCellsArray, aMsg) { + var msg = aMsg ? aMsg : ""; + var acc = getAccessible(aIdentifier, [nsIAccessibleTable]); + if (!acc) { + return; + } + + var rowCount = aCellsArray.length; + var colsCount = aCellsArray[0].length; + + // Columns selection tests. + var selCols = []; + + // isColumnSelected test + for (let colIdx = 0; colIdx < colsCount; colIdx++) { + var isColSelected = true; + for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) { + if ( + !aCellsArray[rowIdx][colIdx] || + aCellsArray[rowIdx][colIdx] == undefined + ) { + isColSelected = false; + break; + } + } + + is( + acc.isColumnSelected(colIdx), + isColSelected, + msg + + "Wrong selection state of " + + colIdx + + " column for " + + prettyName(aIdentifier) + ); + + if (isColSelected) { + selCols.push(colIdx); + } + } + + // selectedColsCount test + is( + acc.selectedColumnCount, + selCols.length, + msg + "Wrong count of selected columns for " + prettyName(aIdentifier) + ); + + // getSelectedColumns test + var actualSelCols = acc.getSelectedColumnIndices(); + + var actualSelColsCount = actualSelCols.length; + is( + actualSelColsCount, + selCols.length, + msg + + "Wrong count of selected columns for " + + prettyName(aIdentifier) + + "from getSelectedColumns." + ); + + for (let i = 0; i < actualSelColsCount; i++) { + is( + actualSelCols[i], + selCols[i], + msg + "Column at index " + selCols[i] + " should be selected." + ); + } + + // Rows selection tests. + var selRows = []; + + // isRowSelected test + for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) { + var isRowSelected = true; + for (let colIdx = 0; colIdx < colsCount; colIdx++) { + if ( + !aCellsArray[rowIdx][colIdx] || + aCellsArray[rowIdx][colIdx] == undefined + ) { + isRowSelected = false; + break; + } + } + + is( + acc.isRowSelected(rowIdx), + isRowSelected, + msg + + "Wrong selection state of " + + rowIdx + + " row for " + + prettyName(aIdentifier) + ); + + if (isRowSelected) { + selRows.push(rowIdx); + } + } + + // selectedRowCount test + is( + acc.selectedRowCount, + selRows.length, + msg + "Wrong count of selected rows for " + prettyName(aIdentifier) + ); + + // getSelectedRows test + var actualSelRows = acc.getSelectedRowIndices(); + + var actualSelrowCount = actualSelRows.length; + is( + actualSelrowCount, + selRows.length, + msg + + "Wrong count of selected rows for " + + prettyName(aIdentifier) + + "from getSelectedRows." + ); + + for (let i = 0; i < actualSelrowCount; i++) { + is( + actualSelRows[i], + selRows[i], + msg + "Row at index " + selRows[i] + " should be selected." + ); + } + + // Cells selection tests. + var selCells = []; + + // isCellSelected test + for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (let colIdx = 0; colIdx < colsCount; colIdx++) { + if (aCellsArray[rowIdx][colIdx] & kSpanned) { + continue; + } + + var isSelected = !!aCellsArray[rowIdx][colIdx]; + is( + acc.isCellSelected(rowIdx, colIdx), + isSelected, + msg + + "Wrong selection state of cell at " + + rowIdx + + " row and " + + colIdx + + " column for " + + prettyName(aIdentifier) + ); + + if (aCellsArray[rowIdx][colIdx]) { + selCells.push(acc.getCellIndexAt(rowIdx, colIdx)); + } + } + } + + // selectedCellCount tests + is( + acc.selectedCellCount, + selCells.length, + msg + "Wrong count of selected cells for " + prettyName(aIdentifier) + ); + + // getSelectedCellIndices test + var actualSelCells = acc.getSelectedCellIndices(); + + var actualSelCellsCount = actualSelCells.length; + is( + actualSelCellsCount, + selCells.length, + msg + + "Wrong count of selected cells for " + + prettyName(aIdentifier) + + "from getSelectedCells." + ); + + for (let i = 0; i < actualSelCellsCount; i++) { + is( + actualSelCells[i], + selCells[i], + msg + + "getSelectedCellIndices: Cell at index " + + selCells[i] + + " should be selected." + ); + } + + // selectedCells and isSelected tests + var actualSelCellsArray = acc.selectedCells; + for (let i = 0; i < actualSelCellsCount; i++) { + var actualSelCellAccessible = actualSelCellsArray.queryElementAt( + i, + nsIAccessibleTableCell + ); + + let colIdx = acc.getColumnIndexAt(selCells[i]); + let rowIdx = acc.getRowIndexAt(selCells[i]); + var expectedSelCellAccessible = acc.getCellAt(rowIdx, colIdx); + + is( + actualSelCellAccessible, + expectedSelCellAccessible, + msg + + "getSelectedCells: Cell at index " + + selCells[i] + + " should be selected." + ); + + ok( + actualSelCellAccessible.isSelected(), + "isSelected: Cell at index " + selCells[i] + " should be selected." + ); + } + + // selected states tests + for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (let colIdx = 0; colIdx < colsCount; colIdx++) { + if (aCellsArray[rowIdx][colIdx] & kSpanned) { + continue; + } + + var cell = acc.getCellAt(rowIdx, colIdx); + var isSel = aCellsArray[rowIdx][colIdx]; + if (isSel == undefined) { + testStates(cell, 0, 0, STATE_SELECTABLE | STATE_SELECTED); + } else if (isSel) { + testStates(cell, STATE_SELECTED); + } else { + testStates(cell, STATE_SELECTABLE, 0, STATE_SELECTED); + } + } + } +} + +/** + * Test columnHeaderCells and rowHeaderCells of accessible table. + */ +function testHeaderCells(aHeaderInfoMap) { + for (var testIdx = 0; testIdx < aHeaderInfoMap.length; testIdx++) { + var dataCellIdentifier = aHeaderInfoMap[testIdx].cell; + var dataCell = getAccessible(dataCellIdentifier, [nsIAccessibleTableCell]); + + // row header cells + var rowHeaderCells = aHeaderInfoMap[testIdx].rowHeaderCells; + var rowHeaderCellsCount = rowHeaderCells.length; + var actualRowHeaderCells = dataCell.rowHeaderCells; + var actualRowHeaderCellsCount = actualRowHeaderCells.length; + + is( + actualRowHeaderCellsCount, + rowHeaderCellsCount, + "Wrong number of row header cells for the cell " + + prettyName(dataCellIdentifier) + ); + + if (actualRowHeaderCellsCount == rowHeaderCellsCount) { + for (let idx = 0; idx < rowHeaderCellsCount; idx++) { + var rowHeaderCell = getAccessible(rowHeaderCells[idx]); + var actualRowHeaderCell = actualRowHeaderCells.queryElementAt( + idx, + nsIAccessible + ); + isObject( + actualRowHeaderCell, + rowHeaderCell, + "Wrong row header cell at index " + + idx + + " for the cell " + + dataCellIdentifier + ); + } + } + + // column header cells + var colHeaderCells = aHeaderInfoMap[testIdx].columnHeaderCells; + var colHeaderCellsCount = colHeaderCells.length; + var actualColHeaderCells = dataCell.columnHeaderCells; + var actualColHeaderCellsCount = actualColHeaderCells.length; + + is( + actualColHeaderCellsCount, + colHeaderCellsCount, + "Wrong number of column header cells for the cell " + + prettyName(dataCellIdentifier) + ); + + if (actualColHeaderCellsCount == colHeaderCellsCount) { + for (let idx = 0; idx < colHeaderCellsCount; idx++) { + var colHeaderCell = getAccessible(colHeaderCells[idx]); + var actualColHeaderCell = actualColHeaderCells.queryElementAt( + idx, + nsIAccessible + ); + isObject( + actualColHeaderCell, + colHeaderCell, + "Wrong column header cell at index " + + idx + + " for the cell " + + dataCellIdentifier + ); + } + } + } +} + +// ////////////////////////////////////////////////////////////////////////////// +// private implementation + +/** + * Return row and column of orig cell for the given spanned cell. + */ +function getOrigRowAndColumn(aCellsArray, aRowIdx, aColIdx) { + var cellState = aCellsArray[aRowIdx][aColIdx]; + + var origRowIdx = aRowIdx, + origColIdx = aColIdx; + if (cellState & kRowSpanned) { + for (var prevRowIdx = aRowIdx - 1; prevRowIdx >= 0; prevRowIdx--) { + let prevCellState = aCellsArray[prevRowIdx][aColIdx]; + if (!(prevCellState & kRowSpanned)) { + origRowIdx = prevRowIdx; + break; + } + } + } + + if (cellState & kColSpanned) { + for (var prevColIdx = aColIdx - 1; prevColIdx >= 0; prevColIdx--) { + let prevCellState = aCellsArray[aRowIdx][prevColIdx]; + if (!(prevCellState & kColSpanned)) { + origColIdx = prevColIdx; + break; + } + } + } + + return [origRowIdx, origColIdx]; +} diff --git a/accessible/tests/mochitest/table/a11y.ini b/accessible/tests/mochitest/table/a11y.ini new file mode 100644 index 0000000000..550fc54e04 --- /dev/null +++ b/accessible/tests/mochitest/table/a11y.ini @@ -0,0 +1,24 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_css_tables.html] +[test_headers_ariagrid.html] +[test_headers_ariatable.html] +[test_headers_table.html] +[test_headers_tree.xhtml] +[test_indexes_ariagrid.html] +[test_indexes_table.html] +[test_indexes_tree.xhtml] +[test_layoutguess.html] +[test_mtable.html] +[test_sels_ariagrid.html] +[test_sels_table.html] +[test_sels_tree.xhtml] +[test_struct_ariagrid.html] +[test_struct_ariatreegrid.html] +[test_struct_table.html] +[test_struct_tree.xhtml] +[test_table_1.html] +[test_table_2.html] +[test_table_mutation.html] diff --git a/accessible/tests/mochitest/table/test_css_tables.html b/accessible/tests/mochitest/table/test_css_tables.html new file mode 100644 index 0000000000..65877564e4 --- /dev/null +++ b/accessible/tests/mochitest/table/test_css_tables.html @@ -0,0 +1,114 @@ +<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <title>CSS display:table is not a table</title> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // elements with display:table + + // only display:table + var accTree = + { SECTION: [ + { TEXT_LEAF: [ ] }, + ] }; + testAccessibleTree("table1", accTree); + + // only display:table and display:table-cell + accTree = + { SECTION: [ + { SECTION: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }; + testAccessibleTree("table2", accTree); + + // display:table, display:table-row, and display:table-cell + accTree = + { SECTION: [ + { SECTION: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }; + testAccessibleTree("table3", accTree); + + // display:table, display:table-row-group, display:table-row, and display:table-cell + accTree = + { SECTION: [ + { SECTION: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }; + testAccessibleTree("table4", accTree); + + // display:inline-table + accTree = + { TEXT_CONTAINER: [ + { TEXT_CONTAINER: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }; + testAccessibleTree("table5", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + title=" div with display:table exposes table semantics" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1007975">Mozilla Bug 1007975</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="table1" style="display:table"> + table1 + </div> + + <div id="table2" style="display:table"> + <div style="display:table-cell">table2</div> + </div> + + <div id="table3" style="display:table"> + <div style="display:table-row"> + <div style="display:table-cell">table3</div> + </div> + </div> + + <div id="table4" style="display:table"> + <div style="display:table-row-group"> + <div style="display:table-row"> + <div style="display:table-cell">table4</div> + </div> + </div> + </div> + + <div> + <span id="table5" style="display:inline-table"> + <span style="display:table-row"> + <span style="display:table-cell">table5</div> + </span> + </span> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_headers_ariagrid.html b/accessible/tests/mochitest/table/test_headers_ariagrid.html new file mode 100644 index 0000000000..7b2c3f3dbf --- /dev/null +++ b/accessible/tests/mochitest/table/test_headers_ariagrid.html @@ -0,0 +1,183 @@ +<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <title>Table header information cells for ARIA grid</title> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // column and row headers from markup + + let headerInfoMap = [ + { + cell: "table_dc_1", + rowHeaderCells: [ "table_rh_1" ], + columnHeaderCells: [ "table_ch_2" ], + }, + { + cell: "table_dc_2", + rowHeaderCells: [ "table_rh_1" ], + columnHeaderCells: [ "table_ch_3" ], + }, + { + cell: "table_dc_3", + rowHeaderCells: [ "table_rh_2" ], + columnHeaderCells: [ "table_ch_2" ], + }, + { + cell: "table_dc_4", + rowHeaderCells: [ "table_rh_2" ], + columnHeaderCells: [ "table_ch_3" ], + }, + { + cell: "table_rh_1", + rowHeaderCells: [], + columnHeaderCells: [ "table_ch_1" ], + }, + { + cell: "table_rh_2", + rowHeaderCells: [], + columnHeaderCells: [ "table_ch_1" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + + // //////////////////////////////////////////////////////////////////////// + // column and row headers from markup for grid. + + headerInfoMap = [ + { + // not focusable cell (ARIAGridCellAccessible is used) + cell: "table2_dc_1", + rowHeaderCells: [], + columnHeaderCells: [ "table2_ch_1" ], + }, + { + // focusable cell (ARIAGridCellAccessible is used) + cell: "table2_dc_2", + rowHeaderCells: [], + columnHeaderCells: [ "table2_ch_2" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + + // //////////////////////////////////////////////////////////////////////// + // column and row headers from markup for one more grid. + + headerInfoMap = [ + { + // ARIAGridCellAccessible is used + cell: "t3_dc_1", + rowHeaderCells: [ "t3_rh_1" ], + columnHeaderCells: [ ], + }, + { + // ARIAGridCellAccessible is used (inside rowgroup) + cell: "t3_dc_2", + rowHeaderCells: [ "t3_rh_2" ], + columnHeaderCells: [ ], + }, + ]; + + testHeaderCells(headerInfoMap); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + title="implement IAccessibleTable2" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424">Mozilla Bug 512424</a> + <a target="_blank" + title="nsHTMLTableCellAccessible is used in dojo's ARIA grid" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Mozilla Bug 513848</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div role="grid"> + <div role="row"> + <span id="table_ch_1" role="columnheader">col_1</span> + <span id="table_ch_2" role="columnheader">col_2</span> + <span id="table_ch_3" role="columnheader">col_3</span> + </div> + <div role="row"> + <span id="table_rh_1" role="rowheader">row_1</span> + <span id="table_dc_1" role="gridcell">cell1</span> + <span id="table_dc_2" role="gridcell">cell2</span> + </div> + <div role="row"> + <span id="table_rh_2" role="rowheader">row_2</span> + <span id="table_dc_3" role="gridcell">cell3</span> + <span id="table_dc_4" role="gridcell">cell4</span> + </div> + </div> + + <div role="grid"> + <div role="row"> + <table role="presentation"> + <tr> + <td id="table2_ch_1" role="columnheader">header1</td> + <td id="table2_ch_2" role="columnheader">header2</td> + </tr> + </table> + </div> + <div role="row"> + <table role="presentation"> + <tr> + <td id="table2_dc_1" role="gridcell">cell1</td> + <td id="table2_dc_2" role="gridcell" tabindex="-1">cell2</td> + </tr> + </table> + </div> + </div> + + <div role="grid"> + <table role="presentation"> + <tbody role="presentation"> + <tr role="row"> + <th id="t3_rh_1" role="rowheader">Row 1</th> + <td id="t3_dc_1" role="gridcell" tabindex="-1"> + Apple Inc. + </td> + </tr> + </tbody> + </table> + <div role="rowgroup" tabindex="0"> + <table role="presentation"> + <tbody role="presentation"> + <tr role="row"> + <th id="t3_rh_2" role="rowheader">Row 2</th> + <td id="t3_dc_2" role="gridcell" tabindex="-1"> + Apple-Shmapple Inc. + </td> + </tr> + </tbody> + </table> + </div> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_headers_ariatable.html b/accessible/tests/mochitest/table/test_headers_ariatable.html new file mode 100644 index 0000000000..1af3813cdc --- /dev/null +++ b/accessible/tests/mochitest/table/test_headers_ariatable.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <title>Table header information cells for ARIA table</title> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // column and row headers from markup + + const headerInfoMap = [ + { + cell: "table_dc_1", + rowHeaderCells: [ "table_rh_1" ], + columnHeaderCells: [ "table_ch_2" ], + }, + { + cell: "table_dc_2", + rowHeaderCells: [ "table_rh_1" ], + columnHeaderCells: [ "table_ch_3" ], + }, + { + cell: "table_dc_3", + rowHeaderCells: [ "table_rh_2" ], + columnHeaderCells: [ "table_ch_2" ], + }, + { + cell: "table_dc_4", + rowHeaderCells: [ "table_rh_2" ], + columnHeaderCells: [ "table_ch_3" ], + }, + { + cell: "table_rh_1", + rowHeaderCells: [], + columnHeaderCells: [ "table_ch_1" ], + }, + { + cell: "table_rh_2", + rowHeaderCells: [], + columnHeaderCells: [ "table_ch_1" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + title="support ARIA table and cell roles" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173364">Bug 1173364</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div role="table"> + <div role="row"> + <span id="table_ch_1" role="columnheader">col_1</span> + <span id="table_ch_2" role="columnheader">col_2</span> + <span id="table_ch_3" role="columnheader">col_3</span> + </div> + <div role="row"> + <span id="table_rh_1" role="rowheader">row_1</span> + <span id="table_dc_1" role="cell">cell1</span> + <span id="table_dc_2" role="cell">cell2</span> + </div> + <div role="row"> + <span id="table_rh_2" role="rowheader">row_2</span> + <span id="table_dc_3" role="cell">cell3</span> + <span id="table_dc_4" role="cell">cell4</span> + </div> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_headers_table.html b/accessible/tests/mochitest/table/test_headers_table.html new file mode 100644 index 0000000000..0d7dafff4b --- /dev/null +++ b/accessible/tests/mochitest/table/test_headers_table.html @@ -0,0 +1,756 @@ +<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <title>Table header information cells for HTML table</title> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // column header from thead and row header from @scope inside of tfoot + + var headerInfoMap = [ + { + cell: "table1_cell_1", + rowHeaderCells: [], + columnHeaderCells: [ "table1_weekday", "table1_date" ], + }, + { + cell: "table1_cell_2", + rowHeaderCells: [], + columnHeaderCells: [ "table1_day", "table1_date" ], + }, + { + cell: "table1_cell_3", + rowHeaderCells: [], + columnHeaderCells: [ "table1_qty" ], + }, + { + cell: "table1_cell_4", + rowHeaderCells: [], + columnHeaderCells: [ "table1_weekday", "table1_date" ], + }, + { + cell: "table1_cell_5", + rowHeaderCells: [], + columnHeaderCells: [ "table1_day", "table1_date" ], + }, + { + cell: "table1_cell_6", + rowHeaderCells: [], + columnHeaderCells: [ "table1_qty" ], + }, + { + cell: "table1_cell_7", + rowHeaderCells: [ "table1_total" ], + columnHeaderCells: [ "table1_qty" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + // //////////////////////////////////////////////////////////////////////// + // column and row headers from thead and @scope + + headerInfoMap = [ + { + cell: "table2_cell_2", + rowHeaderCells: [ "table2_rh_1" ], + columnHeaderCells: [ "table2_ch_2" ], + }, + { + cell: "table2_cell_3", + rowHeaderCells: [ "table2_rh_1" ], + columnHeaderCells: [ "table2_ch_3" ], + }, + { + cell: "table2_cell_5", + rowHeaderCells: [ "table2_rh_2" ], + columnHeaderCells: [ "table2_ch_2" ], + }, + { + cell: "table2_cell_6", + rowHeaderCells: [ "table2_rh_2" ], + columnHeaderCells: [ "table2_ch_3" ], + }, + { + cell: "table2_rh_1", + rowHeaderCells: [], + columnHeaderCells: [ "table2_ch_1" ], + }, + { + cell: "table2_rh_2", + rowHeaderCells: [], + columnHeaderCells: [ "table2_ch_1" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + // //////////////////////////////////////////////////////////////////////// + // column headers from @headers + + headerInfoMap = [ + { + cell: "table3_cell_1", + rowHeaderCells: [], + columnHeaderCells: [ "table3_ch_1" ], + }, + { + cell: "table3_cell_2", + rowHeaderCells: [], + columnHeaderCells: [ "table3_ch_2" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + // //////////////////////////////////////////////////////////////////////// + // table consisted of one column + + headerInfoMap = [ + { + cell: "table4_cell", + rowHeaderCells: [], + columnHeaderCells: [ "table4_ch" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + // //////////////////////////////////////////////////////////////////////// + // table consisted of one row + + headerInfoMap = [ + { + cell: "table5_cell", + rowHeaderCells: [ "table5_rh" ], + columnHeaderCells: [ ], + }, + ]; + + testHeaderCells(headerInfoMap); + + // //////////////////////////////////////////////////////////////////////// + // @headers points to table cells + + headerInfoMap = [ + { + cell: "table6_cell", + rowHeaderCells: [ "table6_rh" ], + columnHeaderCells: [ "table6_ch" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + // //////////////////////////////////////////////////////////////////////// + // @scope="rowgroup" and @scope="row" + + headerInfoMap = [ + { + cell: "t7_r1c1", + rowHeaderCells: [ "t7_Mary", "t7_Females" ], + columnHeaderCells: [ "t7_1km" ], + }, + { + cell: "t7_r1c2", + rowHeaderCells: [ "t7_Mary", "t7_Females" ], + columnHeaderCells: [ "t7_5km" ], + }, + { + cell: "t7_r1c3", + rowHeaderCells: [ "t7_Mary", "t7_Females" ], + columnHeaderCells: [ "t7_10km" ], + }, + { + cell: "t7_r2c1", + rowHeaderCells: [ "t7_Betsy", "t7_Females" ], + columnHeaderCells: [ "t7_1km" ], + }, + { + cell: "t7_r2c2", + rowHeaderCells: [ "t7_Betsy", "t7_Females" ], + columnHeaderCells: [ "t7_5km" ], + }, + { + cell: "t7_r2c3", + rowHeaderCells: [ "t7_Betsy", "t7_Females" ], + columnHeaderCells: [ "t7_10km" ], + }, + { + cell: "t7_r3c1", + rowHeaderCells: [ "t7_Matt", "t7_Males" ], + columnHeaderCells: [ "t7_1km" ], + }, + { + cell: "t7_r3c2", + rowHeaderCells: [ "t7_Matt", "t7_Males" ], + columnHeaderCells: [ "t7_5km" ], + }, + { + cell: "t7_r3c3", + rowHeaderCells: [ "t7_Matt", "t7_Males" ], + columnHeaderCells: [ "t7_10km" ], + }, + { + cell: "t7_r4c1", + rowHeaderCells: [ "t7_Todd", "t7_Males" ], + columnHeaderCells: [ "t7_1km" ], + }, + { + cell: "t7_r4c2", + rowHeaderCells: [ "t7_Todd", "t7_Males" ], + columnHeaderCells: [ "t7_5km" ], + }, + { + cell: "t7_r4c3", + rowHeaderCells: [ "t7_Todd", "t7_Males" ], + columnHeaderCells: [ "t7_10km" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + // //////////////////////////////////////////////////////////////////////// + // @scope="colgroup" and @scope="col" + + headerInfoMap = [ + { + cell: "t8_r1c1", + rowHeaderCells: [ "t8_1km" ], + columnHeaderCells: [ "t8_Mary", "t8_Females" ], + }, + { + cell: "t8_r1c2", + rowHeaderCells: [ "t8_1km" ], + columnHeaderCells: [ "t8_Betsy", "t8_Females" ], + }, + { + cell: "t8_r1c3", + rowHeaderCells: [ "t8_1km" ], + columnHeaderCells: [ "t8_Matt", "t8_Males" ], + }, + { + cell: "t8_r1c4", + rowHeaderCells: [ "t8_1km" ], + columnHeaderCells: [ "t8_Todd", "t8_Males" ], + }, + { + cell: "t8_r2c1", + rowHeaderCells: [ "t8_5km" ], + columnHeaderCells: [ "t8_Mary", "t8_Females" ], + }, + { + cell: "t8_r2c2", + rowHeaderCells: [ "t8_5km" ], + columnHeaderCells: [ "t8_Betsy", "t8_Females" ], + }, + { + cell: "t8_r2c3", + rowHeaderCells: [ "t8_5km" ], + columnHeaderCells: [ "t8_Matt", "t8_Males" ], + }, + { + cell: "t8_r2c4", + rowHeaderCells: [ "t8_5km" ], + columnHeaderCells: [ "t8_Todd", "t8_Males" ], + }, + { + cell: "t8_r3c1", + rowHeaderCells: [ "t8_10km" ], + columnHeaderCells: [ "t8_Mary", "t8_Females" ], + }, + { + cell: "t8_r3c2", + rowHeaderCells: [ "t8_10km" ], + columnHeaderCells: [ "t8_Betsy", "t8_Females" ], + }, + { + cell: "t8_r3c3", + rowHeaderCells: [ "t8_10km" ], + columnHeaderCells: [ "t8_Matt", "t8_Males" ], + }, + { + cell: "t8_r3c4", + rowHeaderCells: [ "t8_10km" ], + columnHeaderCells: [ "t8_Todd", "t8_Males" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + // //////////////////////////////////////////////////////////////////////// + // spanned table header cells (v1), @headers define header order + + headerInfoMap = [ + { + cell: "t9_r1c1", + rowHeaderCells: [ "t9_females", "t9_mary" ], + columnHeaderCells: [ "t9_1km" ], + }, + { + cell: "t9_r1c2", + rowHeaderCells: [ "t9_females", "t9_mary" ], + columnHeaderCells: [ "t9_5km" ], + }, + { + cell: "t9_r1c3", + rowHeaderCells: [ "t9_females", "t9_mary" ], + columnHeaderCells: [ "t9_10km" ], + }, + { + cell: "t9_r2c1", + rowHeaderCells: [ "t9_females", "t9_betsy" ], + columnHeaderCells: [ "t9_1km" ], + }, + { + cell: "t9_r2c2", + rowHeaderCells: [ "t9_females", "t9_betsy" ], + columnHeaderCells: [ "t9_5km" ], + }, + { + cell: "t9_r2c3", + rowHeaderCells: [ "t9_females", "t9_betsy" ], + columnHeaderCells: [ "t9_10km" ], + }, + { + cell: "t9_r3c1", + rowHeaderCells: [ "t9_males", "t9_matt" ], + columnHeaderCells: [ "t9_1km" ], + }, + { + cell: "t9_r3c2", + rowHeaderCells: [ "t9_males", "t9_matt" ], + columnHeaderCells: [ "t9_5km" ], + }, + { + cell: "t9_r3c3", + rowHeaderCells: [ "t9_males", "t9_matt" ], + columnHeaderCells: [ "t9_10km" ], + }, + { + cell: "t9_r4c1", + rowHeaderCells: [ "t9_males", "t9_todd" ], + columnHeaderCells: [ "t9_1km" ], + }, + { + cell: "t9_r4c2", + rowHeaderCells: [ "t9_males", "t9_todd" ], + columnHeaderCells: [ "t9_5km" ], + }, + { + cell: "t9_r4c3", + rowHeaderCells: [ "t9_males", "t9_todd" ], + columnHeaderCells: [ "t9_10km" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + // //////////////////////////////////////////////////////////////////////// + // spanned table header cells (v2), @headers define header order + + headerInfoMap = [ + { + cell: "t10_r1c1", + rowHeaderCells: [ "t10_1km" ], + columnHeaderCells: [ "t10_females", "t10_mary" ], + }, + { + cell: "t10_r1c2", + rowHeaderCells: [ "t10_1km" ], + columnHeaderCells: [ "t10_females", "t10_betsy" ], + }, + { + cell: "t10_r1c3", + rowHeaderCells: [ "t10_1km" ], + columnHeaderCells: [ "t10_males", "t10_matt" ], + }, + { + cell: "t10_r1c4", + rowHeaderCells: [ "t10_1km" ], + columnHeaderCells: [ "t10_males", "t10_todd" ], + }, + { + cell: "t10_r2c1", + rowHeaderCells: [ "t10_5km" ], + columnHeaderCells: [ "t10_females", "t10_mary" ], + }, + { + cell: "t10_r2c2", + rowHeaderCells: [ "t10_5km" ], + columnHeaderCells: [ "t10_females", "t10_betsy" ], + }, + { + cell: "t10_r2c3", + rowHeaderCells: [ "t10_5km" ], + columnHeaderCells: [ "t10_males", "t10_matt" ], + }, + { + cell: "t10_r2c4", + rowHeaderCells: [ "t10_5km" ], + columnHeaderCells: [ "t10_males", "t10_todd" ], + }, + { + cell: "t10_r3c1", + rowHeaderCells: [ "t10_10km" ], + columnHeaderCells: [ "t10_females", "t10_mary" ], + }, + { + cell: "t10_r3c2", + rowHeaderCells: [ "t10_10km" ], + columnHeaderCells: [ "t10_females", "t10_betsy" ], + }, + { + cell: "t10_r3c3", + rowHeaderCells: [ "t10_10km" ], + columnHeaderCells: [ "t10_males", "t10_matt" ], + }, + { + cell: "t10_r3c4", + rowHeaderCells: [ "t10_10km" ], + columnHeaderCells: [ "t10_males", "t10_todd" ], + }, + ]; + + testHeaderCells(headerInfoMap); + + // //////////////////////////////////////////////////////////////////////// + // Ensure correct column headers after colspan in a previous row. + headerInfoMap = [ + { + cell: "t11r1c1", + columnHeaderCells: [], + rowHeaderCells: [], + }, + { + cell: "t11r1c2", + columnHeaderCells: [], + rowHeaderCells: [], + }, + { + cell: "t11r2c1_2", + columnHeaderCells: ["t11r1c1"], + rowHeaderCells: [], + }, + { + cell: "t11r3c1", + columnHeaderCells: ["t11r1c1"], + rowHeaderCells: [], + }, + { + cell: "t11r3c2", + columnHeaderCells: ["t11r1c2"], + rowHeaderCells: [], + }, + ]; + testHeaderCells(headerInfoMap); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + title="implement IAccessibleTable2" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424"> + Bug 512424 + </a> + <a target="_blank" + title="Table headers not associated when header is a td element with no scope" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=704465"> + Bug 704465 + </a> + <a target="_blank" + title="Support rowgroup and colgroup HTML scope" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1141978"> + Bug 1141978 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <table id="table1" border="1"> + <thead> + <tr> + <th id="table1_date" colspan="2">Date</th> + <th id="table1_qty" rowspan="2">Qty</th> + </tr> + <tr> + <th id="table1_weekday">Weekday</th> + <th id="table1_day">Day</th> + </tr> + </thead> + <tbody> + <tr> + <td id="table1_cell_1">Mon</td> + <td id="table1_cell_2">1</td> + <td id="table1_cell_3">20</td> + </tr> + <tr> + <td id="table1_cell_4">Thu</td> + <td id="table1_cell_5">2</td> + <td id="table1_cell_6">15</td> + </tr> + </tbody> + <tfoot> + <tr> + <th id="table1_total" scope="row" colspan="2">Total</th> + <td id="table1_cell_7">35</td> + </tr> + </tfoot> + </table> + + <table id="table2" border="1"> + <thead> + <tr> + <th id="table2_ch_1">col1</th> + <th id="table2_ch_2">col2</th> + <td id="table2_ch_3" scope="col">col3</td> + </tr> + </thead> + <tbody> + <tr> + <th id="table2_rh_1">row1</th> + <td id="table2_cell_2">cell1</td> + <td id="table2_cell_3">cell2</td> + </tr> + <tr> + <td id="table2_rh_2" scope="row">row2</td> + <td id="table2_cell_5">cell3</td> + <td id="table2_cell_6">cell4</td> + </tr> + </tbody> + </table> + + <table id="table3" border="1"> + <tr> + <td id="table3_cell_1" headers="table3_ch_1">cell1</td> + <td id="table3_cell_2" headers="table3_ch_2">cell2</td> + </tr> + <tr> + <td id="table3_ch_1" scope="col">col1</td> + <td id="table3_ch_2" scope="col">col2</td> + </tr> + </table> + + <table id="table4"> + <thead> + <tr> + <th id="table4_ch">colheader</th> + </tr> + </thead> + <tbody> + <tr> + <td id="table4_cell">bla</td> + </tr> + </tbody> + </table> + + <table id="table5"> + <tr> + <th id="table5_rh">rowheader</th> + <td id="table5_cell">cell</td> + </tr> + </table> + + <table id="table6"> + <tr> + <td>empty cell</th> + <td id="table6_ch">colheader</td> + </tr> + <tr> + <td id="table6_rh">rowheader</th> + <td id="table6_cell" headers="table6_ch table6_rh">cell</td> + </tr> + </table> + + <table id="table7" class="data complex" border="1"> + <caption>Version 1 with rowgroup</caption> + <thead> + <tr> + <td colspan="2"> </td> + <th id="t7_1km" scope="col">1 km</th> + <th id="t7_5km" scope="col">5 km</th> + <th id="t7_10km" scope="col">10 km</th> + </tr> + </thead> + <tbody> + <tr> + <th id="t7_Females" rowspan="2" scope="rowgroup">Females</th> + <th id="t7_Mary" scope="row">Mary</th> + <td id="t7_r1c1">8:32</td> + <td id="t7_r1c2">28:04</td> + <td id="t7_r1c3">1:01:16</td> + </tr> + <tr> + <th id="t7_Betsy" scope="row">Betsy</th> + <td id="t7_r2c1">7:43</td> + <td id="t7_r2c2">26:47</td> + <td id="t7_r2c3">55:38</td> + </tr> + <tr> + <th id="t7_Males" rowspan="2" scope="rowgroup">Males</th> + <th id="t7_Matt" scope="row">Matt</th> + <td id="t7_r3c1">7:55</td> + <td id="t7_r3c2">27:29</td> + <td id="t7_r3c3">57:04</td> + </tr> + <tr> + <th id="t7_Todd" scope="row">Todd</th> + <td id="t7_r4c1">7:01</td> + <td id="t7_r4c2">24:21</td> + <td id="t7_r4c3">50:35</td> + </tr> + </tbody> + </table> + + <table id="table8" class="data complex" border="1"> + <caption>Version 2 with colgroup</caption> + <thead> + <tr> + <td rowspan="2"> </td> + <th id="t8_Females" colspan="2" scope="colgroup">Females</th> + <th id="t8_Males" colspan="2" scope="colgroup">Males</th> + </tr> + <tr> + <th id="t8_Mary" scope="col">Mary</th> + <th id="t8_Betsy" scope="col">Betsy</th> + <th id="t8_Matt" scope="col">Matt</th> + <th id="t8_Todd" scope="col">Todd</th> + </tr> + </thead> + <tbody> + <tr> + <th id="t8_1km" scope="row">1 km</th> + <td id="t8_r1c1">8:32</td> + <td id="t8_r1c2">7:43</td> + <td id="t8_r1c3">7:55</td> + <td id="t8_r1c4">7:01</td> + </tr> + <tr> + <th id="t8_5km" scope="row">5 km</th> + <td id="t8_r2c1">28:04</td> + <td id="t8_r2c2">26:47</td> + <td id="t8_r2c3">27:27</td> + <td id="t8_r2c4">24:21</td> + </tr> + <tr> + <th id="t8_10km" scope="row">10 km</th> + <td id="t8_r3c1">1:01:16</td> + <td id="t8_r3c2">55:38</td> + <td id="t8_r3c3">57:04</td> + <td id="t8_r3c4">50:35</td> + </tr> + + </tbody> + </table> + + <table id="table9" border="1"> + <caption> + Example 1 (row group headers): + </caption> + <tr> + <td colspan="2"><span class="offscreen">empty</span></td> + <th id="t9_1km" width="40">1 km</th> + <th id="t9_5km" width="35">5 km</th> + <th id="t9_10km" width="42">10 km</th> + </tr> + <tr> + <th id="t9_females" width="56" rowspan="2">Females</th> + <th id="t9_mary" width="39">Mary</th> + <td id="t9_r1c1" headers="t9_females t9_mary t9_1km">8:32</td> + <td id="t9_r1c2" headers="t9_females t9_mary t9_5km">28:04</td> + <td id="t9_r1c3" headers="t9_females t9_mary t9_10km">1:01:16</td> + </tr> + <tr> + <th id="t9_betsy">Betsy</th> + <td id="t9_r2c1" headers="t9_females t9_betsy t9_1km">7:43</td> + <td id="t9_r2c2" headers="t9_females t9_betsy t9_5km">26:47</td> + <td id="t9_r2c3" headers="t9_females t9_betsy t9_10km">55:38</td> + </tr> + <tr> + <th id="t9_males" rowspan="2">Males</th> + <th id="t9_matt">Matt</th> + <td id="t9_r3c1" headers="t9_males t9_matt t9_1km">7:55</td> + <td id="t9_r3c2" headers="t9_males t9_matt t9_5km">27:29</td> + <td id="t9_r3c3" headers="t9_males t9_matt t9_10km">57:04</td> + </tr> + <tr> + <th id="t9_todd">Todd</th> + <td id="t9_r4c1" headers="t9_males t9_todd t9_1km">7:01</td> + <td id="t9_r4c2" headers="t9_males t9_todd t9_5km">24:21</td> + <td id="t9_r4c3" headers="t9_males t9_todd t9_10km">50:35</td> + </tr> + </table> + + <table id="table10" border="1"> + <caption> + Example 2 (column group headers): + </caption> + <tr> + <td rowspan="2"><span class="offscreen">empty</span></td> + <th colspan="2" id="t10_females">Females</th> + <th colspan="2" id="t10_males">Males</th> + </tr> + <tr> + <th width="40" id="t10_mary">Mary</th> + <th width="35" id="t10_betsy">Betsy</th> + <th width="42" id="t10_matt">Matt</th> + <th width="42" id="t10_todd">Todd</th> + </tr> + <tr> + <th width="39" id="t10_1km">1 km</th> + <td headers="t10_females t10_mary t10_1km" id="t10_r1c1">8:32</td> + <td headers="t10_females t10_betsy t10_1km" id="t10_r1c2">7:43</td> + <td headers="t10_males t10_matt t10_1km" id="t10_r1c3">7:55</td> + <td headers="t10_males t10_todd t10_1km" id="t10_r1c4">7:01</td> + </tr> + <tr> + <th id="t10_5km">5 km</th> + <td headers="t10_females t10_mary t10_5km" id="t10_r2c1">28:04</td> + <td headers="t10_females t10_betsy t10_5km" id="t10_r2c2">26:47</td> + <td headers="t10_males t10_matt t10_5km" id="t10_r2c3">27:29</td> + <td headers="t10_males t10_todd t10_5km" id="t10_r2c4">24:21</td> + </tr> + <tr> + <th id="t10_10km">10 km</th> + <td headers="t10_females t10_mary t10_10km" id="t10_r3c1">1:01:16</td> + <td headers="t10_females t10_betsy t10_10km" id="t10_r3c2">55:38</td> + <td headers="t10_males t10_matt t10_10km" id="t10_r3c3">57:04</td> + <td headers="t10_males t10_todd t10_10km" id="t10_r3c4">50:35</td> + </tr> + </table> + + <table id="table11"> + <tr> + <th id="t11r1c1">a</th> + <th id="t11r1c2">b</th> + </tr> + <tr> + <td id="t11r2c1_2" colspan="2"></td> + </tr> + <tr> + <td id="t11r3c1">e</td> + <td id="t11r3c2">f</td> + </tr> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_headers_tree.xhtml b/accessible/tests/mochitest/table/test_headers_tree.xhtml new file mode 100644 index 0000000000..46e8f43126 --- /dev/null +++ b/accessible/tests/mochitest/table/test_headers_tree.xhtml @@ -0,0 +1,100 @@ +<?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="Table header information cells for XUL tree"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../table.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "debug"; + + function doTest() + { + var treeAcc = getAccessible("tree", [nsIAccessibleTable]); + + var headerInfoMap = [ + { + cell: treeAcc.getCellAt(0, 0), + rowHeaderCells: [], + columnHeaderCells: [ "col" ] + }, + { + cell: treeAcc.getCellAt(0, 1), + rowHeaderCells: [], + columnHeaderCells: [ "scol" ] + }, + { + cell: treeAcc.getCellAt(1, 0), + rowHeaderCells: [], + columnHeaderCells: [ "col" ] + }, + { + cell: treeAcc.getCellAt(1, 1), + rowHeaderCells: [], + columnHeaderCells: [ "scol" ] + }, + { + cell: treeAcc.getCellAt(2, 0), + rowHeaderCells: [], + columnHeaderCells: [ "col" ] + }, + { + cell: treeAcc.getCellAt(2, 1), + rowHeaderCells: [], + columnHeaderCells: [ "scol" ] + }, + ]; + + testHeaderCells(headerInfoMap); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(3)); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424" + title="implement IAccessibleTable2"> + Mozilla Bug 512424 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="debug"/> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + <treecol id="scol" flex="1" label="column 2"/> + </treecols> + <treechildren id="treechildren"/> + </tree> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/table/test_indexes_ariagrid.html b/accessible/tests/mochitest/table/test_indexes_ariagrid.html new file mode 100644 index 0000000000..564e141a70 --- /dev/null +++ b/accessible/tests/mochitest/table/test_indexes_ariagrid.html @@ -0,0 +1,159 @@ +<!DOCTYPE html> +<html> +<head> + <title>Table indexes for ARIA grid tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // ARIA grid + var idxes = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [9, 10, 11], + ]; + testTableIndexes("grid", idxes); + + idxes = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + [9, 10, 11], + ]; + testTableIndexes("grid-rowgroups", idxes); + + // //////////////////////////////////////////////////////////////////////// + // a bit strange ARIA grid + idxes = [ + [0, 1], + [2, 3], + ]; + testTableIndexes("grid2", idxes); + + // an ARIA grid with div wrapping cell and div wrapping row + idxes = [ + [0, 1], + [2, 3], + [4, 5], + ]; + testTableIndexes("grid3", idxes); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=386813" + title="support nsIAccessibleTable on ARIA grid/treegrid">Mozilla Bug 386813</a> + <a target="_blank" + title="nsHTMLTableCellAccessible is used in dojo's ARIA grid" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Mozilla Bug 513848</a> + <a target="_blank" + title="ARIA grid with rowgroup breaks table row/col counting and indices" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=761853">Mozilla Bug 761853</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div role="grid" id="grid"> + <div role="row"> + <span role="columnheader">column1</span> + <span role="columnheader">column2</span> + <span role="columnheader">column3</span> + </div> + <div role="row"> + <span role="rowheader">row1</span> + <span role="gridcell">cell1</span> + <span role="gridcell">cell2</span> + </div> + <div role="row"> + <span role="rowheader">row2</span> + <span role="gridcell">cell3</span> + <span role="gridcell">cell4</span> + </div> + <div role="row"> + <span role="rowheader">row3</span> + <span role="gridcell">cell5</span> + <span role="gridcell">cell6</span> + </div> + </div> + + <div role="grid" id="grid-rowgroups"> + <div role="row"> + <span role="columnheader">grid-rowgroups-col1</span> + <span role="columnheader">grid-rowgroups-col2</span> + <span role="columnheader">grid-rowgroups-col3</span> + </div> + <div role="rowgroup"> + <div role="row"> + <span role="rowheader">grid-rowgroups-row1</span> + <span role="gridcell">grid-rowgroups-cell1</span> + <span role="gridcell">grid-rowgroups-cell2</span> + </div> + <div role="row"> + <span role="rowheader">grid-rowgroups-row2</span> + <span role="gridcell">grid-rowgroups-cell3</span> + <span role="gridcell">grid-rowgroups-cell4</span> + </div> + </div> + <div role="row"> + <span role="rowheader">grid-rowgroups-row3</span> + <span role="gridcell">grid-rowgroups-cell5</span> + <span role="gridcell">grid-rowgroups-cell6</span> + </div> + </div> + + <div role="grid" id="grid2"> + <div role="row"> + <table role="presentation"> + <tr> + <td role="columnheader">header1</td> + <td role="columnheader">header2</td> + </tr> + </table> + </div> + <div role="row"> + <table role="presentation"> + <tr> + <td role="gridcell">cell1</td> + <td role="gridcell" tabindex="-1">cell2</td> + </tr> + </table> + </div> + </div> + + <div role="grid" id="grid3"> + <div role="row"> + <div role="gridcell">Normal cell</div> + <div role="gridcell">1</div> + </div> + <div role="row"> + <div role="gridcell">Div</div> + <div><div role="gridcell">2</div></div> + </div> + <div tabindex="-1"><div role="row"> + <div role="gridcell">Cell in row in div</div> + <div role="gridcell">3</div> + </div></div> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_indexes_table.html b/accessible/tests/mochitest/table/test_indexes_table.html new file mode 100644 index 0000000000..c43dbad8f7 --- /dev/null +++ b/accessible/tests/mochitest/table/test_indexes_table.html @@ -0,0 +1,481 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=410052 +--> +<head> + <title>Table indexes chrome tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // table + var idxes = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 7], + [6, 8, 9], + ]; + + testTableIndexes("table", idxes); + + // //////////////////////////////////////////////////////////////////////// + // tableborder + idxes = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 7], + [6, 8, 9], + ]; + + testTableIndexes("tableborder", idxes); + + // //////////////////////////////////////////////////////////////////////// + // table + idxes = [ + [ 0, 1, 2, 2, 3, 4, 5, 6], + [ 7, 8, 9, 10, 11, 12, 13, 6], + [14, 15, 15, 16, 17, 18, 19, 6], + [20, 15, 15, 21, 22, 18, 23, 6], + ]; + + testTableIndexes("table2", idxes); + + // //////////////////////////////////////////////////////////////////////// + // tableinsane1 (empty row groups) + idxes = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 7], + [6, 8, 9], + ]; + + testTableIndexes("tableinsane1", idxes); + + // //////////////////////////////////////////////////////////////////////// + // tableinsane2 (empry rows) + idxes = [ + [-1, -1, -1], + [-1, -1, -1], + [ 0, 1, 2], + ]; + + testTableIndexes("tableinsane2", idxes); + + // //////////////////////////////////////////////////////////////////////// + // tableinsane3 (cell holes) + idxes = [ + [0, 1, -1], + [2, 3, 4], + ]; + + testTableIndexes("tableinsane3", idxes); + + // //////////////////////////////////////////////////////////////////////// + // tableinsane3.2 (cell holes, row spans, fixed in bug 417912) + idxes = [ + [0, 1, 2], + [3, -1, 2], + [4, 5, 2], + ]; + + testTableIndexes("tableinsane3.2", idxes); + + // //////////////////////////////////////////////////////////////////////// + // tableinsane4 (empty row groups/rows and cell holes) + idxes = [ + [ 0, 1, 2], + [-1, -1, -1], + [ 3, 4, 5], + [ 6, 6, 7], + [ 8, -1, 7], + [ 9, 9, 9], + ]; + testTableIndexes("tableinsane4", idxes); + + // //////////////////////////////////////////////////////////////////////// + // tableinsane5 (just a strange table) + idxes = [ + [ 0, 1, 2, -1, -1], + [-1, -1, -1, -1, -1], + [ 3, 4, 5, -1, -1], + [ 6, 7, -1, -1, -1], + [ 6, 8, 9, -1, -1], + [ 6, 10, 9, 11, 12], + ]; + testTableIndexes("tableinsane5", idxes); + + // //////////////////////////////////////////////////////////////////////// + // tableinsane6 (overlapping cells, mad table) + idxes = [ + [ 0, 1, 2, -1, -1], + [-1, -1, -1, -1, -1], + [ 3, 4, 5, -1, -1], + [ 6, 6, 7, -1, -1], + [ 8, 9, 7, -1, -1], + [ 10, 9, 7, 11, 12], + ]; + testTableIndexes("tableinsane6", idxes); + + // //////////////////////////////////////////////////////////////////////// + // Table with a cell that has display: block; style + idxes = [ + [0, 1], + ]; + testTableIndexes("tablewithcelldisplayblock", idxes); + + // //////////////////////////////////////////////////////////////////////// + // A table with a cell that has display: block; and a cell with colspan. + // This makes us fall back to the ARIAGridCellAccessible implementation. + idxes = [ + [0, 0, 1], + ]; + testTableIndexes("tablewithcolspanandcelldisplayblock", idxes); + + // //////////////////////////////////////////////////////////////////////// + // A table with all elements being display:block, including a row group. + // This makes us fall back to the ARIAGridRowAccessible, and we must + // make sure the index is 0. Strange example from Gmail. + idxes = [ + [0], + ]; + testTableIndexes("tablealldisplayblock", idxes); + + // //////////////////////////////////////////////////////////////////////// + // Table that has display: block; style + idxes = [ + [0, 1], + ]; + testTableIndexes("tablewithdisplayblock", idxes); + + // //////////////////////////////////////////////////////////////////////// + // tbody that has display: block; style + idxes = [ + [0, 1], + ]; + testTableIndexes("tbodywithdisplayblock", idxes); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="GetIndexAt and GetRowAtIndex and GetColumnAtIndex on HTML tables are inconsistent" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052"> + Bug 410052 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- + If you change the structure of the table please make sure to change + the indexes count in 'for' statement in the script above. + --> + <table border="1" id="table"> + <caption><strong><b><font size="29">this is a caption for this table</font></b></strong></caption> + <thead> + <tr> + <th>col1</th> + <th>col2</th> + <th>col3</th> + </tr> + </thead> + <tbody> + <tr> + <td>1</td> + <td>2</td> + <td>3</td> + </tr> + <tr> + <td rowspan="0">4</td> + <td colspan="2">5</td> + </tr> + <tr> + <td>6</td> + <td>7</td> + </tr> + </tbody> + </table> + + <table border="1" id="tableborder" style="border-collapse:collapse"> + <caption><strong><b><font size="29">this is a caption for this bc table</font></b></strong></caption> + <thead> + <tr> + <th>col1</th> + <th>col2</th> + <th>col3</th> + </tr> + </thead> + <tbody> + <tr> + <td>1</td> + <td>2</td> + <td>3</td> + </tr> + <tr> + <td rowspan="2">4</td> + <td colspan="2">5</td> + </tr> + <tr> + <td>6</td> + <td>7</td> + </tr> + </tbody> + </table> + + <table cellpadding="2" cellspacing="2" border="1" width="50%" id="table2"> + <caption>column and row spans</caption> + <tbody> + <tr> + <td>0</td> + <td>1</td> + <td rowspan="1" colspan="2">2</td> + <td>3</td> + <td>4</td> + <td>5</td> + <td rowspan="4" colspan="1">6</td> + </tr> + <tr> + <td>7</td> + <td>8</td> + <td>8</td> + <td>10</td> + <td>11</td> + <td>12</td> + <td>13</td> + </tr> + <tr> + <td>14</td> + <td rowspan="2" colspan="2">15</td> + <td>16</td> + <td>17</td> + <td rowspan="2" colspan="1">18</td> + <td>19</td> + </tr> + <tr> + <td>20</td> + <td>21</td> + <td>22</td> + <td>23</td> + </tr> + </tbody> + </table> + + <table border="1" id="tableinsane1"> + <caption>test empty row groups</caption> + <thead> + <tr> + <th>col1</th> + <th>col2</th> + <th>col3</th> + </tr> + </thead> + <tbody></tbody> + <tbody></tbody> + <tbody></tbody> + <tbody> + <tr> + <td>1</td> + <td>2</td> + <td>3</td> + </tr> + <tr> + <td rowspan="2">4</td> + <td colspan="2">5</td> + </tr> + <tr> + <td>6</td> + <td>7</td> + </tr> + </tbody> + </table> + + <table border="1" id="tableinsane2"> + <caption>empty rows</caption> + <tbody><tr></tr><tr></tr></tbody> + <tbody></tbody> + <tbody> + <tr> + <td>0</td> + <td>1</td> + <td>2</td> + </tr> + </tbody> + </table> + + <table border="1" id="tableinsane3"> + <caption>missed cell</caption> + <tbody> + <tr> + <td>0</td> + <td>1</td> + </tr> + </tbody> + <tbody> + <tr> + <td>2</td> + <td>3</td> + <td>4</td> + </tr> + </tbody> + </table> + + <table cellpadding="2" cellspacing="2" border="1" id="tableinsane3.2"> + <tr><td>1</td><td>2</td><td rowspan=3>3</td> + <tr><td>4</td> + <tr><td>5</td><td>6</td> + </table> + + <table border="1" id="tableinsane4"> + <caption>test empty rows + cellmap holes</caption> + <thead> + <tr> + <th>col1</th> + <th>col2</th> + <th>col3</th> + </tr> + </thead> + <tbody><tr></tr></tbody> + <tbody></tbody> + <tbody></tbody> + <tbody> + <tr> + <td>1</td> + <td>2</td> + <td>3</td> + </tr> + <tr> + <td colspan="2">4</td> + <td rowspan="2">5</td> + </tr> + <tr> + <td>6</td> + </tr> + <tr> + <td colspan="3">7</td> + </tr> + + </tbody> + </table> + + <table border="1" id="tableinsane5"> + <caption>just a strange table</caption> + <thead> + <tr> + <th>col1</th> + <th>col2</th> + <th>col3</th> + </tr> + </thead> + <tbody><tr></tr></tbody> + <tbody></tbody> + <tbody></tbody> + <tbody> + <tr> + <td>1</td> + <td>2</td> + <td>3</td> + </tr> + <tr> + <td rowspan="0">4</td> + <td colspan="0">5</td> + </tr> + <tr> + <td>6</td> + <td rowspan="0">7</td> + </tr> + <tr> + <td>8</td> + <td>9</td> + <td>10</td> + </tr> + + </tbody> + </table> + + <table border="1" id="tableinsane6" > + <caption>overlapping cells</caption> + <thead> + <tr> + <th>header cell 0</th> + <th>header cell 1</th> + <th>header cell 2</th> + </tr> + </thead> + <tbody><tr></tr></tbody> + <tbody></tbody> + <tbody></tbody> + <tbody> + <tr> + <td>3</td> + <td>4</td> + <td>5</td> + </tr> + <tr> + <td colspan="2">6</td> + <td rowspan="0">7</td> + </tr> + <tr> + <td>8</td> + <td rowspan="0">9</td> + </tr> + <tr> + <td colspan="3">10</td> + <td>11</td> + <td>12</td> + </tr> + </tbody> + </table> + + <table id="tablewithcelldisplayblock"> + <tr> + <th>a</th> + <td style="display: block;">b</td> + </tr> + </table> + + <table id="tablewithcolspanandcelldisplayblock"> + <tr> + <th colspan="2">a</th> + <td style="display: block;" >b</td> + </tr> + </table> + + <table id="tablealldisplayblock" style="display:block;"> + <tbody style="display:block;"> + <tr style="display:block;"> + <td style="display:block;">text</td> + </tr> + </tbody> + </table> + + <table id="tablewithdisplayblock" style="display: block;"> + <tr><th>a</th><td>b</td></tr> + </table> + + <table id="tbodywithdisplayblock"> + <tbody style="display: block;"> + <tr> + <th>a</th> + <td>b</td> + </tr> + </tbody> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_indexes_tree.xhtml b/accessible/tests/mochitest/table/test_indexes_tree.xhtml new file mode 100644 index 0000000000..0b1b6b2625 --- /dev/null +++ b/accessible/tests/mochitest/table/test_indexes_tree.xhtml @@ -0,0 +1,70 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible Table indexes tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../table.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "debug"; + + function doTest() + { + var idxes = [ + [0, 1], + [2, 3], + [4, 5] + ]; + testTableIndexes("tree", idxes); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(3)); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727" + title="Reorganize implementation of XUL tree accessibility"> + Mozilla Bug 503727 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="debug"/> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + <treecol id="scol" flex="1" label="column 2"/> + </treecols> + <treechildren id="treechildren"/> + </tree> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/table/test_layoutguess.html b/accessible/tests/mochitest/table/test_layoutguess.html new file mode 100644 index 0000000000..f3e94c5a78 --- /dev/null +++ b/accessible/tests/mochitest/table/test_layoutguess.html @@ -0,0 +1,554 @@ +<html> +<!-- https://bugzilla.mozilla.org/show_bug.cgi?id=495388 --> +<head> + <title>test HTMLTableAccessible::IsProbablyForLayout implementation</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + function isLayoutTable(id) { + // This helps us know if the absence of layout-guess is simply because + // it is not a table. + ok(isAccessible(id, nsIAccessibleTable), `${id} has table interface`); + testAttrs(id, { "layout-guess": "true" }, true); + } + function isDataTable(id) { + testAbsentAttrs(id, { "layout-guess": "true" }); + } + + function doTest() { + // table with role of grid + isDataTable("table1"); + // table with role of grid and datatable="0" + isDataTable("table1.1"); + + // table with landmark role + isDataTable("table2"); + + // table with summary + isDataTable("table3"); + + // table with caption + isDataTable("table4"); + + // layout table with empty caption + isLayoutTable("table4.2"); + + // table with thead element + isDataTable("table5"); + + // table with tfoot element + isDataTable("table5.1"); + + // table with colgroup or col elements + isDataTable("table5.2"); + isDataTable("table5.3"); + + // table with th element + isDataTable("table6"); + + // table with headers attribute + isDataTable("table6.2"); + + // table with scope attribute + isDataTable("table6.2.2"); + + // table with abbr attribute + isDataTable("table6.2.3"); + + // table with abbr element + isDataTable("table6.3"); + + // table with abbr element having empty text node + isDataTable("table6.4"); + + // table with abbr element and non-empty text node + isLayoutTable("table6.5"); + + // layout table with nested table + isLayoutTable("table9"); + + // layout table with 1 column + isLayoutTable("table10"); + + // layout table with 1 row + isLayoutTable("table11"); + + // table with 5 columns + isDataTable("table12"); + + // table with a bordered cell + isDataTable("table13"); + + // table with alternating row background colors + isDataTable("table14"); + + // table with 3 columns and 21 rows + isDataTable("table15"); + + // layout table that has a 100% width + isLayoutTable("table16"); + + // layout table that has a 95% width in pixels + isLayoutTable("table17"); + + // layout table with less than 10 columns + isLayoutTable("table18"); + + // layout table with embedded iframe + isLayoutTable("table19"); + + // tree grid, no layout table + isDataTable("table20"); + + // layout table containing nested data table (having data structures) + isLayoutTable("table21"); + isLayoutTable("table21.2"); + isLayoutTable("table21.3"); + isLayoutTable("table21.4"); + isLayoutTable("table21.5"); + isLayoutTable("table21.6"); + + // layout table having datatable="0" attribute and containing data table structure (tfoot element) + isLayoutTable("table22"); + + // repurposed table for tabbed UI + isLayoutTable("table23"); + + // layout display:block table with 1 column + isLayoutTable("displayblock_table1"); + + // matrix + isDataTable("mtable1"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=495388" + title="Don't treat tables that have a landmark role as layout table"> + Mozilla Bug 495388 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=690222" + title="Data table elements used to determine layout-guess attribute shouldn't be picked from nested tables"> + Mozilla Bug 690222 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=696975" + title="Extend the list of legitimate data table structures"> + Mozilla Bug 696975 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- Table with role of grid --> + <table id="table1" role="grid"> + <tr> + <th>Sender</th> + <th>Subject</th> + <th>Date</th> + </tr> + <tr> + <td>Marco</td> + <td>Test</td> + <td>June 12</td> + </tr> + <tr> + <td>David</td> + <td>Another test</td> + <td>June 12</td> + </tr> + <tr> + <td>Alex</td> + <td>Third test</td> + <td>June 12</td> + </tr> + </table> + <!-- table with role of grid and datatable="0"--> + <table id="table1.1" role="grid" datatable="0"> + <tr> + <td>Cell1</td><td>cell2</td> + </tr> + </table> + + <!-- table with landmark role --> + <table id="table2" role="main"> + <tr> + <th>Sender</th> + <th>Subject</th> + <th>Date</th> + </tr> + <tr> + <td>Marco</td> + <td>Test</td> + <td>June 12</td> + </tr> + <tr> + <td>David</td> + <td>Another test</td> + <td>June 12</td> + </tr> + <tr> + <td>Alex</td> + <td>Third test</td> + <td>June 12</td> + </tr> + </table> + + <!-- table with summary --> + <table id="table3" summary="This is a table"> + <tr> + <td>Cell1</td><td>cell2</td> + </tr> + </table> + + <!-- table with caption --> + <table id="table4"> + <caption>This is a table</caption> + <tr> + <td>Cell1</td><td>cell2</td> + </tr> + </table> + + <!-- layout table with empty caption --> + <table id="table4.2"> + <caption> </caption> + <tr> + <td>Cell1</td><td>cell2</td> + </tr> + </table> + + <!-- table with thead element --> + <table id="table5"> + <thead> + <tr> + <td>Cell1</td><td>cell2</td> + </tr> + </thead> + </table> + + <!-- table with tfoot element --> + <table id="table5.1"> + <tfoot> + <tr> + <td>Cell1</td><td>cell2</td> + </tr> + </tfoot> + </table> + + <!-- table with colgroup and col elements --> + <table id="table5.2"> + <colgroup width="20"></colgroup> + <tr> + <td>Cell1</td><td>cell2</td> + </tr> + </table> + <table id="table5.3"> + <col width="20"> + <tr> + <td>Cell1</td><td>cell2</td> + </tr> + </table> + + <!-- table with th element --> + <table id="table6"> + <tr> + <th>Cell1</th><th>cell2</th> + </tr> + </table> + + <!-- table with headers attribute --> + <table id="table6.2"> + <tr> + <td headers="a">table6.2 cell</td> + </tr> + </table> + + <!-- table with scope attribute --> + <table id="table6.2.2"> + <tr> + <td scope="a">table6.2.2 cell</td> + </tr> + </table> + + <!-- table with abbr attribute --> + <table id="table6.2.3"> + <tr> + <td abbr="table6.2.3">table6.2.3 cell1</td> + </tr> + </table> + + <!-- table with abbr element --> + <table id="table6.3"> + <tr> + <td>table6.3 cell1</td> + <td><abbr>table6.3 cell2</abbr></td> + </tr> + </table> + + <!-- table with abbr element having empty text node --> + <table id="table6.4"> + <tr> + <td> + <abbr>abbr</abbr> + </td> + </tr> + </table> + + <!-- table with abbr element and non-empty text node --> + <table id="table6.5"> + <tr> + <td> + This is a really long text (<abbr>tiarlt</abbr>) inside layout table + </td> + </tr> + </table> + + <!-- layout table with nested table --> + <table id="table9"> + <tr> + <td><table><tr><td>Cell</td></tr></table></td> + </tr> + </table> + + <!-- layout table with 1 column --> + <table id="table10"> + <tr><td>Row1</td></tr> + <tr><td>Row2</td></tr> + </table> + + <!-- layout table with 1 row and purposely many columns --> + <table id="table11"> + <tr><td>Col1</td><td>Col2</td><td>Col3</td><td>Col4</td><td>Col5</td></tr> + </table> + + <!-- table with 5 columns --> + <table id="table12"> + <tr><td>Col1</td><td>Col2</td><td>Col3</td><td>Col4</td><td>Col5</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td><td>Col4</td><td>Col5</td></tr> + </table> + + <!-- table with a bordered cell --> + <table id="table13" border="1" width="100%" bordercolor="#0000FF"> + <tr> + <td bordercolor="#000000"> </td> + <td bordercolor="#000000"> </td> + <td bordercolor="#000000"> </td> + </tr> + <tr> + <td bordercolor="#000000"> </td> + <td bordercolor="#000000"> </td> + <td bordercolor="#000000"> </td> + </tr> + </table> + + <!-- table with alternating row background colors --> + <table id="table14" width="100%"> + <tr style="background-color: #0000FF;"> + <td> </td> + <td> </td> + <td> </td> + </tr> + <tr style="background-color: #00FF00;"> + <td> </td> + <td> </td> + <td> </td> + </tr> + </table> + + <!-- table with 3 columns and 21 rows --> + <table id="table15" border="0"> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + </table> + + <!-- layout table that has a 100% width --> + <table id="table16" width="100%"> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + </table> + + <!-- layout table that has a 95% width in pixels --> + <table id="table17" width="98%"> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + <tr><td>Col1</td><td>Col2</td><td>Col3</td></tr> + </table> + + <!-- layout table with less than 10 columns --> + <table id="table18"> + <tr> + <td>Marco</td> + <td>Test</td> + <td>June 12</td> + </tr> + <tr> + <td>David</td> + <td>Another test</td> + <td>June 12</td> + </tr> + <tr> + <td>Alex</td> + <td>Third test</td> + <td>June 12</td> + </tr> + </table> + + <!-- layout table with embedded iframe --> + <table id="table19"> + <tr><td><iframe id="frame"></iframe></td><td> </td><td> </td></tr> + <tr><td> </td><td> </td><td> </td></tr> + <tr><td> </td><td> </td><td> </td></tr> + <tr><td> </td><td> </td><td> </td></tr> + </table> + + <!-- tree grid, no layout table --> + <table id="table20" role="treegrid"> + <tr role="treeitem"><td>Cell1</td><td>Cell2</td></tr> + </table> + + <!-- layout table with nested data table containing data table elements --> + <table id="table21"> + <tr> + <td> + <table> + <caption>table</caption> + <tr><td>Cell</td></tr> + </table> + </td> + </tr> + </table> + <table id="table21.2"> + <tr> + <td> + <table> + <colgroup width="20"></colgroup> + <tr><th>Cell</th></tr> + </table> + </td> + </tr> + </table> + <table id="table21.3"> + <tr> + <td> + <table> + <col width="20"></col> + <tr><th>Cell</th></tr> + </table> + </td> + </tr> + </table> + <table id="table21.4"> + <tr> + <td> + <table> + <tr><th>Cell</th></tr> + </table> + </td> + </tr> + </table> + <table id="table21.5"> + <tr> + <td> + <table> + <thead> + <tr><td>Cell</td></tr> + </thead> + </table> + </td> + </tr> + </table> + <table id="table21.6"> + <tr> + <td> + <table> + <tfoot> + <tr><td>Cell</td></tr> + </tfoot> + </table> + </td> + </tr> + </table> + + <!-- layout table with datatable="0" and tfoot element--> + <table id="table22" datatable="0"> + <tfoot> + <tr> + <td>Cell1</td><td>cell2</td> + </tr> + </tfoot> + </table> + + <table id="table23" border="1"> + <tr role="tablist"> + <td role="tab">Tab 1</td><td role="tab">Tab 2</td> + </tr> + <tr> + <td role="tabpanel" colspan="2">Hello</td> + </tr> + </table> + + <!-- display:block table --> + <table id="displayblock_table1" style="display:block"> + <tr><td>Row1</td></tr> + <tr><td>Row2</td></tr> + </table> + + <!-- MathML matrix --> + <math> + <mtable id="mtable1"> + <mtr> + <mtd> + <mn>1</mn> + </mtd> + <mtd> + <mn>0</mn> + </mtd> + </mtr> + <mtr> + <mtd> + <mn>0</mn> + </mtd> + <mtd> + <mn>1</mn> + </mtd> + </mtr> + </mtable> + </math> +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_mtable.html b/accessible/tests/mochitest/table/test_mtable.html new file mode 100644 index 0000000000..aa79b3b98c --- /dev/null +++ b/accessible/tests/mochitest/table/test_mtable.html @@ -0,0 +1,160 @@ +<!DOCTYPE html> +<html> +<head> + <title>MathML table tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + function doTest() { + // 'Simple' table + var idxes = [ + [0, 1], + [2, 3], + ]; + testTableIndexes("simple", idxes); + var cellsArray = [ + [kDataCell, kDataCell], + [kDataCell, kDataCell], + ]; + var rowsArray = [ROLE_MATHML_TABLE_ROW, ROLE_MATHML_TABLE_ROW]; + testTableStruct("simple", cellsArray, kNoColumnHeader, + "", "", kMathTable, rowsArray); + + // 'Complex' table + idxes = [ + [0, 0, 0], + [1, 1, 2], + [1, 1, 3], + ]; + testTableIndexes("complex", idxes); + cellsArray = [ + [kDataCell, kColSpanned, kColSpanned], + [kDataCell, kColSpanned, kDataCell], + [kRowSpanned, kSpanned, kDataCell], + ]; + rowsArray = [ + ROLE_MATHML_TABLE_ROW, + ROLE_MATHML_TABLE_ROW, + ROLE_MATHML_TABLE_ROW, + ]; + testTableStruct("complex", cellsArray, kNoColumnHeader, + "", "", kMathTable, rowsArray); + + // 'Simple' table with mlabeledtr + // At the moment we do not implement mlabeledtr but just hide the label + // with display: none. Thus we just test the role for now. See bug 689641. + idxes = [[0]]; + testTableIndexes("simple_label", idxes); + cellsArray = [[kDataCell]]; + rowsArray = [ROLE_MATHML_LABELED_ROW]; + testTableStruct("simple_label", cellsArray, kNoColumnHeader, + "", "", kMathTable, rowsArray); + + // Test that a non-table display style still generates the proper + // roles in the accessibility tree. + const table_tree = { + MATHML_TABLE: [{
+ MATHML_TABLE_ROW: [{ MATHML_CELL: [{ TEXT_LEAF: [] }] }]
+ }], + }; + testAccessibleTree("table_with_display_block_mtd", table_tree); + + // Equivalent to the above test but with display: block mtr. + testAccessibleTree("table_with_display_block_mtr", table_tree); + + // Equivalent to the above test but with display: block mtable. + testAccessibleTree("table_with_display_block", table_tree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <math> + <mtable id="simple"> + <mtr> + <mtd> + <mn>1</mn> + </mtd> + <mtd> + <mn>0</mn> + </mtd> + </mtr> + <mtr> + <mtd> + <mn>0</mn> + </mtd> + <mtd> + <mn>1</mn> + </mtd> + </mtr> + </mtable> + + <mtable id="complex"> + <mtr> + <mtd columnspan="3"> + <mtext>1 x 3</mtext> + </mtd> + </mtr> + <mtr> + <mtd rowspan="2" columnspan="2"> + <mtext>2 x 2</mtext> + </mtd> + <mtd> + <mtext>1 x 1</mtext> + </mtd> + </mtr> + <mtr> + <mtd> + <mtext>1 x 1</mtext> + </mtd> + </mtr> + </mtable> + + <mtable id="simple_label"> + <mlabeledtr> + <mtd><mtext>1</mtext></mtd> + <mtd><mtext>label</mtext></mtd> + </mlabeledtr> + </mtable> + + <mtable id="table_with_display_block_mtd"> + <mtr> + <mtd style="display: block">test</mtd> + </mtr> + </mtable> + + <mtable id="table_with_display_block_mtr"> + <mtr style="display: block"> + <mtd>test</mtd> + </mtr> + </mtable> + + <mtable id="table_with_display_block" style="display: block"> + <mtr> + <mtd>test</mtd> + </mtr> + </mtable> + + </math> + +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_sels_ariagrid.html b/accessible/tests/mochitest/table/test_sels_ariagrid.html new file mode 100644 index 0000000000..6a4f065bd8 --- /dev/null +++ b/accessible/tests/mochitest/table/test_sels_ariagrid.html @@ -0,0 +1,147 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=410052 +--> +<head> + <title>nsIAccesible selection methods testing for ARIA grid</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // ARIA grid + var cellsArray = + [ + [ true, true, false, true], + [ true, false, true, true], + [ true, false, false, true], + [ true, true, true, true], + [ true, true, true, true], + ]; + + testTableSelection("table", cellsArray); + + // //////////////////////////////////////////////////////////////////////// + // a bit strange ARIA grid + cellsArray = + [ + [ false, false], + [ false, false], + ]; + + testTableSelection("grid2", cellsArray); + + // //////////////////////////////////////////////////////////////////////// + // ARIA grid (column and row headers) + + cellsArray = + [ + [ undefined, true, false], + [ undefined, true, false], + ]; + + testTableSelection("grid3", cellsArray); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="implement nsIAccessibleTable selection methods for ARIA grids" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052">Bug 410052</a> + <a target="_blank" + title="nsHTMLTableCellAccessible is used in dojo's ARIA grid" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Bug 513848</a> + <a target="_blank" + title="ARIA columnheader/rowheader shouldn't be selectable by default" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=888247">Bug 888247</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div role="grid" id="table"> + <div role="row"> + <span role="gridcell" aria-selected="true">cell1</span> + <span role="gridcell" aria-selected="true">cell2</span> + <span role="gridcell">cell3</span> + <span role="gridcell" aria-selected="true">cell4</span> + </div> + <div role="row"> + <span role="gridcell" aria-selected="true">cell5</span> + <span role="gridcell">cell6</span> + <span role="gridcell" aria-selected="true">cell7</span> + <span role="gridcell" aria-selected="true">cell8</span> + </div> + <div role="row"> + <span role="gridcell" aria-selected="true">cell9</span> + <span role="gridcell">cell10</span> + <span role="gridcell">cell11</span> + <span role="gridcell" aria-selected="true">cell12</span> + </div> + <div role="row" aria-selected="true"> + <span role="gridcell">cell13</span> + <span role="gridcell">cell14</span> + <span role="gridcell">cell15</span> + <span role="gridcell">cell16</span> + </div> + <div role="row"> + <span role="gridcell" aria-selected="true">cell17</span> + <span role="gridcell" aria-selected="true">cell18</span> + <span role="gridcell" aria-selected="true">cell19</span> + <span role="gridcell" aria-selected="true">cell20</span> + </div> + </div> + + <div role="grid" id="grid2"> + <div role="row"> + <table role="presentation"> + <tr> + <td role="columnheader" aria-selected="false">header1</td> + <td role="columnheader" aria-selected="false">header2</td> + </tr> + </table> + </div> + <div role="row"> + <table role="presentation"> + <tr> + <td role="gridcell">cell1</td> + <td role="gridcell" tabindex="-1">cell2</td> + </tr> + </table> + </div> + </div> + + <div role="grid" id="grid3"> + <div role="row"> + <div role="columnheader" id="colheader_default">col header1</div> + <div role="columnheader" id="colheader_selected" aria-selected="true">col header2</div> + <div role="columnheader" id="colheader_notselected" aria-selected="false">col header3</div> + </div> + <div role="row"> + <div role="rowheader" id="rowheader_default">row header1</div> + <div role="rowheader" id="rowheader_selected" aria-selected="true">row header2</div> + <div role="rowheader" id="rowheader_notselected" aria-selected="false">row header3</div> + </div> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_sels_table.html b/accessible/tests/mochitest/table/test_sels_table.html new file mode 100644 index 0000000000..b0d53ab42c --- /dev/null +++ b/accessible/tests/mochitest/table/test_sels_table.html @@ -0,0 +1,155 @@ +<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en"> +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <title>nsIAccesible selection methods testing for HTML table</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="text/javascript"> + + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // table + + var cellsArray = + [ + [false, false, false, kColSpanned, false, false, false, false], + [false, false, false, false, false, false, false, kRowSpanned], + [false, false, kColSpanned, false, false, false, false, kRowSpanned], + [false, kRowSpanned, kSpanned, false, false, kRowSpanned, false, kRowSpanned], + ]; + + testTableSelection("table", cellsArray); + + var accTable = getAccessible("table", [nsIAccessibleTable]); + ok(!accTable.isProbablyForLayout(), "table is not for layout"); + + // //////////////////////////////////////////////////////////////////////// + // table instane + + cellsArray = + [ + [false, false, false, -1, -1], + [false, false, false, -1, -1], + [false, false, kColSpanned, kColSpanned, -1], + [kRowSpanned, false, false, -1, -1], + [kRowSpanned, false, kRowSpanned, false, false], + ]; + + testTableSelection("tableinsane", cellsArray); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + </head> + <body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052" + title="Fix our nsHTMLAccessibleTable class so GetIndexAt and GetRowAtIndex and GetColumnAtIndex behave consistently"> + Mozilla Bug 410052 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=501635" + title="nsHTMLTableAccessible::GetSelectedCells contains index duplicates for spanned rows or columns"> + Mozilla Bug 501635 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=501659" + title="HTML table's isRowSelected/isColumnSelected shouldn't fail if row or column has cell holes"> + Mozilla Bug 501659 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- Test Table --> + <br><br><b> Testing Table:</b><br><br> + <center> + <table cellpadding="2" cellspacing="2" border="1" width="50%" id="table"> + <tbody> + <tr> + <td><br></td> + <td><br></td> + <td rowspan="1" colspan="2"><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + <td rowspan="4" colspan="1"><br></td> + </tr> + <tr> + <td><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + </tr> + <tr> + <td><br></td> + <td rowspan="2" colspan="2">c1</td> + <td><br></td> + <td><br></td> + <td rowspan="2" colspan="1"><br></td> + <td><br></td> + </tr> + <tr> + <td><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + </tr> + </tbody> + </table> + + <table border="1" id="tableinsane"> + <thead> + <tr> + <th>col1</th> + <th>col2</th> + <th>col3</th> + </tr> + </thead> + <tbody> + <tr> + <td>1</td> + <td>2</td> + <td>3</td> + </tr> + <tr> + <td rowspan="3">4</td> + <td colspan="4">5</td> + </tr> + <tr> + <td>6</td> + <td rowspan="2">7</td> + </tr> + <tr> + <td>8</td> + <td>9</td> + <td>10</td> + </tr> + </tbody> + </table> + + </center> + </body> +</html> diff --git a/accessible/tests/mochitest/table/test_sels_tree.xhtml b/accessible/tests/mochitest/table/test_sels_tree.xhtml new file mode 100644 index 0000000000..7b93d59d47 --- /dev/null +++ b/accessible/tests/mochitest/table/test_sels_tree.xhtml @@ -0,0 +1,76 @@ +<?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="Accessible Table selection tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../table.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "debug"; + + function doTest() + { + var cellsArray = + [ + [false, false], + [false, false], + [false, false] + ]; + + testTableSelection("tree", cellsArray); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yXULTreeLoadEvent(doTest, "tree", new nsTableTreeView(3)); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727" + title="Reorganize implementation of XUL tree accessibility"> + Mozilla Bug 503727 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="debug"/> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + <treecol id="scol" flex="1" label="column 2"/> + </treecols> + <treechildren id="treechildren"/> + </tree> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/table/test_struct_ariagrid.html b/accessible/tests/mochitest/table/test_struct_ariagrid.html new file mode 100644 index 0000000000..92821e19c2 --- /dev/null +++ b/accessible/tests/mochitest/table/test_struct_ariagrid.html @@ -0,0 +1,163 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Table accessible tree and table interface tests for ARIA grid</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // Pure ARIA grid + var cellsArray = [ + [kColHeaderCell, kColHeaderCell, kColHeaderCell], + [kRowHeaderCell, kDataCell, kDataCell], + [kRowHeaderCell, kDataCell, kDataCell], + ]; + + testTableStruct("table", cellsArray); + + // //////////////////////////////////////////////////////////////////////// + // HTML table based ARIA grid + cellsArray = [ + [kColHeaderCell, kColHeaderCell, kColHeaderCell], + [kDataCell, kDataCell, kDataCell], + [kDataCell, kDataCell, kDataCell], + ]; + + testTableStruct("grid", cellsArray); + + // //////////////////////////////////////////////////////////////////////// + // ARIA grid with HTML table elements + cellsArray = [ + [kColHeaderCell, kColHeaderCell], + [kDataCell, kDataCell], + ]; + + testTableStruct("grid2", cellsArray); + + // //////////////////////////////////////////////////////////////////////// + // ARIA grid of wrong markup + cellsArray = [ ]; + testTableStruct("grid3", cellsArray); + + cellsArray = [ [] ]; + testTableStruct("grid4", cellsArray); + + // //////////////////////////////////////////////////////////////////////// + // ARIA table with tr inside a shadow root (bug 1698097). + testTableStruct("tableShadow", [ [ kDataCell ] ]); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + title="ARIA grid based on HTML table" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=491683">Mozilla Bug 491683</a> + <a target="_blank" + title="implement IAccessibleTable2" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424">Mozilla Bug 512424</a> + <a target="_blank" + title="nsHTMLTableCellAccessible is used in dojo's ARIA grid" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=513848">Mozilla Bug 513848</a> + <a target="_blank" + title="Crash [@ AccIterator::GetNext()]" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=675861">Mozilla Bug 675861</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- Not usual markup to avoid text accessible between cell accessibles --> + <div id="table" role="grid"> + <div role="row"><span + id="table_ch_1" role="columnheader">col_1</span><span + id="table_ch_2" role="columnheader">col_2</span><span + id="table_ch_3" role="columnheader">col_3</span></div> + <div role="row"><span + id="table_rh_1" role="rowheader">row_1</span><span + id="table_dc_1" role="gridcell">cell1</span><span + id="table_dc_2" role="gridcell">cell2</span></div> + <div role="row"><span + id="table_rh_2" role="rowheader">row_2</span><span + id="table_dc_3" role="gridcell">cell3</span><span + id="table_dc_4" role="gridcell">cell4</span></div> + </div> + + <table role="grid" id="grid" border="1" cellpadding="10" cellspacing="0"> + <thead> + <tr role="row"> + <th role="columnheader">subject</td> + <th role="columnheader">sender</th> + <th role="columnheader">date</th> + </tr> + </thead> + <tbody> + <tr role="row"> + <td role="gridcell" tabindex="0">about everything</td> + <td role="gridcell">president</td> + <td role="gridcell">today</td> + </tr> + <tr role="row"> + <td role="gridcell">new bugs</td> + <td role="gridcell">mozilla team</td> + <td role="gridcell">today</td> + </tr> + </tbody> + </table> + + <!-- ARIA grid containing presentational HTML:table with HTML:td used as ARIA + grid cells (focusable and not focusable cells) --> + <div role="grid" id="grid2"> + <div role="row"> + <table role="presentation"> + <tr> + <td role="columnheader">header1</td> + <td role="columnheader">header2</td> + </tr> + </table> + </div> + <div role="row"> + <table role="presentation"> + <tr> + <td role="gridcell">cell1</td> + <td role="gridcell" tabindex="-1">cell2</td> + </tr> + </table> + </div> + </div> + + <!-- Wrong markup ARIA grid --> + <div role="grid" id="grid3"></div> + <div role="grid" id="grid4"><div role="row"></div></div> + + <div id="tableShadow" role="table"></div> + <script> + let host = document.getElementById("tableShadow"); + let shadow = host.attachShadow({mode: "open"}); + let tr = document.createElement("tr"); + shadow.append(tr); + tr.setAttribute("role", "row"); + let td = document.createElement("td"); + tr.append(td); + td.textContent = "test"; + </script> +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_struct_ariatreegrid.html b/accessible/tests/mochitest/table/test_struct_ariatreegrid.html new file mode 100644 index 0000000000..8d36a2b350 --- /dev/null +++ b/accessible/tests/mochitest/table/test_struct_ariatreegrid.html @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Table accessible tree and table interface tests for ARIA tree grid</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // HTML based ARIA tree grid + + var cellsArray = [ + [kColHeaderCell, kColHeaderCell, kColHeaderCell], + [kDataCell, kDataCell, kDataCell], + [kDataCell, kDataCell, kDataCell], + ]; + + testTableStruct("treegrid", cellsArray, kNoColumnHeader, "", "", + kTreeTable); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + title="ARIA treegrid role on HTML:table makes thead/tbody accessible" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=491683">Mozilla Bug 516133</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <table role="treegrid" id="treegrid" + border="1" cellpadding="10" cellspacing="0"> + <thead> + <tr role="row"> + <th role="columnheader">subject</td> + <th role="columnheader">sender</th> + <th role="columnheader">date</th> + </tr> + </thead> + <tbody> + <tr role="row"> + <td role="gridcell">about everything</td> + <td role="gridcell">president</td> + <td role="gridcell">today</td> + </tr> + <tr role="row"> + <td role="gridcell">new bugs</td> + <td role="gridcell">mozilla team</td> + <td role="gridcell">today</td> + </tr> + </tbody> + </table> + +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_struct_table.html b/accessible/tests/mochitest/table/test_struct_table.html new file mode 100644 index 0000000000..46bad05c62 --- /dev/null +++ b/accessible/tests/mochitest/table/test_struct_table.html @@ -0,0 +1,217 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Table accessible tree and table interface tests for HTML tables</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // column headers from thead and tfoot + + cellsArray = [ + [kColHeaderCell, kColHeaderCell, kColSpanned], + [kRowSpanned, kColHeaderCell, kColHeaderCell], + [kDataCell, kDataCell, kDataCell], + [kColHeaderCell, kColHeaderCell, kColHeaderCell], + ]; + + testTableStruct("table1", cellsArray); + + // //////////////////////////////////////////////////////////////////////// + // row and column headers from thead and @scope + + var cellsArray = [ + [kColHeaderCell, kColHeaderCell, kColHeaderCell], + [kRowHeaderCell, kDataCell, kDataCell], + [kRowHeaderCell, kDataCell, kDataCell], + ]; + + testTableStruct("table2", cellsArray); + + // //////////////////////////////////////////////////////////////////////// + // caption and @summary + + cellsArray = [ + [kColHeaderCell, kColHeaderCell, kColHeaderCell, kColHeaderCell], + [kRowHeaderCell, kDataCell, kDataCell, kDataCell], + [kRowHeaderCell, kDataCell, kDataCell, kDataCell], + ]; + + testTableStruct("table3", cellsArray, kNoColumnHeader, + "Test Table", + "this is a test table for nsIAccessibleTable"); + + // //////////////////////////////////////////////////////////////////////// + // row and column spans + + cellsArray = [ + [kDataCell, kDataCell, kDataCell, kColSpanned, kDataCell, kDataCell, kDataCell, kDataCell], + [kDataCell, kDataCell, kDataCell, kDataCell, kDataCell, kDataCell, kDataCell, kRowSpanned], + [kDataCell, kDataCell, kColSpanned, kDataCell, kDataCell, kDataCell, kDataCell, kRowSpanned], + [kDataCell, kRowSpanned, kSpanned, kDataCell, kDataCell, kRowSpanned, kDataCell, kRowSpanned], + ]; + + testTableStruct("table4", cellsArray); + + // //////////////////////////////////////////////////////////////////////// + // Table with a cell that has display: block; style + + cellsArray = [ + [kRowHeaderCell, kDataCell], + ]; + + testTableStruct("table5", cellsArray); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Fix our nsHTMLAccessibleTable class so GetIndexAt and GetRowAtIndex and GetColumnAtIndex behave consistently" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052">Mozilla Bug 410052</a> + <a target="_blank" + title="GetCellDataAt callers that expect an error if no cell is found are wrong" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=417912">Mozilla Bug 417912</a> + <a target="_blank" + title="create accessibles for HTML tr" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=493695">Mozilla Bug 493695</a> + <a target="_blank" + title="implement IAccessibleTable2" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424">Mozilla Bug 512424</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <table id="table1"> + <thead> + <tr> + <th rowspan="2">col1</th><th colspan="2">col2</th> + </tr> + <tr> + <th>col2sub1</th><th>col2sub2</th> + </tr> + </thead> + <tbody> + <tr> + <td>cell1</td><td>cell2</td><td>cell3</td> + </tr> + </tbody> + <tfoot> + <tr> + <th>col1</th><th>col2</th><th>col3</th> + </tr> + </tfoot> + </table> + + <table id="table2"> + <thead> + <tr> + <th id="table1_0">col1</th> + <th id="table1_1">col2</th> + <td id="table1_2" scope="col">col3</td> + </tr> + </thead> + <tbody> + <tr> + <th id="table1_3">row1</th> + <td id="table1_4">cell1</td> + <td id="table1_5">cell2</td> + </tr> + <tr> + <td id="table1_6" scope="row">row2</td> + <td id="table1_7">cell3</td> + <td id="table1_8">cell4</td> + </tr> + </tbody> + </table> + + <table id="table3" border="1" + summary="this is a test table for nsIAccessibleTable"> + <caption>Test Table</caption> + <thead> + <tr> + <th></th> + <th>columnHeader_1</th> + <th id ="col2a">columnHeader_2</th> + <th>columnHeader_3</th> + </tr> + </thead> + <tr> + <th id="row2a">rowHeader_1</th> + <td id="row2b">row1_column1</td> + <td id ="col2b">row1_column2</td> + <td id="row2c">row1_column3</td> + </tr> + <tr> + <th>rowHeader_2</th> + <td>row2_column1</td> + <td id ="col2c">row2_column2</td> + <td>row2_column3</td> + </tr> + </table> + + <table id="table4" cellpadding="2" cellspacing="2" border="1" width="50%"> + <tbody> + <tr> + <td><br></td> + <td><br></td> + <td rowspan="1" colspan="2"><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + <td rowspan="4" colspan="1"><br></td> + </tr> + <tr> + <td><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + </tr> + <tr> + <td><br></td> + <td rowspan="2" colspan="2">c1</td> + <td><br></td> + <td><br></td> + <td rowspan="2" colspan="1"><br></td> + <td><br></td> + </tr> + <tr> + <td><br></td> + <td><br></td> + <td><br></td> + <td><br></td> + </tr> + </tbody> + </table> + + <table id="table5"> + <tr> + <th>a</th> + <td style="display: block;">b</td> + </tr> + </table> + +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_struct_tree.xhtml b/accessible/tests/mochitest/table/test_struct_tree.xhtml new file mode 100644 index 0000000000..6710bd2e8b --- /dev/null +++ b/accessible/tests/mochitest/table/test_struct_tree.xhtml @@ -0,0 +1,73 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Table accessible tree and table interface tests for XUL trees"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../table.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "debug"; + + function doTest() + { + var cellsArray = [ + [kDataCell, kDataCell], + [kDataCell, kDataCell], + [kDataCell, kDataCell] + ]; + + testTableStruct("table", cellsArray, kTreeColumnHeader); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yXULTreeLoadEvent(doTest, "table", new nsTableTreeView(3)); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424" + title="implement IAccessibleTable2"> + Mozilla Bug 512424 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="debug"/> + <tree id="table" flex="1"> + <treecols> + <treecol id="col" flex="1" label="column"/> + <treecol id="scol" flex="1" label="column 2"/> + </treecols> + <treechildren id="treechildren"/> + </tree> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/table/test_table_1.html b/accessible/tests/mochitest/table/test_table_1.html new file mode 100644 index 0000000000..b1331a5cc3 --- /dev/null +++ b/accessible/tests/mochitest/table/test_table_1.html @@ -0,0 +1,107 @@ +<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en"> +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + + <script type="application/javascript"> + +function doTest() { + var accTable = getAccessible("table", [nsIAccessibleTable]); + + var s = window.getSelection(); + if (s.rangeCount > 0) + s.removeAllRanges(); + + var cell = getNode("col2b"); + var range = document.createRange(); + range.selectNode(cell); + s.addRange(range); + + is(accTable.selectedCellCount, 1, "only one cell selected"); + cell = getNode("col2a"); + range = document.createRange(); + range.selectNode(cell); + s.addRange(range); + cell = getNode("col2c"); + range = document.createRange(); + range.selectNode(cell); + s.addRange(range); + is(accTable.selectedColumnCount, 1, "only one column selected"); + + cell = getNode("row2a"); + range = document.createRange(); + range.selectNode(cell); + s.addRange(range); + cell = getNode("row2b"); + range = document.createRange(); + range.selectNode(cell); + s.addRange(range); + range = document.createRange(); + cell = getNode("row2c"); + range.selectNode(cell); + s.addRange(range); + + is(accTable.selectedRowCount, 1, "no cells selected"); + + // These shouldn't throw. + try { + accTable.getColumnDescription(1); + accTable.getRowDescription(1); + } catch (ex) { + ok(false, "getColumnDescription/getRowDescription shouldn't throw."); + } + SimpleTest.finish(); +} +SimpleTest.waitForExplicitFinish(); +addA11yLoadEvent(doTest); + </script> + </head> + <body > + + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=410052">Mozilla Bug 410052</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=760878" + title="decomtaminate Get Row / Column Description() on accessible tables"> + Mozilla Bug 760878 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- Test Table --> + <br><br><b> Testing Table:</b><br><br> + <center> + <table id="table" border="1" + summary="this is a test table for nsIAccessibleTable" > + <caption>Test Table</caption> + <thead> + <tr> + <th></th> + <th>columnHeader_1</th> + <th id ="col2a">columnHeader_2</th> + <th>columnHeader_3</th> + </tr> + </thead> + <tr> + <th id="row2a">rowHeader_1</th> + <td id="row2b">row1_column1</td> + <td id ="col2b">row1_column2</td> + <td id="row2c">row1_column3</td> + </tr> + <tr> + <th>rowHeader_2</th> + <td>row2_column1</td> + <td id ="col2c">row2_column2</td> + <td>row2_column3</td> + </tr> + </table> + </center> + </body> +</html> diff --git a/accessible/tests/mochitest/table/test_table_2.html b/accessible/tests/mochitest/table/test_table_2.html new file mode 100644 index 0000000000..6bd7c56b37 --- /dev/null +++ b/accessible/tests/mochitest/table/test_table_2.html @@ -0,0 +1,87 @@ +<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en"> +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="text/javascript"> + +function doTest() { + // Test table with role=alert. + var tableInterfaceExposed = true; + var accTable3 = getAccessible("table3", [nsIAccessibleTable], null, DONOTFAIL_IF_NO_INTERFACE); + if (!accTable3) + tableInterfaceExposed = false; + ok(tableInterfaceExposed, "table interface is not exposed"); + + if (tableInterfaceExposed) { + testRole(accTable3, ROLE_ALERT); + + is(accTable3.getCellAt(0, 0).firstChild.name, "cell0", "wrong cell"); + is(accTable3.getCellAt(0, 1).firstChild.name, "cell1", "wrong cell"); + } + + // Test table with role=log and aria property in tr. We create accessible for + // tr in this case. + tableInterfaceExposed = true; + var accTable4 = getAccessible("table4", [nsIAccessibleTable], null, DONOTFAIL_IF_NO_INTERFACE); + if (!accTable4) + tableInterfaceExposed = false; + ok(tableInterfaceExposed, "table interface is not exposed"); + + if (tableInterfaceExposed) { + let accNotCreated = (!isAccessible("tr")); + ok(!accNotCreated, "missed tr accessible"); + + testRole(accTable4, ROLE_TABLE); + + is(accTable4.getCellAt(0, 0).firstChild.name, "cell0", "wrong cell"); + is(accTable4.getCellAt(0, 1).firstChild.name, "cell1", "wrong cell"); + is(accTable4.getCellAt(1, 0).firstChild.name, "cell2", "wrong cell"); + is(accTable4.getCellAt(1, 1).firstChild.name, "cell3", "wrong cell"); + } + + SimpleTest.finish(); +} +SimpleTest.waitForExplicitFinish(); +addA11yLoadEvent(doTest); + </script> + </head> + + <body > + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=419811">Mozilla Bug 419811</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- Test Table --> + <br><br><b> Testing Table:</b><br><br> + <center> + <table id="table3" border="1" role="alert"> + <tr> + <td>cell0</td> + <td>cell1</td> + </tr> + </table> + + <table id="table4" border="1" role="log"> + <tr aria-live="polite" id="tr"> + <td>cell0</td> + <td>cell1</td> + </tr> + <tr> + <td>cell2</td> + <td>cell3</td> + </tr> + </table> + + </center> + </body> +</html> diff --git a/accessible/tests/mochitest/table/test_table_mutation.html b/accessible/tests/mochitest/table/test_table_mutation.html new file mode 100644 index 0000000000..671e627244 --- /dev/null +++ b/accessible/tests/mochitest/table/test_table_mutation.html @@ -0,0 +1,100 @@ +<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> + <title>Table mutation</title> + <meta http-equiv="content-type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../table.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + + async function doTest() { + let headers = [ + { + cell: "t1r1c1", + columnHeaderCells: [], + rowHeaderCells: [], + }, + // t1r2 is hidden + { + cell: "t1r3c1", + columnHeaderCells: ["t1r1c1"], + rowHeaderCells: [], + }, + ]; + testHeaderCells(headers); + + info("Remove row"); + let reordered = waitForEvent(EVENT_REORDER, "t1"); + getNode("t1r1").hidden = true; + await reordered; + headers = [ + // t1r1 and t1r2 are hidden + { + cell: "t1r3c1", + columnHeaderCells: [], + rowHeaderCells: [], + }, + ]; + testHeaderCells(headers); + + info("Add rows"); + reordered = waitForEvent(EVENT_REORDER, "t1"); + getNode("t1r1").hidden = false; + getNode("t1r2").hidden = false; + await reordered; + headers = [ + { + cell: "t1r1c1", + columnHeaderCells: [], + rowHeaderCells: [], + }, + { + cell: "t1r2c1", + columnHeaderCells: ["t1r1c1"], + rowHeaderCells: [], + }, + { + cell: "t1r3c1", + columnHeaderCells: ["t1r2c1", "t1r1c1"], + rowHeaderCells: [], + }, + ]; + testHeaderCells(headers); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <table id="t1"> + <tr id="t1r1"> + <th id="t1r1c1"></th> + </tr> + <tr id="t1r2" hidden> + <th id="t1r2c1"></th> + </tr> + <tr id="t1r3"> + <td id="t1r3c1"></td> + </tr> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/test_OuterDocAccessible.html b/accessible/tests/mochitest/test_OuterDocAccessible.html new file mode 100644 index 0000000000..b7a719aba5 --- /dev/null +++ b/accessible/tests/mochitest/test_OuterDocAccessible.html @@ -0,0 +1,87 @@ +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=441519 +--> +<head> + <title>nsOuterDocAccessible chrome tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="common.js"></script> + <script type="application/javascript" + src="states.js"></script> + <script type="application/javascript" + src="role.js"></script> + + <script type="application/javascript"> + // needed error return value + const ns_error_invalid_arg = Cr.NS_ERROR_INVALID_ARG; + + function doTest() { + // Get accessible for body tag. + var docAcc = getAccessible(document); + + if (docAcc) { + var outerDocAcc = getAccessible(docAcc.parent); + + if (outerDocAcc) { + testRole(outerDocAcc, ROLE_INTERNAL_FRAME); + + // check if it is focusable. + testStates(outerDocAcc, STATE_FOCUSABLE, 0); + + // see bug 428954: No name wanted for internal frame + is(outerDocAcc.name, null, "Wrong name for internal frame!"); + + // see bug 440770, no actions wanted on outer doc + is(outerDocAcc.actionCount, 0, + "Wrong number of actions for internal frame!"); + + try { + outerDocAcc.getActionName(0); + throw new Error("No exception thrown for actionName!"); + } catch (e) { + is(e.result, ns_error_invalid_arg, + "Wrong return value for actionName call!"); + } + + try { + outerDocAcc.getActionDescription(0); + throw new Error("No exception thrown for actionDescription!"); + } catch (e) { + is(e.result, ns_error_invalid_arg, + "Wrong return value for actionDescription call!"); + } + + try { + outerDocAcc.doAction(0); + throw new Error("No exception thrown for doAction!"); + } catch (e) { + is(e.result, ns_error_invalid_arg, + "Wrong return value for doAction call!"); + } + } + } + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=441519" + title="nsOuterDocAccessible chrome tests"> + Mozilla Bug 441519 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/test_aria_token_attrs.html b/accessible/tests/mochitest/test_aria_token_attrs.html new file mode 100644 index 0000000000..ad0bc25970 --- /dev/null +++ b/accessible/tests/mochitest/test_aria_token_attrs.html @@ -0,0 +1,417 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=452388 +--> +<head> + <title>An NMTOKEN based ARIA property is undefined if the ARIA attribute is not present, or is set to "" or "undefined"</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="common.js"></script> + <script type="application/javascript" + src="role.js"></script> + <script type="application/javascript" + src="states.js"></script> + + <script type="application/javascript"> + function doTest() { + // test aria-pressed state mapping to roles PUSHBUTTON vs TOGGLEBUTTON + testRole("button_pressed_true", ROLE_TOGGLE_BUTTON); + testRole("button_pressed_false", ROLE_TOGGLE_BUTTON); + testRole("button_pressed_empty", ROLE_PUSHBUTTON); + testRole("button_pressed_undefined", ROLE_PUSHBUTTON); + testRole("button_pressed_absent", ROLE_PUSHBUTTON); + + // test button aria-pressed states + testStates("button_pressed_true", STATE_PRESSED, 0, STATE_CHECKABLE); + testStates("button_pressed_false", 0, 0, STATE_CHECKABLE | STATE_PRESSED); + testStates("button_pressed_empty", 0, 0, STATE_PRESSED | STATE_CHECKABLE); + testStates("button_pressed_undefined", 0, 0, STATE_PRESSED | STATE_CHECKABLE); + testStates("button_pressed_absent", 0, 0, STATE_PRESSED | STATE_CHECKABLE); + + // test (checkbox) checkable and checked states + testStates("checkbox_checked_true", (STATE_CHECKABLE | STATE_CHECKED)); + testStates("checkbox_checked_mixed", (STATE_CHECKABLE | STATE_MIXED), 0, STATE_CHECKED); + testStates("checkbox_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("checkbox_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("checkbox_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("checkbox_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED); + + // test native checkbox checked state and aria-checked state (if conflict, native wins) + testStates("native_checkbox_nativechecked_ariatrue", (STATE_CHECKABLE | STATE_CHECKED)); + testStates("native_checkbox_nativechecked_ariafalse", (STATE_CHECKABLE | STATE_CHECKED)); + testStates("native_checkbox_nativechecked_ariaempty", (STATE_CHECKABLE | STATE_CHECKED)); + testStates("native_checkbox_nativechecked_ariaundefined", (STATE_CHECKABLE | STATE_CHECKED)); + testStates("native_checkbox_nativechecked_ariaabsent", (STATE_CHECKABLE | STATE_CHECKED)); + + testStates("native_checkbox_nativeunchecked_ariatrue", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("native_checkbox_nativeunchecked_ariafalse", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("native_checkbox_nativeunchecked_ariaempty", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("native_checkbox_nativeunchecked_ariaundefined", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("native_checkbox_nativeunchecked_ariaabsent", STATE_CHECKABLE, 0, STATE_CHECKED); + + // test (checkbox) readonly states + testStates("checkbox_readonly_true", STATE_READONLY); + testStates("checkbox_readonly_false", 0, 0, STATE_READONLY); + testStates("checkbox_readonly_empty", 0, 0, STATE_READONLY); + testStates("checkbox_readonly_undefined", 0, 0, STATE_READONLY); + testStates("checkbox_readonly_absent", 0, 0, STATE_READONLY); + + // test (checkbox) required states + testStates("checkbox_required_true", STATE_REQUIRED); + testStates("checkbox_required_false", 0, 0, STATE_REQUIRED); + testStates("checkbox_required_empty", 0, 0, STATE_REQUIRED); + testStates("checkbox_required_undefined", 0, 0, STATE_REQUIRED); + testStates("checkbox_required_absent", 0, 0, STATE_REQUIRED); + + // test (checkbox) invalid states + testStates("checkbox_invalid_true", STATE_INVALID); + testStates("checkbox_invalid_false", 0, 0, STATE_INVALID); + testStates("checkbox_invalid_empty", 0, 0, STATE_INVALID); + testStates("checkbox_invalid_undefined", 0, 0, STATE_INVALID); + testStates("checkbox_invalid_absent", 0, 0, STATE_INVALID); + + // test (checkbox) disabled states + testStates("checkbox_disabled_true", STATE_UNAVAILABLE); + testStates("checkbox_disabled_false", 0, 0, STATE_UNAVAILABLE); + testStates("checkbox_disabled_empty", 0, 0, STATE_UNAVAILABLE); + testStates("checkbox_disabled_undefined", 0, 0, STATE_UNAVAILABLE); + testStates("checkbox_disabled_absent", 0, 0, STATE_UNAVAILABLE); + + // test (listbox) multiselectable states + testStates("listbox_multiselectable_true", STATE_MULTISELECTABLE | STATE_EXTSELECTABLE); + testStates("listbox_multiselectable_false", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE); + testStates("listbox_multiselectable_empty", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE); + testStates("listbox_multiselectable_undefined", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE); + testStates("listbox_multiselectable_absent", 0, 0, STATE_MULTISELECTABLE | STATE_EXTSELECTABLE); + + // test (option) checkable and checked states + testStates("option_checked_true", (STATE_CHECKABLE | STATE_CHECKED)); + testStates("option_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("option_checked_empty", 0, 0, STATE_CHECKABLE | STATE_CHECKED); + testStates("option_checked_undefined", 0, 0, STATE_CHECKABLE | STATE_CHECKED); + testStates("option_checked_absent", 0, 0, STATE_CHECKABLE | STATE_CHECKED); + + // test (menuitem) checkable and checked states, which are unsupported on this role + testStates("menuitem_checked_true", 0, 0, (STATE_CHECKABLE | STATE_CHECKED)); + testStates("menuitem_checked_mixed", 0, 0, (STATE_CHECKABLE | STATE_CHECKED | STATE_MIXED)); + testStates("menuitem_checked_false", 0, 0, (STATE_CHECKABLE | STATE_CHECKED)); + testStates("menuitem_checked_empty", 0, 0, (STATE_CHECKABLE | STATE_CHECKED)); + testStates("menuitem_checked_undefined", 0, 0, (STATE_CHECKABLE | STATE_CHECKED)); + testStates("menuitem_checked_absent", 0, 0, (STATE_CHECKABLE | STATE_CHECKED)); + + // test (menuitemcheckbox) checkable and checked states + testStates("menuitemcheckbox_checked_true", (STATE_CHECKABLE | STATE_CHECKED)); + testStates("menuitemcheckbox_checked_mixed", (STATE_CHECKABLE | STATE_MIXED), 0, STATE_CHECKED); + testStates("menuitemcheckbox_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("menuitemcheckbox_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("menuitemcheckbox_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("menuitemcheckbox_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED); + + // test (menuitemcheckbox) readonly states + testStates("menuitemcheckbox_readonly_true", STATE_READONLY); + testStates("menuitemcheckbox_readonly_false", 0, 0, STATE_READONLY); + testStates("menuitemcheckbox_readonly_empty", 0, 0, STATE_READONLY); + testStates("menuitemcheckbox_readonly_undefined", 0, 0, STATE_READONLY); + testStates("menuitemcheckbox_readonly_absent", 0, 0, STATE_READONLY); + + // test (menuitemradio) checkable and checked states + testStates("menuitemradio_checked_true", (STATE_CHECKABLE | STATE_CHECKED)); + testStates("menuitemradio_checked_mixed", STATE_CHECKABLE, 0, (STATE_MIXED | STATE_CHECKED)); + testStates("menuitemradio_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("menuitemradio_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("menuitemradio_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("menuitemradio_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED); + + // test (menuitemradio) readonly states + testStates("menuitemradio_readonly_true", STATE_READONLY); + testStates("menuitemradio_readonly_false", 0, 0, STATE_READONLY); + testStates("menuitemradio_readonly_empty", 0, 0, STATE_READONLY); + testStates("menuitemradio_readonly_undefined", 0, 0, STATE_READONLY); + testStates("menuitemradio_readonly_absent", 0, 0, STATE_READONLY); + + // test (radio) checkable and checked states + testStates("radio_checked_true", (STATE_CHECKABLE | STATE_CHECKED)); + testStates("radio_checked_mixed", STATE_CHECKABLE, 0, (STATE_MIXED | STATE_CHECKED)); + testStates("radio_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("radio_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("radio_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED); + testStates("radio_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED); + + // test (radiogroup) readonly states + testStates("radiogroup_readonly_true", STATE_READONLY); + testStates("radiogroup_readonly_false", 0, 0, STATE_READONLY); + testStates("radiogroup_readonly_empty", 0, 0, STATE_READONLY); + testStates("radiogroup_readonly_undefined", 0, 0, STATE_READONLY); + testStates("radiogroup_readonly_absent", 0, 0, STATE_READONLY); + + // test (switch) readonly states + testStates("switch_readonly_true", STATE_READONLY); + testStates("switch_readonly_false", 0, 0, STATE_READONLY); + testStates("switch_readonly_empty", 0, 0, STATE_READONLY); + testStates("switch_readonly_undefined", 0, 0, STATE_READONLY); + testStates("switch_readonly_absent", 0, 0, STATE_READONLY); + + // test (textbox) multiline states + testStates("textbox_multiline_true", 0, EXT_STATE_MULTI_LINE); + testStates("textbox_multiline_false", 0, EXT_STATE_SINGLE_LINE); + testStates("textbox_multiline_empty", 0, EXT_STATE_SINGLE_LINE); + testStates("textbox_multiline_undefined", 0, EXT_STATE_SINGLE_LINE); + testStates("textbox_multiline_absent", 0, EXT_STATE_SINGLE_LINE); + + // test (textbox) readonly states + testStates("textbox_readonly_true", STATE_READONLY); + testStates("textbox_readonly_false", 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("textbox_readonly_empty", 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("textbox_readonly_undefined", 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("textbox_readonly_absent", 0, EXT_STATE_EDITABLE, STATE_READONLY); + + // test native textbox readonly state and aria-readonly state (if conflict, native wins) + testStates("native_textbox_nativereadonly_ariatrue", STATE_READONLY); + testStates("native_textbox_nativereadonly_ariafalse", STATE_READONLY); + testStates("native_textbox_nativereadonly_ariaempty", STATE_READONLY); + testStates("native_textbox_nativereadonly_ariaundefined", STATE_READONLY); + testStates("native_textbox_nativereadonly_ariaabsent", STATE_READONLY); + + testStates("native_textbox_nativeeditable_ariatrue", 0, 0, STATE_READONLY); + testStates("native_textbox_nativeeditable_ariafalse", 0, 0, STATE_READONLY); + testStates("native_textbox_nativeeditable_ariaempty", 0, 0, STATE_READONLY); + testStates("native_textbox_nativeeditable_ariaundefined", 0, 0, STATE_READONLY); + testStates("native_textbox_nativeeditable_ariaabsent", 0, 0, STATE_READONLY); + + // test (treeitem) selectable and selected states + testStates("treeitem_selected_true", (STATE_SELECTABLE | STATE_SELECTED)); + testStates("treeitem_selected_false", STATE_SELECTABLE, 0, STATE_SELECTED); + testStates("treeitem_selected_empty", STATE_SELECTABLE, 0, STATE_SELECTED); + testStates("treeitem_selected_undefined", STATE_SELECTABLE, 0, STATE_SELECTED); + testStates("treeitem_selected_absent", STATE_SELECTABLE, 0, STATE_SELECTED); + + // test (treeitem) haspopup states + testStates("treeitem_haspopup_true", STATE_HASPOPUP); + testStates("treeitem_haspopup_false", 0, 0, STATE_HASPOPUP); + testStates("treeitem_haspopup_empty", 0, 0, STATE_HASPOPUP); + testStates("treeitem_haspopup_undefined", 0, 0, STATE_HASPOPUP); + testStates("treeitem_haspopup_absent", 0, 0, STATE_HASPOPUP); + + // test (treeitem) expandable and expanded/collapsed states + testStates("treeitem_expanded_true", STATE_EXPANDED, EXT_STATE_EXPANDABLE); + testStates("treeitem_expanded_false", STATE_COLLAPSED, EXT_STATE_EXPANDABLE); + testStates("treeitem_expanded_empty", 0, 0, STATE_EXPANDED | STATE_COLLAPSED, EXT_STATE_EXPANDABLE); + testStates("treeitem_expanded_undefined", 0, 0, STATE_EXPANDED | STATE_COLLAPSED, EXT_STATE_EXPANDABLE); + testStates("treeitem_expanded_absent", 0, 0, STATE_EXPANDED | STATE_COLLAPSED, EXT_STATE_EXPANDABLE); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=452388"> + Mozilla Bug 452388 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=499653" + title="Unify ARIA state attributes mapping rules"> + Mozilla Bug 499653 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=989958" + title="Pressed state is not exposed on a button element with aria-pressed attribute" + Mozilla Bug 989958 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="button_pressed_true" role="button" aria-pressed="true">This button has aria-pressed="true" and should get ROLE_TOGGLE_BUTTON. It should also get STATE_PRESSED.</div> + <div id="button_pressed_false" role="button" aria-pressed="false">This button has aria-pressed="false" and should get ROLE_TOGGLE_BUTTON.</div> + <div id="button_pressed_empty" role="button" aria-pressed="">This button has aria-pressed="" and should <emph>not</emph> get ROLE_BUTTON.</div> + <div id="button_pressed_undefined" role="button" aria-pressed="undefined">This button has aria-pressed="undefined" and should <emph>not</emph> get ROLE_TOGGLE_BUTTON.</div> + <div id="button_pressed_absent" role="button">This button has <emph>no</emph> aria-pressed attribute and should <emph>not</emph> get ROLE_TOGGLE_BUTTON.</div> + + <div id="checkbox_checked_true" role="checkbox" aria-checked="true">This checkbox has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div> + <div id="checkbox_checked_mixed" role="checkbox" aria-checked="mixed">This checkbox has aria-checked="mixed" and should get STATE_CHECKABLE. It should also get STATE_MIXED.</div> + <div id="checkbox_checked_false" role="checkbox" aria-checked="false">This checkbox has aria-checked="false" and should get STATE_CHECKABLE.</div> + <div id="checkbox_checked_empty" role="checkbox" aria-checked="">This checkbox has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div> + <div id="checkbox_checked_undefined" role="checkbox" aria-checked="undefined">This checkbox has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div> + <div id="checkbox_checked_absent" role="checkbox">This checkbox has <emph>no</emph> aria-checked attribute and should get STATE_CHECKABLE.</div> + + <form action=""> + <input id="native_checkbox_nativechecked_ariatrue" type="checkbox" checked="checked" aria-checked="true"/> + <input id="native_checkbox_nativechecked_ariafalse" type="checkbox" checked="checked" aria-checked="false"/> + <input id="native_checkbox_nativechecked_ariaempty" type="checkbox" checked="checked" aria-checked=""/> + <input id="native_checkbox_nativechecked_ariaundefined" type="checkbox" checked="checked" aria-checked="undefined"/> + <input id="native_checkbox_nativechecked_ariaabsent" type="checkbox" checked="checked"/> + + <input id="native_checkbox_nativeunchecked_ariatrue" type="checkbox" aria-checked="true"/> + <input id="native_checkbox_nativeunchecked_ariafalse" type="checkbox" aria-checked="false"/> + <input id="native_checkbox_nativeunchecked_ariaempty" type="checkbox" aria-checked=""/> + <input id="native_checkbox_nativeunchecked_ariaundefined" type="checkbox" aria-checked="undefined"/> + <input id="native_checkbox_nativeunchecked_ariaabsent" type="checkbox"/> + </form> + + <div id="checkbox_readonly_true" role="checkbox" aria-readonly="true">This checkbox has aria-readonly="true" and should get STATE_READONLY.</div> + <div id="checkbox_readonly_false" role="checkbox" aria-readonly="false">This checkbox has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="checkbox_readonly_empty" role="checkbox" aria-readonly="">This checkbox has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="checkbox_readonly_undefined" role="checkbox" aria-readonly="undefined">This checkbox has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="checkbox_readonly_absent" role="checkbox">This checkbox has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div> + + <div id="checkbox_required_true" role="checkbox" aria-required="true">This checkbox has aria-required="true" and should get STATE_REQUIRED.</div> + <div id="checkbox_required_false" role="checkbox" aria-required="false">This checkbox has aria-required="false" and should <emph>not</emph> get STATE_REQUIRED.</div> + <div id="checkbox_required_empty" role="checkbox" aria-required="">This checkbox has aria-required="" and should <emph>not</emph> get STATE_REQUIRED.</div> + <div id="checkbox_required_undefined" role="checkbox" aria-required="undefined">This checkbox has aria-required="undefined" and should <emph>not</emph> get STATE_REQUIRED.</div> + <div id="checkbox_required_absent" role="checkbox">This checkbox has <emph>no</emph> aria-required attribute and should <emph>not</emph> get STATE_REQUIRED.</div> + + <div id="checkbox_invalid_true" role="checkbox" aria-invalid="true">This checkbox has aria-invalid="true" and should get STATE_INVALID.</div> + <div id="checkbox_invalid_false" role="checkbox" aria-invalid="false">This checkbox has aria-invalid="false" and should <emph>not</emph> get STATE_INVALID.</div> + <div id="checkbox_invalid_empty" role="checkbox" aria-invalid="">This checkbox has aria-invalid="" and should <emph>not</emph> get STATE_INVALID.</div> + <div id="checkbox_invalid_undefined" role="checkbox" aria-invalid="undefined">This checkbox has aria-invalid="undefined" and should <emph>not</emph> get STATE_INVALID.</div> + <div id="checkbox_invalid_absent" role="checkbox">This checkbox has <emph>no</emph> aria-invalid attribute and should <emph>not</emph> get STATE_INVALID.</div> + + <div id="checkbox_disabled_true" role="checkbox" aria-disabled="true" tabindex="0">This checkbox has aria-disabled="true" and should get STATE_DISABLED.</div> + <div id="checkbox_disabled_false" role="checkbox" aria-disabled="false">This checkbox has aria-disabled="false" and should <emph>not</emph> get STATE_DISABLED.</div> + <div id="checkbox_disabled_empty" role="checkbox" aria-disabled="">This checkbox has aria-disabled="" and should <emph>not</emph> get STATE_DISABLED.</div> + <div id="checkbox_disabled_undefined" role="checkbox" aria-disabled="undefined">This checkbox has aria-disabled="undefined" and should <emph>not</emph> get STATE_DISABLED.</div> + <div id="checkbox_disabled_absent" role="checkbox">This checkbox has <emph>no</emph> aria-disabled attribute and should <emph>not</emph> get STATE_DISABLED.</div> + + <div id="listbox_multiselectable_true" role="listbox" aria-multiselectable="true"> + <div id="option_checked_true" role="option" aria-checked="true">item</div> + </div> + <div id="listbox_multiselectable_false" role="listbox" aria-multiselectable="false"> + <div id="option_checked_false" role="option" aria-checked="false">item</div> + </div> + <div id="listbox_multiselectable_empty" role="listbox" aria-multiselectable=""> + <div id="option_checked_empty" role="option" aria-checked="">item</div> + </div> + <div id="listbox_multiselectable_undefined" role="listbox" aria-multiselectable="undefined"> + <div id="option_checked_undefined" role="option" aria-checked="undefined">item</div> + </div> + <div id="listbox_multiselectable_absent" role="listbox"> + <div id="option_checked_absent" role="option">item</div> + </div> + + <div role="menu"> + <div id="menuitem_checked_true" role="menuitem" aria-checked="true">Generic menuitems don't support aria-checked.</div> + <div id="menuitem_checked_mixed" role="menuitem" aria-checked="mixed">Generic menuitems don't support aria-checked.</div> + <div id="menuitem_checked_false" role="menuitem" aria-checked="false">Generic menuitems don't support aria-checked.</div> + <div id="menuitem_checked_empty" role="menuitem" aria-checked="">Generic menuitems don't support aria-checked.</div> + <div id="menuitem_checked_undefined" role="menuitem" aria-checked="undefined">Generic menuitems don't support aria-checked.</div> + <div id="menuitem_checked_absent" role="menuitem">Generic menuitems don't support aria-checked.</div> + + <div id="menuitemcheckbox_checked_true" role="menuitemcheckbox" aria-checked="true">This menuitemcheckbox has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div> + <div id="menuitemcheckbox_checked_mixed" role="menuitemcheckbox" aria-checked="mixed">This menuitemcheckbox has aria-checked="mixed" and should get STATE_CHECKABLE. It should also get STATE_MIXED.</div> + <div id="menuitemcheckbox_checked_false" role="menuitemcheckbox" aria-checked="false">This menuitemcheckbox has aria-checked="false" and should get STATE_CHECKABLE.</div> + <div id="menuitemcheckbox_checked_empty" role="menuitemcheckbox" aria-checked="">This menuitemcheckbox has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div> + <div id="menuitemcheckbox_checked_undefined" role="menuitemcheckbox" aria-checked="undefined">This menuitemcheckbox has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div> + <div id="menuitemcheckbox_checked_absent" role="menuitemcheckbox">This menuitemcheckbox has <emph>no</emph> aria-checked attribute and should <emph>not</emph> get STATE_CHECKABLE.</div> + + <div id="menuitemcheckbox_readonly_true" role="menuitemcheckbox" aria-readonly="true">This menuitemcheckbox has aria-readonly="true" and should get STATE_READONLY.</div> + <div id="menuitemcheckbox_readonly_false" role="menuitemcheckbox" aria-readonly="false">This menuitemcheckbox has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="menuitemcheckbox_readonly_empty" role="menuitemcheckbox" aria-readonly="">This menuitemcheckbox has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="menuitemcheckbox_readonly_undefined" role="menuitemcheckbox" aria-readonly="undefined">This menuitemcheckbox has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="menuitemcheckbox_readonly_absent" role="menuitemcheckbox">This menuitemcheckbox has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div> + + <div id="menuitemradio_checked_true" role="menuitemradio" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div> + <div id="menuitemradio_checked_mixed" role="menuitemradio" aria-checked="mixed">This menuitem has aria-checked="mixed" and should get STATE_CHECKABLE. It should not get STATE_MIXED.</div> + <div id="menuitemradio_checked_false" role="menuitemradio" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div> + <div id="menuitemradio_checked_empty" role="menuitemradio" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div> + <div id="menuitemradio_checked_undefined" role="menuitemradio" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div> + <div id="menuitemradio_checked_absent" role="menuitemradio">This menuitem has <emph>no</emph> aria-checked attribute but should get STATE_CHECKABLE.</div> + </div> + + <div id="menuitemradio_readonly_true" role="menuitemradio" aria-readonly="true">This menuitemradio has aria-readonly="true" and should get STATE_READONLY.</div> + <div id="menuitemradio_readonly_false" role="menuitemradio" aria-readonly="false">This menuitemradio has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="menuitemradio_readonly_empty" role="menuitemradio" aria-readonly="">This menuitemradio has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="menuitemradio_readonly_undefined" role="menuitemradio" aria-readonly="undefined">This menuitemradio has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="menuitemradio_readonly_absent" role="menuitemradio">This menuitemradio has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div> + + <div id="radio_checked_true" role="radio" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_CHECKED.</div> + <div id="radio_checked_mixed" role="radio" aria-checked="mixed">This radio button has aria-checked="mixed" and should get STATE_CHECKABLE. It should not get STATE_MIXED.</div> + <div id="radio_checked_false" role="radio" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div> + <div id="radio_checked_empty" role="radio" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div> + <div id="radio_checked_undefined" role="radio" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div> + <div id="radio_checked_absent" role="radio">This menuitem has <emph>no</emph> aria-checked attribute but should get STATE_CHECKABLE.</div> + + <div id="radiogroup_readonly_true" role="radiogroup" aria-readonly="true"> + <div role="radio">yes</div> + <div role="radio">no</div> + </div> + <div id="radiogroup_readonly_false" role="radiogroup" aria-readonly="false"> + <div role="radio">yes</div> + <div role="radio">no</div> + </div> + <div id="radiogroup_readonly_empty" role="radiogroup" aria-readonly=""> + <div role="radio">yes</div> + <div role="radio">no</div> + </div> + <div id="radiogroup_readonly_undefined" role="radiogroup" aria-readonly="undefined"> + <div role="radio">yes</div> + <div role="radio">no</div> + </div> + <div id="radiogroup_readonly_absent" role="radiogroup"> + <div role="radio">yes</div> + <div role="radio">no</div> + </div> + + <div id="switch_readonly_true" role="switch" aria-readonly="true">This switch has aria-readonly="true" and should get STATE_READONLY.</div> + <div id="switch_readonly_false" role="switch" aria-readonly="false">This switch has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="switch_readonly_empty" role="switch" aria-readonly="">This switch has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="switch_readonly_undefined" role="switch" aria-readonly="undefined">This switch has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div> + <div id="switch_readonly_absent" role="switch">This switch has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div> + + <div id="textbox_readonly_true" role="textbox" aria-readonly="true"></div> + <div id="textbox_readonly_false" role="textbox" aria-readonly="false"></div> + <div id="textbox_readonly_empty" role="textbox" aria-readonly=""></div> + <div id="textbox_readonly_undefined" role="textbox" aria-readonly="undefined"></div> + <div id="textbox_readonly_absent" role="textbox"></div> + + <div id="textbox_multiline_true" role="textbox" aria-multiline="true"></div> + <div id="textbox_multiline_false" role="textbox" aria-multiline="false"></div> + <div id="textbox_multiline_empty" role="textbox" aria-multiline=""></div> + <div id="textbox_multiline_undefined" role="textbox" aria-multiline="undefined"></div> + <div id="textbox_multiline_absent" role="textbox"></div> + + <form action=""> + <input id="native_textbox_nativereadonly_ariatrue" readonly="readonly" aria-readonly="true"/> + <input id="native_textbox_nativereadonly_ariafalse" readonly="readonly" aria-readonly="false"/> + <input id="native_textbox_nativereadonly_ariaempty" readonly="readonly" aria-readonly=""/> + <input id="native_textbox_nativereadonly_ariaundefined" readonly="readonly" aria-readonly="undefined"/> + <input id="native_textbox_nativereadonly_ariaabsent" readonly="readonly"/> + + <input id="native_textbox_nativeeditable_ariatrue" aria-readonly="true"/> + <input id="native_textbox_nativeeditable_ariafalse" aria-readonly="false"/> + <input id="native_textbox_nativeeditable_ariaempty" aria-readonly=""/> + <input id="native_textbox_nativeeditable_ariaundefined" aria-readonly="undefined"/> + <input id="native_textbox_nativeeditable_ariaabsent"/> + </form> + + <div role="tree"> + <div id="treeitem_selected_true" role="treeitem" aria-selected="true">This treeitem has aria-selected="true" and should get STATE_SELECTABLE. It should also get STATE_SELECTED.</div> + <div id="treeitem_selected_false" role="treeitem" aria-selected="false">This treeitem has aria-selected="false" and should get STATE_SELECTABLE.</div> + <div id="treeitem_selected_empty" role="treeitem" aria-selected="">This treeitem has aria-selected="" and should <emph>not</emph> get STATE_SELECTABLE.</div> + <div id="treeitem_selected_undefined" role="treeitem" aria-selected="undefined">This treeitem has aria-selected="undefined" and should <emph>not</emph> get STATE_SELECTABLE.</div> + <div id="treeitem_selected_absent" role="treeitem">This treeitem has <emph>no</emph> aria-selected attribute and should <emph>not</emph> get STATE_SELECTABLE.</div> + + <div id="treeitem_haspopup_true" role="treeitem" aria-haspopup="true">This treeitem has aria-haspopup="true" and should get STATE_HASPOPUP.</div> + <div id="treeitem_haspopup_false" role="treeitem" aria-haspopup="false">This treeitem has aria-haspopup="false" and should get STATE_HASPOPUP.</div> + <div id="treeitem_haspopup_empty" role="treeitem" aria-haspopup="">This treeitem has aria-haspopup="" and should <emph>not</emph> get STATE_HASPOPUP.</div> + <div id="treeitem_haspopup_undefined" role="treeitem" aria-haspopup="undefined">This treeitem has aria-haspopup="undefined" and should <emph>not</emph> get STATE_HASPOPUP.</div> + <div id="treeitem_haspopup_absent" role="treeitem">This treeitem has <emph>no</emph> aria-haspopup attribute and should <emph>not</emph> get STATE_HASPOPUP.</div> + + <div id="treeitem_expanded_true" role="treeitem" aria-expanded="true">This treeitem has aria-expanded="true" and should get STATE_EXPANDABLE. It should also get STATE_EXPANDED.</div> + <div id="treeitem_expanded_false" role="treeitem" aria-expanded="false">This treeitem has aria-expanded="false" and should get STATE_EXPANDABLE. It should also get STATE_COLLAPSED.</div> + <div id="treeitem_expanded_empty" role="treeitem" aria-expanded="">This treeitem has aria-expanded="" and should <emph>not</emph> get STATE_EXPANDABLE.</div> + <div id="treeitem_expanded_undefined" role="treeitem" aria-expanded="undefined">This treeitem has aria-expanded="undefined" and should <emph>not</emph> get STATE_EXPANDABLE.</div> + <div id="treeitem_expanded_absent" role="treeitem">This treeitem has <emph>no</emph> aria-expanded attribute and should <emph>not</emph> get STATE_EXPANDABLE.</div> + </div> + + </body> +</html> diff --git a/accessible/tests/mochitest/test_bug420863.html b/accessible/tests/mochitest/test_bug420863.html new file mode 100644 index 0000000000..4f3a608fe1 --- /dev/null +++ b/accessible/tests/mochitest/test_bug420863.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=420863 +--> +<head> + <title>Table indexes chrome tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="common.js"></script> + <script type="application/javascript" + src="events.js"></script> + <script type="application/javascript" + src="actions.js"></script> + + <script type="application/javascript"> + var gClickHandler = null; + + function doTest() { + // Actions should be exposed on any accessible having related DOM node + // with registered 'click' event handler. + + // //////////////////////////////////////////////////////////////////////// + // generic td + var td1Acc = getAccessible("td1"); + if (!td1Acc) { + SimpleTest.finish(); + return; + } + + is(td1Acc.actionCount, 0, + "Simple table cell shouldn't have any actions"); + + // //////////////////////////////////////////////////////////////////////// + // one td with 'onclick' attribute and one with registered click handler + var td3Node = getNode("td3"); + + // register 'click' event handler + gClickHandler = { + handleEvent: function handleEvent(aEvent) { + }, + }; + td3Node.addEventListener("click", gClickHandler); + + // check actions + var actionsArray = [ + { + ID: "td2", // "onclick" attribute + actionName: "click", + actionIndex: 0, + events: CLICK_EVENTS, + }, + { + ID: td3Node, + actionName: "click", + actionIndex: 0, + events: CLICK_EVENTS, + checkOnClickEvent: function check(aEvent) { + // unregister click event handler + this.ID.removeEventListener("click", gClickHandler); + + // check actions + is(getAccessible(this.ID).actionCount, 0, + "td3 shouldn't have actions"); + }, + }, + ]; + + testActions(actionsArray); // will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=420863" + title="If an HTML element has an onClick attribute, expose its click action on the element rather than its child text leaf node." + target="_blank">Mozilla Bug 420863</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <table> + <tr> + <td id="td1">Can't click this cell</td> + <td onclick="gTdClickAttr = true;" + id="td2">Cell with 'onclick' attribute</td> + <td id="td3">Cell with registered 'click' event handler</td> + </tr> + </table> + +</body> +</html> diff --git a/accessible/tests/mochitest/test_custom_element_accessibility_defaults.html b/accessible/tests/mochitest/test_custom_element_accessibility_defaults.html new file mode 100644 index 0000000000..28e337ed8e --- /dev/null +++ b/accessible/tests/mochitest/test_custom_element_accessibility_defaults.html @@ -0,0 +1,383 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1665151 +--> +<head> + <title>Test for default accessibility semantics for custom elements</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css"> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="common.js"></script> + <script src="attributes.js"></script> + <script src="role.js"></script> + <script src="states.js"></script> + <script src="value.js"></script> + <script src="name.js"></script> + <script src="promisified-events.js"></script> + <script> +[ + ["role", "math", () => {}], + ["atomic", "toolbar", internals => internals.ariaAtomic = "true"], + ["autocomplete", "textbox", internals => internals.ariaAutoComplete = "inline"], + ["busy", "feed", internals => internals.ariaBusy = "true"], + ["checked", "checkbox", internals => internals.ariaChecked = "true"], + ["colcount", "grid", internals => internals.ariaColCount = "1"], + ["col", "gridcell", internals => { + internals.ariaColIndex = "1"; + internals.ariaColIndexText = "Default"; + internals.ariaColSpan = "1"; + }], + ["current", "listitem", internals => internals.ariaCurrent = "page"], + ["description", "note", internals => internals.ariaDescription = "Default"], + ["disabled", "button", internals => internals.ariaDisabled = "true"], + ["expanded", "button", internals => internals.ariaExpanded = "true"], + ["haspopup", "button", internals => internals.ariaHasPopup = "true"], + ["hidden", "region", internals => internals.ariaHidden = "false"], + ["invalid", "textbox", internals => internals.ariaInvalid = "true"], + ["keyshortcuts", "button", internals => internals.ariaKeyShortcuts = "Alt+Shift+A"], + ["label", "button", internals => internals.ariaLabel = "Default"], + ["level", "heading", internals => internals.ariaLevel = "1"], + ["live", "region", internals => internals.ariaLive = "polite"], + ["modal", "dialog", internals => internals.ariaModal = "true"], + ["multiline", "textbox", internals => internals.ariaMultiLine = "true"], + ["multiselectable", "listbox", internals => internals.ariaMultiSelectable = "true"], + ["orientation", "menu", internals => internals.ariaOrientation = "vertical"], + ["placeholder", "textbox", internals => internals.ariaPlaceholder = "Default"], + ["posinset", "option", internals => internals.ariaPosInSet = "1"], + ["pressed", "button", internals => internals.ariaPressed = "true"], + ["readonly", "textbox", internals => internals.ariaReadOnly = "true"], + ["relevant", "region", internals => internals.ariaRelevant = "all"], + ["required", "textbox", internals => internals.ariaRequired = "true"], + ["roledescription", "region", internals => internals.ariaRoleDescription = "Default"], + ["rowcount", "grid", internals => internals.ariaRowCount = "1"], + ["row", "row", internals => { + internals.ariaRowIndex = "1"; + internals.ariaRowIndexText = "Default"; + }], + ["rowspan", "cell", internals => internals.ariaRowSpan = "1"], + ["selected", "option", internals => internals.ariaSelected = "true"], + ["setsize", "listitem", internals => internals.ariaSetSize = "1"], + ["sort", "columnheader", internals => internals.ariaSort = "ascending"], + ["value", "slider", internals => { + internals.ariaValueNow = "5"; + internals.ariaValueMin = "1"; + internals.ariaValueMax = "10"; + internals.ariaValueText = "Default"; + }], +].forEach(([name, role, apply]) => { + customElements.define(`custom-${name}`, + class extends HTMLElement { + constructor() { + super(); + this._internals = this.attachInternals(); + this._internals.role = role; + apply(this._internals); + } + get internals() { + return this._internals; + } + } + ); +}); + +async function runTest() { + // Test for proper overriding of default attributes. + testAttrs("default-role", {"xml-roles": "math"}, true); + testAttrs("custom-role", {"xml-roles": "note"}, true); + + testAttrs("default-atomic", {"atomic": "true"}, true); + testAbsentAttrs("custom-atomic", {"atomic": "false"}); + + testAttrs("default-autocomplete", {"autocomplete": "inline"}, true); + testAttrs("custom-autocomplete", {"autocomplete": "list"}, true); + + testStates("default-busy", STATE_BUSY); + testStates("custom-busy", 0, 0, STATE_BUSY); + + testStates("default-checked", STATE_CHECKED); + testStates("custom-checked", 0, 0, STATE_CHECKED); + + testAttrs("default-colCount", {"colcount": "1"}, true); + testAttrs("default-col", {"colindex": "1"}, true); + testAttrs("default-col", {"colindextext": "Default"}, true); + testAttrs("default-col", {"colspan": "1"}, true); + testAttrs("custom-colCount", {"colcount": "3"}, true); + testAttrs("custom-col", {"colindex": "2"}, true); + testAttrs("custom-col", {"colindextext": "Custom"}, true); + testAttrs("custom-col", {"colspan": "2"}, true); + + testAttrs("default-current", {"current": "page"}, true); + testAttrs("custom-current", {"current": "step"}, true); + + testDescr("default-description", "Default"); + testDescr("custom-description", "Custom"); + + testStates("default-disabled", STATE_UNAVAILABLE); + testStates("custom-disabled", 0, 0, STATE_UNAVAILABLE); + + testStates("default-expanded", STATE_EXPANDED); + testStates("custom-expanded", STATE_COLLAPSED); + + testAttrs("default-haspopup", {"haspopup": "true"}, true); + testAbsentAttrs("custom-haspopup", {"haspopup": "false"}); + + ok(isAccessible("default-hidden"), "Accessible for not aria-hidden"); + ok(!isAccessible("custom-hidden"), "No accessible for aria-hidden"); + + testStates("default-invalid", STATE_INVALID); + testStates("custom-invalid", 0, 0, STATE_INVALID); + + testAttrs("default-keyshortcuts", {"keyshortcuts": "Alt+Shift+A"}, true); + testAttrs("custom-keyshortcuts", {"keyshortcuts": "A"}, true); + + testName("default-label", "Default"); + testName("custom-label", "Custom"); + + testAttrs("default-level", {"level": "1"}, true); + testAttrs("custom-level", {"level": "2"}, true); + + testAttrs("default-live", {"live": "polite"}, true); + testAttrs("custom-live", {"live": "assertive"}, true); + + testStates("default-modal", 0, EXT_STATE_MODAL); + testStates("custom-modal", 0, 0, 0, EXT_STATE_MODAL); + + testStates("default-multiline", 0, EXT_STATE_MULTI_LINE, 0, EXT_STATE_SINGLE_LINE); + testStates("custom-multiline", 0, EXT_STATE_SINGLE_LINE, 0, EXT_STATE_MULTI_LINE); + + testStates("default-multiselectable", STATE_MULTISELECTABLE); + testStates("custom-multiselectable", 0, 0, STATE_MULTISELECTABLE); + + testStates("default-orientation", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + testStates("custom-orientation", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL); + + testAttrs("default-posinset", {"posinset": "1"}, true); + testAttrs("custom-posinset", {"posinset": "2"}, true); + + testStates("default-pressed", STATE_PRESSED); + testStates("custom-pressed", 0, 0, STATE_PRESSED); + + testStates("default-readonly", STATE_READONLY); + testStates("custom-readonly", 0, 0, STATE_READONLY); + + testAttrs("default-relevant", {"relevant": "all"}, true); + testAttrs("custom-relevant", {"relevant": "text"}, true); + + testStates("default-required", STATE_REQUIRED); + testStates("custom-required", 0, 0, STATE_REQUIRED); + + testAttrs("default-roledescription", {"roledescription": "Default"}, true); + testAttrs("custom-roledescription", {"roledescription": "Custom"}, true); + + testAttrs("default-rowcount", {"rowcount": "1"}, true); + testAttrs("default-row", {"rowindex": "1"}, true); + testAttrs("default-row", {"rowindextext": "Default"}, true); + testAttrs("default-rowspan", {"rowspan": "1"}, true); + testAttrs("custom-rowcount", {"rowcount": "3"}, true); + testAttrs("custom-row", {"rowindex": "2"}, true); + testAttrs("custom-row", {"rowindextext": "Custom"}, true); + testAttrs("custom-rowspan", {"rowspan": "2"}, true); + + testStates("default-selected", STATE_SELECTED); + testStates("custom-selected", 0, 0, STATE_SELECTED); + + testAttrs("default-setsize", {"setsize": "1"}, true); + testAttrs("custom-setsize", {"setsize": "2"}, true); + + testAttrs("default-sort", {"sort": "ascending"}, true); + testAttrs("custom-sort", {"sort": "descending"}, true); + + testValue("default-value", "Default", 5, 1, 10, 0); + testValue("custom-value", "Custom", 15, 10, 20, 0); + + // Test that changes of defaults fire the proper events. + info("Changing ElementInternals ariaLabel"); + let nameChanged = waitForEvent(EVENT_NAME_CHANGE, "default-label"); + let customLabelElement = document.getElementById("default-label"); + customLabelElement.internals.ariaLabel = "Changed Default"; + await nameChanged; + testName("default-label", "Changed Default"); + + info("Changing ElementInternals ariaRequired"); + let stateChanged = waitForEvent(EVENT_STATE_CHANGE, "default-required"); + let requiredElement = document.getElementById("default-required"); + requiredElement.internals.ariaRequired = "false"; + await stateChanged; + testStates("default-required", 0, 0, STATE_REQUIRED); + + info("Changing ElementInternals ariaSort"); + let attributeChanged = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, "default-sort"); + let sortElement = document.getElementById("default-sort"); + sortElement.internals.ariaSort = "descending"; + await attributeChanged; + testAttrs("default-sort", {"sort": "descending"}, true); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +Services.prefs.setBoolPref("accessibility.ARIAReflection.enabled", true); +addA11yLoadEvent(runTest); + </script> +</head> +<body> + +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1665151">Bug 1665151</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<custom-role id="default-role"></custom-role> +<custom-role id="custom-role" role="note"></custom-role> + +<custom-autocomplete id="default-autocomplete"></custom-autocomplete> +<custom-autocomplete id="custom-autocomplete" aria-autocomplete="list"></custom-autocomplete> + +<custom-atomic id="default-atomic"></custom-atomic> +<custom-atomic id="custom-atomic" aria-atomic="false"></custom-atomic> + +<custom-busy id="default-busy"></custom-busy> +<custom-busy id="custom-busy" aria-busy="false"></custom-busy> + +<custom-checked id="default-checked"></custom-checked> +<custom-checked id="custom-checked" aria-checked="false"></custom-checked> + +<custom-colcount id="default-colCount"> + <div role="rowgroup"> + <div role="row"> + <custom-col id="default-col"></custom-col> + </div> + </div> +</custom-colcount> +<custom-colcount id="custom-colCount" aria-colcount="3"> + <div role="rowgroup"> + <div role="row"> + <custom-col + id="custom-col" + aria-colindex="2" + aria-colindextext="Custom" + aria-colspan="2"> + </custom-col> + </div> + </div> +</custom-colcount> + +<custom-current id="default-current"></custom-current> +<custom-current id="custom-current" aria-current="step"></custom-current> + +<custom-description id="default-description"></custom-description> +<custom-description id="custom-description" aria-description="Custom"></custom-description> + +<custom-disabled id="default-disabled"></custom-disabled> +<custom-disabled id="custom-disabled" aria-disabled="false"></custom-disabled> + +<custom-expanded id="default-expanded"></custom-expanded> +<custom-expanded id="custom-expanded" aria-expanded="false"></custom-expanded> + +<custom-haspopup id="default-haspopup"></custom-haspopup> +<custom-haspopup id="custom-haspopup" aria-haspopup="false"></custom-haspopup> + +<custom-hidden id="default-hidden"></custom-hidden> +<custom-hidden id="custom-hidden" aria-hidden="true"></custom-hidden> + +<custom-invalid id="default-invalid"></custom-invalid> +<custom-invalid id="custom-invalid" aria-invalid="false"></custom-invalid> + +<custom-keyshortcuts id="default-keyshortcuts"></custom-keyshortcuts> +<custom-keyshortcuts id="custom-keyshortcuts" aria-keyshortcuts="A"></custom-keyshortcuts> + +<custom-label id="default-label"></custom-label> +<custom-label id="custom-label" aria-label="Custom"></custom-label> + +<custom-level id="default-level"></custom-level> +<custom-level id="custom-level" aria-level="2"></custom-level> + +<custom-live id="default-live"></custom-live> +<custom-live id="custom-live" aria-live="assertive"></custom-live> + +<custom-modal id="default-modal"></custom-modal> +<custom-modal id="custom-modal" aria-modal="false"></custom-modal> + +<custom-multiline id="default-multiline"></custom-multiline> +<custom-multiline id="custom-multiline" aria-multiline="false"></custom-multiline> + +<custom-multiselectable id="default-multiselectable"></custom-multiselectable> +<custom-multiselectable id="custom-multiselectable" aria-multiselectable="false"></custom-multiselectable> + +<custom-orientation id="default-orientation"></custom-orientation> +<custom-orientation id="custom-orientation" aria-orientation="horizontal"></custom-orientation> + +<custom-posinset id="default-posinset"></custom-posinset> +<custom-posinset id="custom-posinset" aria-posinset="2"></custom-posinset> + +<custom-pressed id="default-pressed"></custom-pressed> +<custom-pressed id="custom-pressed" aria-pressed="false"></custom-pressed> + +<custom-readonly id="default-readonly"></custom-readonly> +<custom-readonly id="custom-readonly" aria-readonly="false"></custom-readonly> + +<custom-relevant id="default-relevant"></custom-relevant> +<custom-relevant id="custom-relevant" aria-relevant="text"></custom-relevant> + +<custom-required id="default-required"></custom-required> +<custom-required id="custom-required" aria-required="false"></custom-required> + +<custom-roledescription id="default-roledescription"></custom-roledescription> +<custom-roledescription id="custom-roledescription" aria-roledescription="Custom"></custom-roledescription> + +<custom-rowcount id="default-rowcount"> + <div role="rowgroup"> + <custom-row id="default-row"> + <custom-rowspan id="default-rowspan"></custom-rowspan> + </custom-row> + </div> +</custom-rowcount> +<custom-rowcount id="custom-rowcount" aria-rowcount="3"> + <div role="rowgroup"> + <custom-row + id="custom-row" + aria-rowindex="2" + aria-rowindextext="Custom"> + <custom-rowspan id="custom-rowspan" aria-rowspan="2"></custom-rowspan> + </custom-row> + </div> +</custom-rowcount> + +<custom-selected id="default-selected"></custom-selected> +<custom-selected id="custom-selected" aria-selected="false"></custom-selected> + +<div role="listbox"> + <custom-setsize id="default-setsize"></custom-setsize> +</div> +<div role="listbox"> + <custom-setsize id="custom-setsize" aria-setsize="2"></custom-setsize> + <div role="listitem" aria-setsize="2"></div> +</div> + +<div role="grid"> + <div role="rowgroup"> + <div role="row"> + <custom-sort id="default-sort"></custom-sort> + </div> + </div> +</div> +<div role="grid"> + <div role="rowgroup"> + <div role="row"> + <custom-sort id="custom-sort" aria-sort="descending"></custom-sort> + </div> + </div> +</div> + +<custom-value id="default-value"></custom-value> +<custom-value + id="custom-value" + aria-valuenow="15" + aria-valuemin="10" + aria-valuemax="20" + aria-valuetext="Custom"> +</custom-value> +</pre> + +</body> +</html> diff --git a/accessible/tests/mochitest/test_descr.html b/accessible/tests/mochitest/test_descr.html new file mode 100644 index 0000000000..c386ee5dc1 --- /dev/null +++ b/accessible/tests/mochitest/test_descr.html @@ -0,0 +1,134 @@ +<html> + +<head> + <title>nsIAccessible::description tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="common.js"></script> + <script type="application/javascript" + src="name.js"></script> + + <script type="application/javascript"> + function doTest() { + // Description from aria-describedby attribute + testDescr("img1", "aria description"); + + // No description from @title attribute because it is used to generate + // name. + testDescr("img2", ""); + + // Description from @title attribute, name is generated from @alt + // attribute. + testDescr("img3", "description"); + + // No description from aria-describedby since it is the same as the + // @alt attribute which is used as the name + testDescr("img4", ""); + + // No description from @title attribute since it is the same as the + // @alt attribute which is used as the name + testDescr("img5", ""); + + // Description from content of h2. + testDescr("p", "heading"); + + // Description from aria-description attribute + testDescr("p2", "I describe"); + + // Description from contents of h2 when both aria-describedby and + // aria-description are present + testDescr("p3", "heading"); + + // From table summary (caption is used as a name) + testDescr("table1", "summary"); + + // Empty (summary is used as a name) + testDescr("table2", ""); + + // From title (summary is used as a name) + testDescr("table3", "title"); + + // No description from <desc> element since it is the same as the + // <title> element. + testDescr("svg", ""); + + // role="alert" referenced by aria-describedby should include subtree. + testDescr("inputDescribedByAlert", "Error"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=489944" + title="@title attribute no longer exposed on accDescription"> + Mozilla Bug 489944 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=666212" + title="summary attribute content mapped to accessible name in MSAA"> + Mozilla Bug 666212 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi/id=1031188" + title="Ensure that accDescription never duplicates AccessibleName"> + Mozilla Bug 1031188 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <p id="description">aria description</p> + <img id="img1" aria-describedby="description" /> + <img id="img2" title="title" /> + <img id="img3" alt="name" title="description" /> + <img id="img4" alt="aria description" aria-describedby="description"> + <img id="img5" alt="image" title="image"> + + <h2 id="heading">heading</h2> + <p id="p" aria-describedby="heading" role="button">click me</p> + <p id="p2" aria-description="I describe" role="button">click me</p> + <p id="p3" aria-description="I do not describe" aria-describedby="heading" role="button">click me</p> + + <table id="table1" summary="summary"> + <caption>caption</caption> + <tr><td>cell</td></tr> + </table> + + <table id="table2" summary="summary"> + <tr><td>cell</td></tr> + </table> + + <table id="table3" summary="summary" title="title"> + <tr><td>cell</td></tr> + </table> + + <svg xmlns="http://www.w3.org/2000/svg" version="1.1" + viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice" + id="svg" + style="width:100px; height:100px;"> + <title>SVG Image</title> + <desc>SVG Image</desc> + <linearGradient id="gradient"> + <stop class="begin" offset="0%"/> + <stop class="end" offset="100%"/> + </linearGradient> + <rect x="0" y="0" width="100" height="100" style="fill:url(#gradient)" /> + <circle cx="50" cy="50" r="30" style="fill:url(#gradient)" /> + </svg> + + <div id="alert" role="alert">Error</div> + <input type="text" id="inputDescribedByAlert" aria-describedby="alert"> +</body> +</html> diff --git a/accessible/tests/mochitest/test_nsIAccessibleDocument.html b/accessible/tests/mochitest/test_nsIAccessibleDocument.html new file mode 100644 index 0000000000..7758f63805 --- /dev/null +++ b/accessible/tests/mochitest/test_nsIAccessibleDocument.html @@ -0,0 +1,94 @@ +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=441737 +--> +<head> + <title>nsIAccessibleDocument chrome tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="common.js"></script> + <script type="application/javascript" + src="role.js"></script> + <script type="application/javascript" + src="states.js"></script> + + <script src="chrome://mochikit/content/chrome-harness.js"></script> + + <script type="application/javascript"> + function doTest() { + var docAcc = getAccessible(document, [nsIAccessibleDocument]); + if (docAcc) { + // nsIAccessible + is(docAcc.name, "nsIAccessibleDocument chrome tests", + "Name for document accessible not correct!"); + + testRole(docAcc, ROLE_DOCUMENT); + + // check if it is focusable, read-only. + testStates(docAcc, (STATE_READONLY | STATE_FOCUSABLE)); + + // No actions wanted on doc + is(docAcc.actionCount, 0, "Wrong number of actions for document!"); + + // attributes should contain tag:body + const attributes = docAcc.attributes; + is(attributes.getStringProperty("tag"), "body", + "Wrong attribute on document!"); + + // Document URL. + var rootDir = getRootDirectory(window.location.href); + is(docAcc.URL, rootDir + "test_nsIAccessibleDocument.html", + "Wrong URL for document!"); + + // Document title and mime type. + is(docAcc.title, "nsIAccessibleDocument chrome tests", + "Wrong title for document!"); + is(docAcc.mimeType, "text/html", + "Wrong mime type for document!"); + + // DocAccessible::getDocType currently returns NS_ERROR_FAILURE. + // See bug 442005. After fixing, please remove this comment and + // uncomment the below two lines to enable the test. +// is(docAcc.docType, "HTML", +// "Wrong type of document!"); + + // Test for correct Document retrieval. + var domDoc = null; + try { + domDoc = docAcc.DOMDocument; + } catch (e) {} + ok(domDoc, "no Document for this doc accessible!"); + is(domDoc, document, "Document nodes do not match!"); + + // Test for correct nsIDOMWindow retrieval. + var domWindow = null; + try { + domWindow = docAcc.window; + } catch (e) {} + ok(domWindow, "no nsIDOMWindow for this doc accessible!"); + is(domWindow, window, "Window nodes do not match!"); + } + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=441737" + title="nsAccessibleDocument chrome tests"> + Mozilla Bug 441737 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/test_nsIAccessibleImage.html b/accessible/tests/mochitest/test_nsIAccessibleImage.html new file mode 100644 index 0000000000..1d8ee2022d --- /dev/null +++ b/accessible/tests/mochitest/test_nsIAccessibleImage.html @@ -0,0 +1,198 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=429659 +--> +<head> + <title>nsIAccessibleImage chrome tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="common.js"></script> + <script type="application/javascript" + src="role.js"></script> + <script type="application/javascript" + src="attributes.js"></script> + <script type="application/javascript" + src="layout.js"></script> + + <script type="application/javascript"> + function testCoordinates(aID, aAcc, aWidth, aHeight) { + var screenX = {}, screenY = {}, windowX = {}, windowY = {}, parentX = {}, + parentY = {}; + + // get screen coordinates. + aAcc.getImagePosition( + nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE, + screenX, screenY); + // get window coordinates. + aAcc.getImagePosition( + nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE, + windowX, windowY); + // get parent related coordinates. + aAcc.getImagePosition( + nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE, + parentX, parentY); + // XXX For linked images, a negative parentY value is returned, and the + // screenY coordinate is the link's screenY coordinate minus 1. + // Until this is fixed, set parentY to -1 if it's negative. + if (parentY.value < 0) + parentY.value = -1; + + // See if asking image for child at image's screen coordinates gives + // correct accessible. getChildAtPoint operates on screen coordinates. + var tempAcc = null; + try { + tempAcc = aAcc.getChildAtPoint(screenX.value, screenY.value); + } catch (e) {} + is(tempAcc, aAcc, + "Wrong accessible returned for position of " + aID + "!"); + + // get image's parent. + var imageParentAcc = null; + try { + imageParentAcc = aAcc.parent; + } catch (e) {} + ok(imageParentAcc, "no parent accessible for " + aID + "!"); + + if (imageParentAcc) { + // See if parent's screen coordinates plus image's parent relative + // coordinates equal to image's screen coordinates. + var parentAccX = {}, parentAccY = {}, parentAccWidth = {}, + parentAccHeight = {}; + imageParentAcc.getBounds(parentAccX, parentAccY, parentAccWidth, + parentAccHeight); + is(parentAccX.value + parentX.value, screenX.value, + "Wrong screen x coordinate for " + aID + "!"); +// XXX see bug 456344 is(parentAccY.value + parentY.value, screenY.value, +// "Wrong screen y coordinate for " + aID + "!"); + } + + var [expected_w, expected_h] = CSSToDevicePixels(window, aWidth, aHeight); + var width = {}, height = {}; + aAcc.getImageSize(width, height); + is(width.value, expected_w, "Wrong width for " + aID + "!"); + is(height.value, expected_h, "wrong height for " + aID + "!"); + } + + function testThis(aID, aSRC, aWidth, aHeight, + aActionCount, aActionNames) { + var acc = getAccessible(aID, [nsIAccessibleImage]); + if (!acc) + return; + + // Test role + testRole(aID, ROLE_GRAPHIC); + + // test coordinates and size + testCoordinates(aID, acc, aWidth, aHeight); + + // bug 429659: Make sure the SRC attribute is set for any image + var attributes = {"src": aSRC}; + testAttrs(acc, attributes, true); + + var actionCount = aActionCount || 0; + is(acc.actionCount, actionCount, + "Wrong number of actions for " + aID + "!"); + if (actionCount) { + for (let index = 0; index < aActionNames.length; index++) { + is(acc.getActionName(index), aActionNames[index], + "Wrong action name for " + aID + ", index " + index + "!"); + } + } + } + + function doTest() { + // Test non-linked image + testThis("nonLinkedImage", "moz.png", 89, 38); + + // Test linked image + var actionNamesArray = ["click ancestor"]; + testThis("linkedImage", "moz.png", 89, 38, 1, + actionNamesArray); + + // Image with long desc + actionNamesArray = ["showlongdesc"]; + testThis("longdesc", "moz.png", 89, 38, 1, + actionNamesArray); + + // Image with invalid url in long desc + testThis("invalidLongdesc", "moz.png", 89, 38, 0); + + // Image with click and long desc + actionNamesArray = null; + actionNamesArray = ["click", "showlongdesc"]; + testThis("clickAndLongdesc", "moz.png", + 89, 38, 2, actionNamesArray); + + // Image with click + actionNamesArray = null; + actionNamesArray = ["click"]; + testThis("click", "moz.png", + 89, 38, 1, actionNamesArray); + + // Image with long desc + actionNamesArray = null; + actionNamesArray = ["showlongdesc"]; + testThis("longdesc2", "moz.png", + 89, 38, 1, actionNamesArray); + + // Image described by HTML:a@href with whitespaces + actionNamesArray = null; + actionNamesArray = ["showlongdesc"]; + testThis("longdesc3", "moz.png", + 89, 38, 1, actionNamesArray); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=429659">Mozilla Bug 429659</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=652635" + title="fall back missing @longdesc to aria-describedby pointing to a href"> + Mozilla Bug 652635 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <br>Simple image:<br> + <img id="nonLinkedImage" src="moz.png"/> + <br>Linked image:<br> + <a href="http://www.mozilla.org"><img id="linkedImage" src="moz.png"></a> + <br>Image with longdesc:<br> + <img id="longdesc" src="moz.png" longdesc="longdesc_src.html" + alt="Image of Mozilla logo"/> + <br>Image with invalid url in longdesc:<br> + <img id="invalidLongdesc" src="moz.png" longdesc="longdesc src.html" + alt="Image of Mozilla logo"/> + <br>Image with click and longdesc:<br> + <img id="clickAndLongdesc" src="moz.png" longdesc="longdesc_src.html" + alt="Another image of Mozilla logo" onclick="alert('Clicked!');"/> + + <br>image described by a link to be treated as longdesc<br> + <img id="longdesc2" src="moz.png" aria-describedby="describing_link" + alt="Second Image of Mozilla logo"/> + <a id="describing_link" href="longdesc_src.html">link to description of image</a> + + <br>Image described by a link to be treated as longdesc with whitespaces<br> + <img id="longdesc3" src="moz.png" aria-describedby="describing_link2" + alt="Second Image of Mozilla logo"/> + <a id="describing_link2" href="longdesc src.html">link to description of image</a> + + <br>Image with click:<br> + <img id="click" src="moz.png" + alt="A third image of Mozilla logo" onclick="alert('Clicked, too!');"/> + +</body> +</html> diff --git a/accessible/tests/mochitest/text.js b/accessible/tests/mochitest/text.js new file mode 100644 index 0000000000..21392336a1 --- /dev/null +++ b/accessible/tests/mochitest/text.js @@ -0,0 +1,814 @@ +/* import-globals-from common.js */ + +// ////////////////////////////////////////////////////////////////////////////// +// Public + +const BOUNDARY_CHAR = nsIAccessibleText.BOUNDARY_CHAR; +const BOUNDARY_WORD_START = nsIAccessibleText.BOUNDARY_WORD_START; +const BOUNDARY_WORD_END = nsIAccessibleText.BOUNDARY_WORD_END; +const BOUNDARY_LINE_START = nsIAccessibleText.BOUNDARY_LINE_START; +const BOUNDARY_LINE_END = nsIAccessibleText.BOUNDARY_LINE_END; +const BOUNDARY_PARAGRAPH = nsIAccessibleText.BOUNDARY_PARAGRAPH; + +const kTextEndOffset = nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT; +const kCaretOffset = nsIAccessibleText.TEXT_OFFSET_CARET; + +const EndPoint_Start = nsIAccessibleTextRange.EndPoint_Start; +const EndPoint_End = nsIAccessibleTextRange.EndPoint_End; + +const kTodo = 1; // a test is expected to fail +const kOk = 2; // a test doesn't fail + +/** + * Test characterCount for the given array of accessibles. + * + * @param aCount [in] the expected character count + * @param aIDs [in] array of accessible identifiers to test + * @param aTodoFlag [in, optional] either kOk or kTodo + */ +function testCharacterCount(aIDs, aCount, aTodoFlag) { + var ids = aIDs instanceof Array ? aIDs : [aIDs]; + var isFunc = aTodoFlag == kTodo ? todo_is : is; + for (var i = 0; i < ids.length; i++) { + var textacc = getAccessible(ids[i], [nsIAccessibleText]); + isFunc( + textacc.characterCount, + aCount, + "Wrong character count for " + prettyName(ids[i]) + ); + } +} + +/** + * Test text between two given offsets. + * + * @param aIDs [in] an array of accessible IDs to test + * @param aStartOffset [in] the start offset within the text to test + * @param aEndOffset [in] the end offset up to which the text is tested + * @param aText [in] the expected result from the test + * @param aTodoFlag [in, optional] either kOk or kTodo + */ +function testText(aIDs, aStartOffset, aEndOffset, aText, aTodoFlag) { + var ids = aIDs instanceof Array ? aIDs : [aIDs]; + var isFunc = aTodoFlag == kTodo ? todo_is : is; + for (var i = 0; i < ids.length; i++) { + var acc = getAccessible(ids[i], nsIAccessibleText); + try { + isFunc( + acc.getText(aStartOffset, aEndOffset), + aText, + "getText: wrong text between start and end offsets '" + + aStartOffset + + "', '" + + aEndOffset + + " for '" + + prettyName(ids[i]) + + "'" + ); + } catch (e) { + ok( + false, + "getText fails between start and end offsets '" + + aStartOffset + + "', '" + + aEndOffset + + " for '" + + prettyName(ids[i]) + + "'" + ); + } + } +} + +/** + * Test getTextAtOffset for BOUNDARY_CHAR over different elements. + * + * @param aIDs [in] the accessible identifier or array of accessible + * identifiers + * @param aOffset [in] the offset to get a character at it + * @param aChar [in] the expected character + * @param aStartOffset [in] expected start offset of the character + * @param aEndOffset [in] expected end offset of the character + */ +function testCharAtOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset) { + var IDs = aIDs instanceof Array ? aIDs : [aIDs]; + for (var i = 0; i < IDs.length; i++) { + var acc = getAccessible(IDs[i], nsIAccessibleText); + testTextHelper( + IDs[i], + aOffset, + BOUNDARY_CHAR, + aChar, + aStartOffset, + aEndOffset, + kOk, + kOk, + kOk, + acc.getTextAtOffset, + "getTextAtOffset " + ); + } +} + +/** + * Test getTextAtOffset function over different elements. + * + * @param aIDs [in] ID or array of IDs + * @param aBoundaryType [in] boundary type for text to be retrieved + * @param aTestList [in] array of sets: + * offset1 and offset2 defining the offset range + * the text in the range + * start offset of the text in the range + * end offset of the text in the range + * + * or + * + * @param aOffset [in] the offset to get the text at + * @param aBoundaryType [in] Boundary type for text to be retrieved + * @param aText [in] expected return text for getTextAtOffset + * @param aStartOffset [in] expected return start offset for getTextAtOffset + * @param aEndOffset [in] expected return end offset for getTextAtOffset + * @param ... [in] list of ids or list of tuples made of: + * element identifier + * kTodo or kOk for returned text + * kTodo or kOk for returned start offset + * kTodo or kOk for returned offset result + */ +function testTextAtOffset() { + testTextSuperHelper("getTextAtOffset", arguments); +} + +/** + * Test getTextAfterOffset for BOUNDARY_CHAR over different elements. + * + * @param aIDs [in] the accessible identifier or array of accessible + * identifiers + * @param aOffset [in] the offset to get a character after it + * @param aChar [in] the expected character + * @param aStartOffset [in] expected start offset of the character + * @param aEndOffset [in] expected end offset of the character + */ +function testCharAfterOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset) { + var IDs = aIDs instanceof Array ? aIDs : [aIDs]; + for (var i = 0; i < IDs.length; i++) { + var acc = getAccessible(IDs[i], nsIAccessibleText); + testTextHelper( + IDs[i], + aOffset, + BOUNDARY_CHAR, + aChar, + aStartOffset, + aEndOffset, + kOk, + kOk, + kOk, + acc.getTextAfterOffset, + "getTextAfterOffset " + ); + } +} + +/** + * Test getTextAfterOffset function over different elements + * + * @param aIDs [in] ID or array of IDs + * @param aBoundaryType [in] boundary type for text to be retrieved + * @param aTestList [in] array of sets: + * offset1 and offset2 defining the offset range + * the text in the range + * start offset of the text in the range + * end offset of the text in the range + * + * or + * + * @param aOffset [in] the offset to get the text after + * @param aBoundaryType [in] Boundary type for text to be retrieved + * @param aText [in] expected return text for getTextAfterOffset + * @param aStartOffset [in] expected return start offset for getTextAfterOffset + * @param aEndOffset [in] expected return end offset for getTextAfterOffset + * @param ... [in] list of ids or list of tuples made of: + * element identifier + * kTodo or kOk for returned text + * kTodo or kOk for returned start offset + * kTodo or kOk for returned offset result + */ +function testTextAfterOffset( + aOffset, + aBoundaryType, + aText, + aStartOffset, + aEndOffset +) { + testTextSuperHelper("getTextAfterOffset", arguments); +} + +/** + * Test getTextBeforeOffset for BOUNDARY_CHAR over different elements. + * + * @param aIDs [in] the accessible identifier or array of accessible + * identifiers + * @param aOffset [in] the offset to get a character before it + * @param aChar [in] the expected character + * @param aStartOffset [in] expected start offset of the character + * @param aEndOffset [in] expected end offset of the character + */ +function testCharBeforeOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset) { + var IDs = aIDs instanceof Array ? aIDs : [aIDs]; + for (var i = 0; i < IDs.length; i++) { + var acc = getAccessible(IDs[i], nsIAccessibleText); + testTextHelper( + IDs[i], + aOffset, + BOUNDARY_CHAR, + aChar, + aStartOffset, + aEndOffset, + kOk, + kOk, + kOk, + acc.getTextBeforeOffset, + "getTextBeforeOffset " + ); + } +} + +/** + * Test getTextBeforeOffset function over different elements + * + * @param aIDs [in] ID or array of IDs + * @param aBoundaryType [in] boundary type for text to be retrieved + * @param aTestList [in] array of sets: + * offset1 and offset2 defining the offset range + * the text in the range + * start offset of the text in the range + * end offset of the text in the range + * + * or + * + * @param aOffset [in] the offset to get the text before + * @param aBoundaryType [in] Boundary type for text to be retrieved + * @param aText [in] expected return text for getTextBeforeOffset + * @param aStartOffset [in] expected return start offset for getTextBeforeOffset + * @param aEndOffset [in] expected return end offset for getTextBeforeOffset + * @param ... [in] list of ids or list of tuples made of: + * element identifier + * kTodo or kOk for returned text + * kTodo or kOk for returned start offset + * kTodo or kOk for returned offset result + */ +function testTextBeforeOffset( + aOffset, + aBoundaryType, + aText, + aStartOffset, + aEndOffset +) { + testTextSuperHelper("getTextBeforeOffset", arguments); +} + +/** + * Test word count for an element. + * + * @param aElement [in] element identifier + * @param aCount [in] Expected word count + * @param aToDoFlag [in] kTodo or kOk for returned text + */ +function testWordCount(aElement, aCount, aToDoFlag) { + var isFunc = aToDoFlag == kTodo ? todo_is : is; + var acc = getAccessible(aElement, nsIAccessibleText); + var startOffsetObj = {}, + endOffsetObj = {}; + var length = acc.characterCount; + var offset = 0; + var wordCount = 0; + while (true) { + acc.getTextAtOffset( + offset, + BOUNDARY_WORD_START, + startOffsetObj, + endOffsetObj + ); + if (offset >= length) { + break; + } + + wordCount++; + offset = endOffsetObj.value; + } + isFunc( + wordCount, + aCount, + "wrong words count for '" + + acc.getText(0, -1) + + "': " + + wordCount + + " in " + + prettyName(aElement) + ); +} + +/** + * Test word at a position for an element. + * + * @param aElement [in] element identifier + * @param aWordIndex [in] index of the word to test + * @param aText [in] expected text for that word + * @param aToDoFlag [in] kTodo or kOk for returned text + */ +function testWordAt(aElement, aWordIndex, aText, aToDoFlag) { + var isFunc = aToDoFlag == kTodo ? todo_is : is; + var acc = getAccessible(aElement, nsIAccessibleText); + + var textLength = acc.characterCount; + var wordIdx = aWordIndex; + var startOffsetObj = { value: 0 }, + endOffsetObj = { value: 0 }; + for (let offset = 0; offset < textLength; offset = endOffsetObj.value) { + acc.getTextAtOffset( + offset, + BOUNDARY_WORD_START, + startOffsetObj, + endOffsetObj + ); + + wordIdx--; + if (wordIdx < 0) { + break; + } + } + + if (wordIdx >= 0) { + ok( + false, + "the given word index '" + + aWordIndex + + "' exceeds words amount in " + + prettyName(aElement) + ); + + return; + } + + var startWordOffset = startOffsetObj.value; + var endWordOffset = endOffsetObj.value; + + // Calculate the end word offset. + acc.getTextAtOffset( + endOffsetObj.value, + BOUNDARY_WORD_END, + startOffsetObj, + endOffsetObj + ); + if (startOffsetObj.value != textLength) { + endWordOffset = startOffsetObj.value; + } + + if (endWordOffset <= startWordOffset) { + todo( + false, + "wrong start and end offset for word at index '" + + aWordIndex + + "': " + + " of text '" + + acc.getText(0, -1) + + "' in " + + prettyName(aElement) + ); + + return; + } + + let text = acc.getText(startWordOffset, endWordOffset); + isFunc( + text, + aText, + "wrong text for word at index '" + + aWordIndex + + "': " + + " of text '" + + acc.getText(0, -1) + + "' in " + + prettyName(aElement) + ); +} + +/** + * Test words in a element. + * + * @param aElement [in] element identifier + * @param aWords [in] array of expected words + * @param aToDoFlag [in, optional] kTodo or kOk for returned text + */ +function testWords(aElement, aWords, aToDoFlag) { + if (aToDoFlag == null) { + aToDoFlag = kOk; + } + + testWordCount(aElement, aWords.length, aToDoFlag); + + for (var i = 0; i < aWords.length; i++) { + testWordAt(aElement, i, aWords[i], aToDoFlag); + } +} + +/** + * Remove all selections. + * + * @param aID [in] Id, DOM node, or acc obj + */ +function cleanTextSelections(aID) { + var acc = getAccessible(aID, [nsIAccessibleText]); + + while (acc.selectionCount > 0) { + acc.removeSelection(0); + } +} + +/** + * Test addSelection method. + * + * @param aID [in] Id, DOM node, or acc obj + * @param aStartOffset [in] start offset for the new selection + * @param aEndOffset [in] end offset for the new selection + * @param aSelectionsCount [in] expected number of selections after addSelection + */ +function testTextAddSelection(aID, aStartOffset, aEndOffset, aSelectionsCount) { + var acc = getAccessible(aID, [nsIAccessibleText]); + var text = acc.getText(0, -1); + + acc.addSelection(aStartOffset, aEndOffset); + + is( + acc.selectionCount, + aSelectionsCount, + text + + ": failed to add selection from offset '" + + aStartOffset + + "' to offset '" + + aEndOffset + + "': selectionCount after" + ); +} + +/** + * Test removeSelection method. + * + * @param aID [in] Id, DOM node, or acc obj + * @param aSelectionIndex [in] index of the selection to be removed + * @param aSelectionsCount [in] expected number of selections after + * removeSelection + */ +function testTextRemoveSelection(aID, aSelectionIndex, aSelectionsCount) { + var acc = getAccessible(aID, [nsIAccessibleText]); + var text = acc.getText(0, -1); + + acc.removeSelection(aSelectionIndex); + + is( + acc.selectionCount, + aSelectionsCount, + text + + ": failed to remove selection at index '" + + aSelectionIndex + + "': selectionCount after" + ); +} + +/** + * Test setSelectionBounds method. + * + * @param aID [in] Id, DOM node, or acc obj + * @param aStartOffset [in] new start offset for the selection + * @param aEndOffset [in] new end offset for the selection + * @param aSelectionIndex [in] index of the selection to set + * @param aSelectionsCount [in] expected number of selections after + * setSelectionBounds + */ +function testTextSetSelection( + aID, + aStartOffset, + aEndOffset, + aSelectionIndex, + aSelectionsCount +) { + var acc = getAccessible(aID, [nsIAccessibleText]); + var text = acc.getText(0, -1); + + acc.setSelectionBounds(aSelectionIndex, aStartOffset, aEndOffset); + + is( + acc.selectionCount, + aSelectionsCount, + text + + ": failed to set selection at index '" + + aSelectionIndex + + "': selectionCount after" + ); +} + +/** + * Test selectionCount method. + * + * @param aID [in] Id, DOM node, or acc obj + * @param aCount [in] expected selection count + */ +function testTextSelectionCount(aID, aCount) { + var acc = getAccessible(aID, [nsIAccessibleText]); + var text = acc.getText(0, -1); + + is(acc.selectionCount, aCount, text + ": wrong selectionCount: "); +} + +/** + * Test getSelectionBounds method. + * + * @param aID [in] Id, DOM node, or acc obj + * @param aStartOffset [in] expected start offset for the selection + * @param aEndOffset [in] expected end offset for the selection + * @param aSelectionIndex [in] index of the selection to get + */ +function testTextGetSelection(aID, aStartOffset, aEndOffset, aSelectionIndex) { + var acc = getAccessible(aID, [nsIAccessibleText]); + var text = acc.getText(0, -1); + + var startObj = {}, + endObj = {}; + acc.getSelectionBounds(aSelectionIndex, startObj, endObj); + + is( + startObj.value, + aStartOffset, + text + ": wrong start offset for index '" + aSelectionIndex + "'" + ); + is( + endObj.value, + aEndOffset, + text + ": wrong end offset for index '" + aSelectionIndex + "'" + ); +} + +function testTextRange( + aRange, + aRangeDescr, + aStartContainer, + aStartOffset, + aEndContainer, + aEndOffset, + aText, + aCommonContainer, + aChildren +) { + isObject( + aRange.startContainer, + getAccessible(aStartContainer), + "Wrong start container of " + aRangeDescr + ); + is(aRange.startOffset, aStartOffset, "Wrong start offset of " + aRangeDescr); + isObject( + aRange.endContainer, + getAccessible(aEndContainer), + "Wrong end container of " + aRangeDescr + ); + is(aRange.endOffset, aEndOffset, "Wrong end offset of " + aRangeDescr); + + if (aText === undefined) { + return; + } + + is(aRange.text, aText, "Wrong text of " + aRangeDescr); + + var children = aRange.embeddedChildren; + is( + children ? children.length : 0, + aChildren ? aChildren.length : 0, + "Wrong embedded children count of " + aRangeDescr + ); + + isObject( + aRange.container, + getAccessible(aCommonContainer), + "Wrong container of " + aRangeDescr + ); + + if (aChildren) { + for (var i = 0; i < aChildren.length; i++) { + var expectedChild = getAccessible(aChildren[i]); + var actualChild = children.queryElementAt(i, nsIAccessible); + isObject( + actualChild, + expectedChild, + "Wrong child at index '" + i + "' of " + aRangeDescr + ); + } + } +} + +// ////////////////////////////////////////////////////////////////////////////// +// Private + +function testTextSuperHelper(aFuncName, aArgs) { + // List of tests. + if (aArgs[2] instanceof Array) { + let ids = aArgs[0] instanceof Array ? aArgs[0] : [aArgs[0]]; + let boundaryType = aArgs[1]; + let list = aArgs[2]; + for (let i = 0; i < list.length; i++) { + let offset1 = list[i][0], + offset2 = list[i][1]; + let text = list[i][2], + startOffset = list[i][3], + endOffset = list[i][4]; + let failureList = list[i][5]; + for (let offset = offset1; offset <= offset2; offset++) { + for (let idIdx = 0; idIdx < ids.length; idIdx++) { + let id = ids[idIdx]; + + let flagOk1 = kOk, + flagOk2 = kOk, + flagOk3 = kOk; + if (failureList) { + for (let fIdx = 0; fIdx < failureList.length; fIdx++) { + if ( + offset == failureList[fIdx][0] && + id == failureList[fIdx][1] + ) { + flagOk1 = failureList[fIdx][2]; + flagOk2 = failureList[fIdx][3]; + flagOk3 = failureList[fIdx][4]; + break; + } + } + } + + let acc = getAccessible(id, nsIAccessibleText); + testTextHelper( + id, + offset, + boundaryType, + text, + startOffset, + endOffset, + flagOk1, + flagOk2, + flagOk3, + acc[aFuncName], + aFuncName + " " + ); + } + } + } + return; + } + + // Test at single offset. List of IDs. + var offset = aArgs[0]; + var boundaryType = aArgs[1]; + var text = aArgs[2]; + var startOffset = aArgs[3]; + var endOffset = aArgs[4]; + if (aArgs[5] instanceof Array) { + let ids = aArgs[5]; + for (let i = 0; i < ids.length; i++) { + let acc = getAccessible(ids[i], nsIAccessibleText); + testTextHelper( + ids[i], + offset, + boundaryType, + text, + startOffset, + endOffset, + kOk, + kOk, + kOk, + acc[aFuncName], + aFuncName + " " + ); + } + + return; + } + + // Each ID is tested separately. + for (let i = 5; i < aArgs.length; i = i + 4) { + let ID = aArgs[i]; + let acc = getAccessible(ID, nsIAccessibleText); + let toDoFlag1 = aArgs[i + 1]; + let toDoFlag2 = aArgs[i + 2]; + let toDoFlag3 = aArgs[i + 3]; + + testTextHelper( + ID, + offset, + boundaryType, + text, + startOffset, + endOffset, + toDoFlag1, + toDoFlag2, + toDoFlag3, + acc[aFuncName], + aFuncName + " " + ); + } +} + +function testTextHelper( + aID, + aOffset, + aBoundaryType, + aText, + aStartOffset, + aEndOffset, + aToDoFlag1, + aToDoFlag2, + aToDoFlag3, + aTextFunc, + aTextFuncName +) { + var exceptionFlag = + aToDoFlag1 == undefined || + aToDoFlag2 == undefined || + aToDoFlag3 == undefined; + + var startMsg = aTextFuncName + "(" + boundaryToString(aBoundaryType) + "): "; + var endMsg = ", id: " + prettyName(aID) + ";"; + + try { + var startOffsetObj = {}, + endOffsetObj = {}; + var text = aTextFunc(aOffset, aBoundaryType, startOffsetObj, endOffsetObj); + + if (exceptionFlag) { + ok(false, startMsg + "no expected failure at offset " + aOffset + endMsg); + return; + } + + var isFunc1 = aToDoFlag1 == kTodo ? todo : ok; + var isFunc2 = aToDoFlag2 == kTodo ? todo : ok; + var isFunc3 = aToDoFlag3 == kTodo ? todo : ok; + + isFunc1( + text == aText, + startMsg + + "wrong text " + + "(got '" + + text + + "', expected: '" + + aText + + "')" + + ", offset: " + + aOffset + + endMsg + ); + isFunc2( + startOffsetObj.value == aStartOffset, + startMsg + + "wrong start offset" + + "(got '" + + startOffsetObj.value + + "', expected: '" + + aStartOffset + + "')" + + ", offset: " + + aOffset + + endMsg + ); + isFunc3( + endOffsetObj.value == aEndOffset, + startMsg + + "wrong end offset" + + "(got '" + + endOffsetObj.value + + "', expected: '" + + aEndOffset + + "')" + + ", offset: " + + aOffset + + endMsg + ); + } catch (e) { + var okFunc = exceptionFlag ? todo : ok; + okFunc( + false, + startMsg + "failed at offset " + aOffset + endMsg + ", exception: " + e + ); + } +} + +function boundaryToString(aBoundaryType) { + switch (aBoundaryType) { + case BOUNDARY_CHAR: + return "char"; + case BOUNDARY_WORD_START: + return "word start"; + case BOUNDARY_WORD_END: + return "word end"; + case BOUNDARY_LINE_START: + return "line start"; + case BOUNDARY_LINE_END: + return "line end"; + case BOUNDARY_PARAGRAPH: + return "paragraph"; + } + return "unknown"; +} diff --git a/accessible/tests/mochitest/text/a11y.ini b/accessible/tests/mochitest/text/a11y.ini new file mode 100644 index 0000000000..18c4982aa6 --- /dev/null +++ b/accessible/tests/mochitest/text/a11y.ini @@ -0,0 +1,19 @@ +[DEFAULT] +support-files = doc.html + !/accessible/tests/mochitest/*.js + +[test_atcaretoffset.html] +[test_charboundary.html] +[test_doc.html] +[test_dynamic.html] +[test_general.xhtml] +[test_gettext.html] +[test_hypertext.html] +[test_lineboundary.html] +[test_paragraphboundary.html] +[test_passwords.html] +[test_selection.html] +[test_settext_input_event.html] +[test_textBounds.html] +[test_wordboundary.html] +[test_words.html] diff --git a/accessible/tests/mochitest/text/doc.html b/accessible/tests/mochitest/text/doc.html new file mode 100644 index 0000000000..d57406c226 --- /dev/null +++ b/accessible/tests/mochitest/text/doc.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> + <script type="application/javascript"> + document.documentElement.appendChild(document.createTextNode("outbody")); + </script> +</head> +<body>inbody</body> +</html> diff --git a/accessible/tests/mochitest/text/test_atcaretoffset.html b/accessible/tests/mochitest/text/test_atcaretoffset.html new file mode 100644 index 0000000000..33fe7cd793 --- /dev/null +++ b/accessible/tests/mochitest/text/test_atcaretoffset.html @@ -0,0 +1,425 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test: nsIAccessibleText getText* functions at caret offset</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../text.js"></script> + + <script type="application/javascript"> + // gA11yEventDumpToConsole = true; // debugging + + function traverseTextByLines(aQueue, aID, aLines) { + var wholeText = ""; + for (var i = 0; i < aLines.length ; i++) + wholeText += aLines[i][0] + aLines[i][1]; + + var baseInvokerFunc = synthClick; + var charIter = new charIterator(wholeText, aLines); + // charIter.debugOffset = 10; // enable to run tests at given offset only + + while (charIter.next()) { + aQueue.push(new tmpl_moveTo(aID, baseInvokerFunc, wholeText, charIter)); + baseInvokerFunc = synthRightKey; + } + } + + /** + * Used to get test list for each traversed character. + */ + function charIterator(aWholeText, aLines) { + this.next = function charIterator_next() { + // Don't increment offset if we are at end of the wrapped line + // (offset is shared between end of this line and start of next line). + if (this.mAtWrappedLineEnd) { + this.mAtWrappedLineEnd = false; + this.mLine = this.mLine.nextLine; + return true; + } + + this.mOffset++; + if (this.mOffset > aWholeText.length) + return false; + + var nextLine = this.mLine.nextLine; + if (!nextLine.isFakeLine() && this.mOffset == nextLine.start) { + if (nextLine.start == this.mLine.end) + this.mAtWrappedLineEnd = true; + else + this.mLine = nextLine; + } + + return true; + }; + + Object.defineProperty(this, "offset", { get() { return this.mOffset; }, + }); + + Object.defineProperty(this, "offsetDescr", { get() { + return this.mOffset + " offset (" + this.mLine.number + " line, " + + (this.mOffset - this.mLine.start) + " offset on the line)"; + }, + }); + + Object.defineProperty(this, "tests", { get() { + // Line boundary tests. + var cLine = this.mLine; + var pLine = cLine.prevLine; + var ppLine = pLine.prevLine; + var nLine = cLine.nextLine; + var nnLine = nLine.nextLine; + + var lineTests = [ + [ testTextBeforeOffset, BOUNDARY_LINE_START, pLine.start, cLine.start], + [ testTextBeforeOffset, BOUNDARY_LINE_END, ppLine.end, pLine.end], + [ testTextAtOffset, BOUNDARY_LINE_START, cLine.start, nLine.start], + [ testTextAtOffset, BOUNDARY_LINE_END, pLine.end, cLine.end], + [ testTextAfterOffset, BOUNDARY_LINE_START, nLine.start, nnLine.start], + [ testTextAfterOffset, BOUNDARY_LINE_END, cLine.end, nLine.end], + ]; + + // Word boundary tests. + var cWord = this.mLine.firstWord; + var nWord = cWord.nextWord, pWord = cWord.prevWord; + + // The current word is a farthest word starting at or after the offset. + if (this.mOffset >= nWord.start) { + while (this.mOffset >= nWord.start && !this.mLine.isLastWord(cWord)) { + cWord = nWord; + nWord = nWord.nextWord; + } + pWord = cWord.prevWord; + } else if (this.mOffset < cWord.start) { + while (this.mOffset < cWord.start) { + cWord = pWord; + pWord = pWord.prevWord; + } + nWord = cWord.nextWord; + } + + var nnWord = nWord.nextWord, ppWord = pWord.prevWord; + + var isAfterWordEnd = + this.mOffset > cWord.end || cWord.line != this.mLine; + var isAtOrAfterWordEnd = (this.mOffset >= cWord.end); + var useNextWordForAtWordEnd = + isAtOrAfterWordEnd && this.mOffset != aWholeText.length; + + var wordTests = [ + [ testTextBeforeOffset, BOUNDARY_WORD_START, + pWord.start, cWord.start ], + [ testTextBeforeOffset, BOUNDARY_WORD_END, + (isAfterWordEnd ? pWord : ppWord).end, + (isAfterWordEnd ? cWord : pWord).end ], + [ testTextAtOffset, BOUNDARY_WORD_START, + cWord.start, nWord.start ], + [ testTextAtOffset, BOUNDARY_WORD_END, + (useNextWordForAtWordEnd ? cWord : pWord).end, + (useNextWordForAtWordEnd ? nWord : cWord).end ], + [ testTextAfterOffset, BOUNDARY_WORD_START, + nWord.start, nnWord.start ], + [ testTextAfterOffset, BOUNDARY_WORD_END, + (isAfterWordEnd ? nWord : cWord).end, + (isAfterWordEnd ? nnWord : nWord).end ], + ]; + + // Character boundary tests. + var prevOffset = this.offset > 1 ? this.offset - 1 : 0; + var nextOffset = this.offset >= aWholeText.length ? + this.offset : this.offset + 1; + var nextAfterNextOffset = nextOffset >= aWholeText.length ? + nextOffset : nextOffset + 1; + + var charTests = [ + [ testTextBeforeOffset, BOUNDARY_CHAR, + prevOffset, this.offset ], + [ testTextAtOffset, BOUNDARY_CHAR, + this.offset, + this.mAtWrappedLineEnd ? this.offset : nextOffset ], + [ testTextAfterOffset, BOUNDARY_CHAR, + this.mAtWrappedLineEnd ? this.offset : nextOffset, + this.mAtWrappedLineEnd ? nextOffset : nextAfterNextOffset ], + ]; + + return lineTests.concat(wordTests.concat(charTests)); + }, + }); + + Object.defineProperty(this, "failures", { get() { + if (this.mOffset == this.mLine.start) + return this.mLine.lineStartFailures; + if (this.mOffset == this.mLine.end) + return this.mLine.lineEndFailures; + return []; + }, + }); + + this.mOffset = -1; + this.mLine = new line(aWholeText, aLines, 0); + this.mAtWrappedLineEnd = false; + this.mWord = this.mLine.firstWord; + } + + /** + * A line object. Allows to navigate by lines and by words. + */ + function line(aWholeText, aLines, aIndex) { + Object.defineProperty(this, "prevLine", { get() { + return new line(aWholeText, aLines, aIndex - 1); + }, + }); + Object.defineProperty(this, "nextLine", { get() { + return new line(aWholeText, aLines, aIndex + 1); + }, + }); + + Object.defineProperty(this, "start", { get() { + if (aIndex < 0) + return 0; + + if (aIndex >= aLines.length) + return aWholeText.length; + + return aLines[aIndex][2]; + }, + }); + Object.defineProperty(this, "end", { get() { + if (aIndex < 0) + return 0; + + if (aIndex >= aLines.length) + return aWholeText.length; + + return aLines[aIndex][3]; + }, + }); + + Object.defineProperty(this, "number", { get() { return aIndex; }, + }); + Object.defineProperty(this, "wholeText", { get() { return aWholeText; }, + }); + this.isFakeLine = function line_isFakeLine() { + return aIndex < 0 || aIndex >= aLines.length; + }; + + Object.defineProperty(this, "lastWord", { get() { + if (aIndex < 0) + return new word(this, [], -1); + if (aIndex >= aLines.length) + return new word(this, [], 0); + + var words = aLines[aIndex][4].words; + return new word(this, words, words.length - 2); + }, + }); + Object.defineProperty(this, "firstWord", { get() { + if (aIndex < 0) + return new word(this, [], -1); + if (aIndex >= aLines.length) + return new word(this, [], 0); + + var words = aLines[aIndex][4].words; + return new word(this, words, 0); + }, + }); + + this.isLastWord = function line_isLastWord(aWord) { + var lastWord = this.lastWord; + return lastWord.start == aWord.start && lastWord.end == aWord.end; + }; + + Object.defineProperty(this, "lineStartFailures", { get() { + if (aIndex < 0 || aIndex >= aLines.length) + return []; + + return aLines[aIndex][4].lsf || []; + }, + }); + Object.defineProperty(this, "lineEndFailures", { get() { + if (aIndex < 0 || aIndex >= aLines.length) + return []; + + return aLines[aIndex][4].lef || []; + }, + }); + } + + /** + * A word object. Allows to navigate by words. + */ + function word(aLine, aWords, aIndex) { + Object.defineProperty(this, "prevWord", { get() { + if (aIndex >= 2) + return new word(aLine, aWords, aIndex - 2); + + var prevLineLastWord = aLine.prevLine.lastWord; + if (this.start == prevLineLastWord.start && !this.isFakeStartWord()) + return prevLineLastWord.prevWord; + return prevLineLastWord; + }, + }); + Object.defineProperty(this, "nextWord", { get() { + if (aIndex + 2 < aWords.length) + return new word(aLine, aWords, aIndex + 2); + + var nextLineFirstWord = aLine.nextLine.firstWord; + if (this.end == nextLineFirstWord.end && !this.isFakeEndWord()) + return nextLineFirstWord.nextWord; + return nextLineFirstWord; + }, + }); + + Object.defineProperty(this, "line", { get() { return aLine; } }); + + Object.defineProperty(this, "start", { get() { + if (this.isFakeStartWord()) + return 0; + + if (this.isFakeEndWord()) + return aLine.end; + return aWords[aIndex]; + }, + }); + Object.defineProperty(this, "end", { get() { + if (this.isFakeStartWord()) + return 0; + + return this.isFakeEndWord() ? aLine.end : aWords[aIndex + 1]; + }, + }); + + this.toString = function word_toString() { + var start = this.start, end = this.end; + return "'" + aLine.wholeText.substring(start, end) + + "' at [" + start + ", " + end + "]"; + }; + + this.isFakeStartWord = function() { return aIndex < 0; }; + this.isFakeEndWord = function() { return aIndex >= aWords.length; }; + } + + /** + * A template invoker to move through the text. + */ + function tmpl_moveTo(aID, aInvokerFunc, aWholeText, aCharIter) { + this.offset = aCharIter.offset; + + var checker = new caretMoveChecker(this.offset, true, aID); + this.__proto__ = new (aInvokerFunc)(aID, checker); + + this.finalCheck = function genericMoveTo_finalCheck() { + if (this.noTests()) + return; + + for (var i = 0; i < this.tests.length; i++) { + var func = this.tests[i][0]; + var boundary = this.tests[i][1]; + var startOffset = this.tests[i][2]; + var endOffset = this.tests[i][3]; + var text = aWholeText.substring(startOffset, endOffset); + + var isOk1 = kOk, isOk2 = kOk, isOk3 = kOk; + for (var fIdx = 0; fIdx < this.failures.length; fIdx++) { + var failure = this.failures[fIdx]; + if (func.name.includes(failure[0]) && boundary == failure[1]) { + isOk1 = failure[2]; + isOk2 = failure[3]; + isOk3 = failure[4]; + } + } + + func(kCaretOffset, boundary, text, startOffset, endOffset, + aID, isOk1, isOk2, isOk3); + } + }; + + this.getID = function genericMoveTo_getID() { + return "move to " + this.offsetDescr; + }; + + this.noTests = function tmpl_moveTo_noTests() { + return ("debugOffset" in aCharIter) && + (aCharIter.debugOffset != this.offset); + }; + + this.offsetDescr = aCharIter.offsetDescr; + this.tests = this.noTests() ? null : aCharIter.tests; + this.failures = aCharIter.failures; + } + + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + + // __a__w__o__r__d__\n + // 0 1 2 3 4 5 + // __t__w__o__ (soft line break) + // 6 7 8 9 + // __w__o__r__d__s + // 10 11 12 13 14 15 + + traverseTextByLines(gQueue, "textarea", + [ [ "aword", "\n", 0, 5, { words: [ 0, 5 ] } ], + [ "two ", "", 6, 10, { words: [ 6, 9 ] } ], + [ "words", "", 10, 15, { words: [ 10, 15 ] } ], + ] ); + + var line4 = [ // "riend " + [ "TextBeforeOffset", BOUNDARY_WORD_END, + kOk, kOk, kOk], + [ "TextAfterOffset", BOUNDARY_WORD_END, + kOk, kOk, kOk ], + ]; + traverseTextByLines(gQueue, "ta_wrapped", + [ [ "hi ", "", 0, 3, { words: [ 0, 2 ] } ], + [ "hello ", "", 3, 9, { words: [ 3, 8 ] } ], + [ "my ", "", 9, 12, { words: [ 9, 11 ] } ], + [ "longf", "", 12, 17, { words: [ 12, 17 ] } ], + [ "riend ", "", 17, 23, { words: [ 17, 22 ], lsf: line4 } ], + [ "t sq ", "", 23, 28, { words: [ 23, 24, 25, 27 ] } ], + [ "t", "", 28, 29, { words: [ 28, 29 ] } ], + ] ); + + gQueue.invoke(); // will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="nsIAccessibleText getText related functions tests at caret offset" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=852021"> + Bug 852021 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + + <textarea id="textarea" cols="5">aword +two words</textarea> + + <!-- scrollbar-width: none is needed so that the width of the scrollbar + doesn't incorrectly affect the width of the textarea on some systems. + See bug 1600170 and bug 33654. + --> + <textarea id="ta_wrapped" cols="5" style="scrollbar-width: none;">hi hello my longfriend t sq t</textarea> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_charboundary.html b/accessible/tests/mochitest/text/test_charboundary.html new file mode 100644 index 0000000000..5ca4120a47 --- /dev/null +++ b/accessible/tests/mochitest/text/test_charboundary.html @@ -0,0 +1,138 @@ +<!DOCTYPE html> +<html> +<head> + <title>Char boundary text tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // + // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__ + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + + var IDs = [ "i1", "d1", "e1", "t1" ]; + + testCharBeforeOffset(IDs, 0, "", 0, 0); + testCharBeforeOffset(IDs, 1, "h", 0, 1); + testCharBeforeOffset(IDs, 14, "n", 13, 14); + testCharBeforeOffset(IDs, 15, "d", 14, 15); + + testCharAtOffset(IDs, 0, "h", 0, 1); + testCharAtOffset(IDs, 1, "e", 1, 2); + testCharAtOffset(IDs, 14, "d", 14, 15); + testCharAtOffset(IDs, 15, "", 15, 15); + + testCharAfterOffset(IDs, 0, "e", 1, 2); + testCharAfterOffset(IDs, 1, "l", 2, 3); + testCharAfterOffset(IDs, 14, "", 15, 15); + testCharAfterOffset(IDs, 15, "", 15, 15); + + // //////////////////////////////////////////////////////////////////////// + // + // __B__r__a__v__e__ __S__i__r__ __ __R__o__b__i__n__ __ __ __r__a__n + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + + IDs = [ "i2", "d2", "e2", "t2" ]; + + testCharBeforeOffset(IDs, 0, "", 0, 0); + testCharBeforeOffset(IDs, 1, "B", 0, 1); + testCharBeforeOffset(IDs, 6, " ", 5, 6); + testCharBeforeOffset(IDs, 10, " ", 9, 10); + testCharBeforeOffset(IDs, 11, " ", 10, 11); + testCharBeforeOffset(IDs, 17, " ", 16, 17); + testCharBeforeOffset(IDs, 19, " ", 18, 19); + + testCharAtOffset(IDs, 0, "B", 0, 1); + testCharAtOffset(IDs, 1, "r", 1, 2); + testCharAtOffset(IDs, 5, " ", 5, 6); + testCharAtOffset(IDs, 9, " ", 9, 10); + testCharAtOffset(IDs, 10, " ", 10, 11); + testCharAtOffset(IDs, 17, " ", 17, 18); + + testCharAfterOffset(IDs, 0, "r", 1, 2); + testCharAfterOffset(IDs, 1, "a", 2, 3); + testCharAfterOffset(IDs, 4, " ", 5, 6); + testCharAfterOffset(IDs, 5, "S", 6, 7); + testCharAfterOffset(IDs, 8, " ", 9, 10); + testCharAfterOffset(IDs, 9, " ", 10, 11); + testCharAfterOffset(IDs, 10, "R", 11, 12); + testCharAfterOffset(IDs, 15, " ", 16, 17); + testCharAfterOffset(IDs, 16, " ", 17, 18); + testCharAfterOffset(IDs, 17, " ", 18, 19); + testCharAfterOffset(IDs, 18, "r", 19, 20); + + // //////////////////////////////////////////////////////////////////////// + // + // __o__n__e__w__o__r__d__\n + // 0 1 2 3 4 5 6 7 + // __\n + // 8 + // __t__w__o__ __w__o__r__d__s__\n + // 9 10 11 12 13 14 15 16 17 18 + + IDs = ["d3", "dbr3", "e3", "ebr3", "t3"]; + + testCharBeforeOffset(IDs, 8, "\n", 7, 8); + testCharBeforeOffset(IDs, 9, "\n", 8, 9); + testCharBeforeOffset(IDs, 10, "t", 9, 10); + + testCharAtOffset(IDs, 7, "\n", 7, 8); + testCharAtOffset(IDs, 8, "\n", 8, 9); + testCharAtOffset(IDs, 9, "t", 9, 10); + + testCharAfterOffset(IDs, 6, "\n", 7, 8); + testCharAfterOffset(IDs, 7, "\n", 8, 9); + testCharAfterOffset(IDs, 8, "t", 9, 10); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="i1" value="hello my friend"/> + <div id="d1">hello my friend</div> + <div id="e1" contenteditable="true">hello my friend</div> + <textarea id="t1" contenteditable="true">hello my friend</textarea> + + <input id="i2" value="Brave Sir Robin ran"/> + <pre> + <div id="d2">Brave Sir Robin ran</div> + <div id="e2" contenteditable="true">Brave Sir Robin ran</div> + </pre> + <textarea id="t2" cols="300">Brave Sir Robin ran</textarea> + + <pre> + <div id="d3">oneword + +two words +</div> + <div id="dbr3">oneword<br/><br/>two words<br/></div> + <div id="e3" contenteditable="true">oneword + +two words +</div> + <div id="ebr3" contenteditable="true">oneword<br/><br/>two words<br/></div> + <textarea id="t3" cols="300">oneword + +two words</textarea> + </pre> + +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_doc.html b/accessible/tests/mochitest/text/test_doc.html new file mode 100644 index 0000000000..88b75b98c4 --- /dev/null +++ b/accessible/tests/mochitest/text/test_doc.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> +<head> + <title>nsIAccessibleText getText related function tests for document accessible</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + <script type="application/javascript"> + + function doTest() { + var iframeDoc = [ getNode("iframe").contentDocument ]; + testCharacterCount(iframeDoc, 15); + testText(iframeDoc, 0, 15, "outbody inbody "); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Elements appended outside the body aren't accessible" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887">Mozilla Bug 608887</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <iframe id="iframe" src="doc.html"></iframe> + +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_dynamic.html b/accessible/tests/mochitest/text/test_dynamic.html new file mode 100644 index 0000000000..63889fc664 --- /dev/null +++ b/accessible/tests/mochitest/text/test_dynamic.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<html> +<head> + <title>nsIAccessibleText getText related function tests for tree mutations</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function insertBefore(aId, aEl, aTextBefore, aTextAfter, aStartIdx, aEndIdx) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aId), + ]; + + this.invoke = function insertBefore_invoke() { + testText(aId, 0, -1, aTextBefore); + getNode(aId).insertBefore(aEl, getNode(aId).firstChild); + }; + + this.finalCheck = function insertBefore_finalCheck() { + testText(aId, aStartIdx, aEndIdx, aTextAfter); + }; + + this.getID = function insertTextBefore_getID() { + return "insert " + prettyName(aEl) + " before"; + }; + } + + function insertTextBefore(aId, aTextBefore, aText) { + var el = document.createTextNode(aText); + this.__proto__ = new insertBefore(aId, el, aTextBefore, + aText + aTextBefore, 0, -1); + } + + function insertImgBefore(aId, aTextBefore) { + var el = document.createElement("img"); + el.setAttribute("src", "../moz.png"); + el.setAttribute("alt", "mozilla"); + + this.__proto__ = new insertBefore(aId, el, aTextBefore, + kEmbedChar + aTextBefore, 0, -1); + } + + function insertTextBefore2(aId) { + var el = document.createTextNode("hehe"); + this.__proto__ = new insertBefore(aId, el, "ho", "ho", 4, -1); + } + + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + gQueue.push(new insertTextBefore("c1", "ho", "ha")); + gQueue.push(new insertImgBefore("c1", "haho")); + gQueue.push(new insertImgBefore("c2", kEmbedChar)); + gQueue.push(new insertTextBefore2("c3")); + gQueue.invoke(); // will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="c1">ho</div> + <div id="c2"><img src="../moz.png" alt="mozilla"></div> + <div id="c3">ho</div> +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_general.xhtml b/accessible/tests/mochitest/text/test_general.xhtml new file mode 100644 index 0000000000..df0ffcc0c6 --- /dev/null +++ b/accessible/tests/mochitest/text/test_general.xhtml @@ -0,0 +1,79 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Tests: XUL label text interface"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../text.js"></script> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Testing + + var gQueue = null; + function doTests() + { + ////////////////////////////////////////////////////////////////////////// + // XUL label + + var ids = ["label1", "label2"]; + + testCharacterCount(ids, 5); + + testText(ids, 0, -1, "Hello"); + testText(ids, 0, 1, "H"); + + testCharAfterOffset(ids, 0, "e", 1, 2); + testCharBeforeOffset(ids, 1, "H", 0, 1); + testCharAtOffset(ids, 1, "e", 1, 2); + + ////////////////////////////////////////////////////////////////////////// + // HTML input + + testTextAtOffset([ getNode("tbox1") ], BOUNDARY_LINE_START, + [ [ 0, 4, "test", 0, 4 ] ]); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + ]]> + </script> + + <vbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=396166" + title="xul:label@value accessible should implement nsIAccessibleText"> + Bug 396166 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=899433" + title="Accessibility returns empty line for last line in certain cases"> + Bug 899433 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + <label id="label1" value="Hello"/> + <label id="label2">Hello</label> + + <html:input id="tbox1" value="test"/> + </vbox> +</window> diff --git a/accessible/tests/mochitest/text/test_gettext.html b/accessible/tests/mochitest/text/test_gettext.html new file mode 100644 index 0000000000..2f221a416b --- /dev/null +++ b/accessible/tests/mochitest/text/test_gettext.html @@ -0,0 +1,135 @@ +<!DOCTYPE html> +<html> +<head> + <title>Get text between offsets tests</title> + <meta charset="utf-8"> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // + // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__ + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + + var IDs = [ "i1", "d1", "d1wrap", "e1", "t1" ]; + + testCharacterCount(IDs, 15); + + testText(IDs, 0, 1, "h"); + testText(IDs, 1, 3, "el"); + testText(IDs, 14, 15, "d"); + testText(IDs, 0, 15, "hello my friend"); + testText(IDs, 0, -1, "hello my friend"); + + // //////////////////////////////////////////////////////////////////////// + // + // __B__r__a__v__e__ __S__i__r__ __ __R__o__b__i__n__ __ __ __r__a__n + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + + IDs = [ "i2", "dpre2", "epre2", "t2" ]; + + testCharacterCount(IDs, 22); + + testText(IDs, 0, 1, "B"); + testText(IDs, 5, 6, " "); + testText(IDs, 9, 11, " "); + testText(IDs, 16, 19, " "); + testText(IDs, 0, 22, "Brave Sir Robin ran"); + testText(IDs, 0, -1, "Brave Sir Robin ran"); + + testCharacterCount(["d2", "e2"], 19); + testText(["d2", "e2"], 0, 19, "Brave Sir Robin ran"); + + // //////////////////////////////////////////////////////////////////////// + // + // __o__n__e__w__o__r__d__\n + // 0 1 2 3 4 5 6 7 + // __\n + // 8 + // __t__w__o__ __w__o__r__d__s__\n + // 9 10 11 12 13 14 15 16 17 18 + + IDs = ["d3", "dbr3", "e3", "ebr3", "t3"]; + + testCharacterCount(IDs, 19); + + testText(IDs, 0, 19, "oneword\n\ntwo words\n"); + testText(IDs, 0, -1, "oneword\n\ntwo words\n"); + + // //////////////////////////////////////////////////////////////////////// + // + // CSS text-transform + // + // Content with `text-transform:uppercase | lowercase | capitalize` returns + // the transformed content. + // + testText(["d4a"], 0, -1, "HELLO MY FRIEND"); + testText(["d4b"], 0, -1, "hello my friend"); + testText(["d4c"], 0, -1, "Hello My Friend"); + + // `text-transform: full-width | full-size-kana` should not be reflected in + // a11y. + testText(["d5a"], 0, -1, "hello my friend"); + testText(["d5b"], 0, -1, "ゕゖヵヶ"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="i1" value="hello my friend"/> + <div id="d1">hello my friend</div> + <div id="d1wrap" style="word-wrap:break-word; width:1px">hello my friend</div> + <div id="e1" contenteditable="true">hello my friend</div> + <textarea id="t1">hello my friend</textarea> + + <input id="i2" value="Brave Sir Robin ran"/> + <pre><div id="dpre2">Brave Sir Robin ran</div></pre> + <pre><div id="epre2" contenteditable="true">Brave Sir Robin ran</div></pre> + <textarea id="t2" cols="300">Brave Sir Robin ran</textarea> + <div id="d2">Brave Sir Robin ran</div> + <div id="e2" contenteditable="true">Brave Sir Robin ran</div> + + <pre> + <div id="d3">oneword + +two words +</div> + <div id="dbr3">oneword<br/><br/>two words<br/></div> + <div id="e3" contenteditable="true">oneword + +two words +</div> + <div id="ebr3" contenteditable="true">oneword<br/><br/>two words<br/></div> + <textarea id="t3" cols="300">oneword + +two words +</textarea> + </pre> + + <div id="d4a" style="text-transform:uppercase">Hello My Friend</div> + <div id="d4b" style="text-transform:lowercase">Hello My Friend</div> + <div id="d4c" style="text-transform:capitalize">hello my friend</div> + + <div id="d5a" style="text-transform:full-width">hello my friend</div> + <div id="d5b" style="text-transform:full-size-kana">ゕゖヵヶ</div> + +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_hypertext.html b/accessible/tests/mochitest/text/test_hypertext.html new file mode 100644 index 0000000000..b8a289ea52 --- /dev/null +++ b/accessible/tests/mochitest/text/test_hypertext.html @@ -0,0 +1,150 @@ +<!DOCTYPE html> +<html> +<head> + <title>nsIAccessibleText getText related function tests for rich text</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style> + #listitemnone { + list-style-type: none; + } + h6.gencontent:before { + content: "aga" + } + </style> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // null getText + // //////////////////////////////////////////////////////////////////////// + + var emptyTextAcc = getAccessible("nulltext", [nsIAccessibleText]); + is(emptyTextAcc.getText(0, -1), "", "getText() END_OF_TEXT with null string"); + is(emptyTextAcc.getText(0, 0), "", "getText() Len==0 with null string"); + + // //////////////////////////////////////////////////////////////////////// + // hypertext + // //////////////////////////////////////////////////////////////////////// + + // ! - embedded object char + // __h__e__l__l__o__ __!__ __s__e__e__ __!__ + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 + + var IDs = [ "hypertext", "hypertext2", "ht_displaycontents" ]; + + // ////////////////////////////////////////////////////////////////////// + // characterCount + + testCharacterCount(IDs, 13); + + // ////////////////////////////////////////////////////////////////////// + // getText + + testText(IDs, 0, 1, "h"); + testText(IDs, 5, 7, " " + kEmbedChar); + testText(IDs, 10, 13, "e " + kEmbedChar); + testText(IDs, 0, 13, "hello " + kEmbedChar + " see " + kEmbedChar); + + // ////////////////////////////////////////////////////////////////////// + // getTextAtOffset line boundary + + testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5, + "hypertext3", kOk, kOk, kOk); + + // XXX: see bug 634202. + testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5, + "hypertext4", kOk, kOk, kOk); + + // //////////////////////////////////////////////////////////////////////// + // list + // //////////////////////////////////////////////////////////////////////// + + IDs = [ "list" ]; + testCharacterCount(IDs, 2); + testText(IDs, 0, 2, kEmbedChar + kEmbedChar); + + IDs = [ "listitem" ]; + testCharacterCount(IDs, 6); + testText(IDs, 0, 6, "1. foo"); + + IDs = [ "listitemnone" ]; + testCharacterCount(IDs, 3); + testText(IDs, 0, 3, "bar"); + + testText(["testbr"], 0, 3, "foo"); + + testTextAtOffset(2, nsIAccessibleText.BOUNDARY_CHAR, "o", 2, 3, "testbr", + kOk, kOk, kOk); + testTextAtOffset(2, nsIAccessibleText.BOUNDARY_WORD_START, "foo\n", 0, 4, + "testbr", kOk, kOk, kOk); + testTextBeforeOffset(2, nsIAccessibleText.BOUNDARY_LINE_START, "foo\n", + 0, 4, "testbr", kTodo, kOk, kTodo); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Fix getText" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=630001"> + Bug 630001, part3 + </a> + <a target="_blank" + title="getTextAtOffset line boundary may return more than one line" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=638326"> + Bug 638326 + </a> + <a target="_blank" + title="getText(0, -1) fails with empty text" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=749810"> + Bug 749810 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="nulltext"></div> + + <div id="hypertext">hello <a>friend</a> see <img src="about:blank"></div> + <div id="hypertext2">hello <a>friend</a> see <input></div> + <div id="ht_displaycontents">hello <a>friend</a> see <ul id="ul" style="display: contents;"> + <li>Supermarket 1</li> + <li>Supermarket 2</li> + </ul></div> + <ol id="list"> + <li id="listitem">foo</li> + <li id="listitemnone">bar</li> + </ol> + + <div id="hypertext3">line +<!-- haha --> +<!-- hahaha --> +<h6>heading</h6> + </div> + + <div id="hypertext4">line +<!-- haha --> +<!-- hahaha --> +<h6 role="presentation" class="gencontent">heading</h6> + </div> + + <div id="testbr">foo<br/></div> + + <div></div> +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_lineboundary.html b/accessible/tests/mochitest/text/test_lineboundary.html new file mode 100644 index 0000000000..77b35ece5d --- /dev/null +++ b/accessible/tests/mochitest/text/test_lineboundary.html @@ -0,0 +1,422 @@ +<!DOCTYPE html> +<html> +<head> + <title>Line boundary getText* functions tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + <script type="application/javascript"> + function doTest() { + testTextAtOffset("line_test_1", BOUNDARY_LINE_START, + [[0, 6, "Line 1 ", 0, 7], + // See the kOk test below. + // [7, 7, kEmbedChar, 7, 8], + [8, 15, "Line 3 ", 8, 15]]); + testTextAtOffset(/* aOffset */ 7, BOUNDARY_LINE_START, + kEmbedChar, /* aStartOffset */ 7, /* aEndOffset */ 8, + "line_test_1", + /* returned text */ kOk, + /* returned start offset */ kOk, /* returned end offset */ kOk); + + // //////////////////////////////////////////////////////////////////////// + // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__ + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + + var IDs = [ "input", "div", "editable", "textarea", + getNode("ta", getNode("ta_cntr").contentDocument) ]; + + testTextBeforeOffset(IDs, BOUNDARY_LINE_START, + [ [ 0, 15, "", 0, 0 ] ]); + testTextBeforeOffset(IDs, BOUNDARY_LINE_END, + [ [ 0, 15, "", 0, 0 ] ]); + + testTextAtOffset(IDs, BOUNDARY_LINE_START, + [ [ 0, 15, "hello my friend", 0, 15 ] ]); + testTextAtOffset(IDs, BOUNDARY_LINE_END, + [ [ 0, 15, "hello my friend", 0, 15 ] ]); + + testTextAfterOffset(IDs, BOUNDARY_LINE_START, + [ [ 0, 15, "", 15, 15 ] ]); + testTextAfterOffset(IDs, BOUNDARY_LINE_END, + [ [ 0, 15, "", 15, 15 ] ]); + + // //////////////////////////////////////////////////////////////////////// + // __o__n__e__w__o__r__d__\n + // 0 1 2 3 4 5 6 7 + // __\n + // 8 + // __t__w__o__ __w__o__r__d__s__\n + // 9 10 11 12 13 14 15 16 17 18 + + IDs = [ "ml_div", "ml_divbr", "ml_editable", "ml_editablebr", "ml_textarea"]; + + testTextBeforeOffset(IDs, BOUNDARY_LINE_START, + [ [ 0, 7, "", 0, 0 ], + [ 8, 8, "oneword\n", 0, 8 ], + [ 9, 18, "\n", 8, 9 ], + [ 19, 19, "two words\n", 9, 19 ]]); + + testTextBeforeOffset(IDs, BOUNDARY_LINE_END, + [ [ 0, 7, "", 0, 0 ], + [ 8, 8, "oneword", 0, 7 ], + [ 9, 18, "\n", 7, 8 ], + [ 19, 19, "\ntwo words", 8, 18 ]]); + + testTextAtOffset(IDs, BOUNDARY_LINE_START, + [ [ 0, 7, "oneword\n", 0, 8 ], + [ 8, 8, "\n", 8, 9 ], + [ 9, 18, "two words\n", 9, 19 ], + [ 19, 19, "", 19, 19 ]]); + testTextAtOffset(IDs, BOUNDARY_LINE_END, + [ [ 0, 7, "oneword", 0, 7 ], + [ 8, 8, "\n", 7, 8 ], + [ 9, 18, "\ntwo words", 8, 18 ], + [ 19, 19, "\n", 18, 19 ]]); + + testTextAfterOffset(IDs, BOUNDARY_LINE_START, + [ [ 0, 7, "\n", 8, 9 ], + [ 8, 8, "two words\n", 9, 19 ], + [ 9, 19, "", 19, 19 ]]); + testTextAfterOffset(IDs, BOUNDARY_LINE_END, + [ [ 0, 7, "\n", 7, 8 ], + [ 8, 8, "\ntwo words", 8, 18 ], + [ 9, 18, "\n", 18, 19 ], + [ 19, 19, "", 19, 19 ]]); + + // //////////////////////////////////////////////////////////////////////// + // a * b (* is embedded char for link) + testTextBeforeOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_START, + [ [ 0, 5, "", 0, 0 ] ]); + + testTextBeforeOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_END, + [ [ 0, 5, "", 0, 0 ] ]); + + testTextAtOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_START, + [ [ 0, 5, "a " + kEmbedChar + " c", 0, 5 ] ]); + + testTextAtOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_END, + [ [ 0, 5, "a " + kEmbedChar + " c", 0, 5 ] ]); + + testTextAfterOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_START, + [ [ 0, 5, "", 5, 5 ] ]); + + testTextAfterOffset([ getAccessible("ht_1").firstChild ], BOUNDARY_LINE_END, + [ [ 0, 5, "", 5, 5 ] ]); + + // //////////////////////////////////////////////////////////////////////// + // foo<br> and foo<br><br> + + testTextAtOffset([ getAccessible("ht_2").firstChild.firstChild ], + BOUNDARY_LINE_START, + [ [ 0, 3, "foo\n", 0, 4 ] ]); + testTextAtOffset([ getAccessible("ht_3").firstChild.firstChild ], + BOUNDARY_LINE_START, + [ [ 0, 3, "foo\n", 0, 4 ], [ 4, 4, "\n", 4, 5 ] ]); + + // //////////////////////////////////////////////////////////////////////// + // 'Hello world ' (\n is rendered as space) + + testTextAtOffset([ "ht_4" ], BOUNDARY_LINE_START, + [ [ 0, 12, "Hello world ", 0, 12 ] ]); + + // //////////////////////////////////////////////////////////////////////// + // list items + + testTextAtOffset([ "li1" ], BOUNDARY_LINE_START, + [ [ 0, 6, kDiscBulletText + "Item", 0, 6 ] ]); + testTextAtOffset([ "li2" ], BOUNDARY_LINE_START, + [ [ 0, 2, kDiscBulletText, 0, 2 ] ]); + testTextAtOffset([ "li3" ], BOUNDARY_LINE_START, + [ [ 0, 8, kDiscBulletText + "a long ", 0, 9 ], + [ 9, 12, "and ", 9, 13 ] ]); + testTextAtOffset([ "li4" ], BOUNDARY_LINE_START, + [ [ 0, 7, kDiscBulletText + "a " + kEmbedChar + " c", 0, 7 ] ]); + testTextAtOffset([ "li5" ], BOUNDARY_LINE_START, + [ [ 0, 2, kDiscBulletText + "\n", 0, 3 ], + [ 3, 7, "hello", 3, 8 ] ]); + testTextAtOffset([ "ul1" ], BOUNDARY_LINE_START, + [ [ 0, 0, kEmbedChar, 0, 1 ], + [ 1, 1, kEmbedChar, 1, 2 ], + [ 2, 2, kEmbedChar, 2, 3 ], + [ 3, 3, kEmbedChar, 3, 4 ], + [ 4, 5, kEmbedChar, 4, 5 ] ]); + + testTextAtOffset([ "li6" ], BOUNDARY_LINE_START, + [ [ 0, 7, "1. Item", 0, 7 ] ]); + testTextAtOffset([ "li7" ], BOUNDARY_LINE_START, + [ [ 0, 3, "2. ", 0, 3 ] ]); + testTextAtOffset([ "li8" ], BOUNDARY_LINE_START, + [ [ 0, 9, "3. a long ", 0, 10 ], + [ 10, 13, "and ", 10, 14 ] ]); + testTextAtOffset([ "li9" ], BOUNDARY_LINE_START, + [ [ 0, 8, "4. a " + kEmbedChar + " c", 0, 8 ] ]); + testTextAtOffset([ "li10" ], BOUNDARY_LINE_START, + [ [ 0, 3, "5. \n", 0, 4 ], + [ 4, 8, "hello", 4, 9 ] ]); + testTextAtOffset([ "ol1" ], BOUNDARY_LINE_START, + [ [ 0, 0, kEmbedChar, 0, 1 ], + [ 1, 1, kEmbedChar, 1, 2 ], + [ 2, 2, kEmbedChar, 2, 3 ], + [ 3, 3, kEmbedChar, 3, 4 ], + [ 4, 5, kEmbedChar, 4, 5 ] ]); + + // //////////////////////////////////////////////////////////////////////// + // Nested hypertexts + + testTextAtOffset(["ht_5" ], BOUNDARY_LINE_START, + [ [ 0, 0, kEmbedChar, 0, 1 ] ]); + + // //////////////////////////////////////////////////////////////////////// + // Block followed by list + + testTextAtOffset([ "block_then_ul" ], BOUNDARY_LINE_START, + [ [ 0, 0, kEmbedChar, 0, 1 ], + [ 1, 1, kEmbedChar, 1, 2 ] ]); + + // Embedded char containing a line break breaks line offsets in parent. + testTextAtOffset([ "brInEmbed" ], BOUNDARY_LINE_START, + [ [0, 1, "a " + kEmbedChar, 0, 3], + [2, 2, "a " + kEmbedChar + " d", 0, 5], + [3, 5, kEmbedChar + " d", 2, 5] ]); + testTextAtOffset([ "brInEmbedAndBefore" ], BOUNDARY_LINE_START, + [ [0, 1, "a\n", 0, 2], + [2, 3, "b " + kEmbedChar, 2, 5], + [4, 4, "b " + kEmbedChar + " e", 2, 7], + [5, 7, kEmbedChar + " e", 4, 7] ]); + testTextAtOffset([ "brInEmbedAndAfter" ], BOUNDARY_LINE_START, + [ [0, 1, "a " + kEmbedChar, 0, 3], + [2, 2, "a " + kEmbedChar + " d\n", 0, 6], + [3, 5, kEmbedChar + " d\n", 2, 6], + [6, 7, "e", 6, 7] ]); + testTextAtOffset([ "brInEmbedAndBlockElementAfter" ], BOUNDARY_LINE_START, + [ [0, 2, "a " + kEmbedChar, 0, 3], + [3, 4, kEmbedChar, 3, 4] ]); + testTextAtOffset([ "brInEmbedThenTextThenBlockElement" ], BOUNDARY_LINE_START, + [ [0, 1, "a " + kEmbedChar, 0, 3], + [2, 2, "a " + kEmbedChar + " d", 0, 5], + [3, 4, kEmbedChar + " d", 2, 5], + [5, 6, kEmbedChar, 5, 6] ]); + testTextAtOffset([ "noBrInEmbedButOneBefore" ], BOUNDARY_LINE_START, + [ [0, 1, "a\n", 0, 2], + [2, 7, "b " + kEmbedChar + " d", 2, 7] ]); + testTextAtOffset([ "noBrInEmbedButOneAfter" ], BOUNDARY_LINE_START, + [ [0, 3, "a " + kEmbedChar + "\n", 0, 4], + [4, 5, "c", 4, 5] ]); + testTextAtOffset([ "twoEmbedsWithBRs" ], BOUNDARY_LINE_START, + [ [0, 1, "a " + kEmbedChar, 0, 3], + [2, 2, "a " + kEmbedChar + kEmbedChar, 0, 4], + [3, 3, kEmbedChar + kEmbedChar + " f", 2, 6], + [4, 6, kEmbedChar + " f", 3, 6] ]); + + // Inline block span with nested spans and BRs + testTextAtOffset([ "inlineBlockWithSpansAndBrs" ], BOUNDARY_LINE_START, + [ [0, 1, "a\n", 0, 2], + [2, 3, "b\n", 2, 4], + [4, 5, "c", 4, 5] ]); + + // Spans with BRs and whitespaces. + testTextAtOffset([ "spansWithWhitespaces" ], BOUNDARY_LINE_START, + [ [0, 6, "Line 1\n", 0, 7], + [7, 13, "Line 2\n", 7, 14], + [14, 20, "Line 3\n", 14, 21], + [21, 27, "Line 4\n", 21, 28], + [28, 28, "", 28, 28] ]); + + // A line with an empty display: contents leaf in the middle. + testTextAtOffset([ "displayContents" ], BOUNDARY_LINE_START, + // See the kOk test below. + // [ [0, 3, `a${kEmbedChar}b`, 0, 3] ]); + [ [0, 0, `a${kEmbedChar}b`, 0, 3], + [2, 3, `a${kEmbedChar}b`, 0, 3] ]); + testTextAtOffset(/* aOffset */ 1, BOUNDARY_LINE_START, + `a${kEmbedChar}b`, /* aStartOffset */ 0, /* aEndOffset */ 3, + "displayContents", + /* returned text */ kOk, + /* returned start offset */ kOk, + /* returned end offset */ kOk); + + // A line which wraps, followed by a br, followed by another line. + testTextAtOffset([ "brAfterWrapped" ], BOUNDARY_LINE_START, + [ [0, 1, "a ", 0, 2], + [2, 3, "b\n", 2, 4], + [4, 5, "c", 4, 5] ]); + + testTextAtOffset([ "inlineInput" ], BOUNDARY_LINE_END, + [ [0, 1, "a", 0, 1], + [2, 7, `\nb ${kEmbedChar} d`, 1, 7, + [ [ 4, "inlineInput", kOk, kOk, kOk] ] ] ]); + + testTextAtOffset([ "inlineInput2" ], BOUNDARY_LINE_END, + [ [0, 1, "a", 0, 1], + [2, 7, `\n${kEmbedChar} c d`, 1, 7, + [ [ 2, "inlineInput2", kOk, kOk, kOk] ] ] ]); + + testTextAtOffset([ "inlineInput3" ], BOUNDARY_LINE_END, + [ [0, 1, "a", 0, 1], + [2, 8, `\nb${kEmbedChar} c d`, 1, 8, + [ [ 3, "inlineInput3", kOk, kOk, kOk] ] ] ]); + + testTextAtOffset([ "inlineInput4" ], BOUNDARY_LINE_END, + [ [0, 1, "a", 0, 1], + [2, 7, `\n${kEmbedChar}b c d`, 1, 8, + [ [ 2, "inlineInput4", kOk, kOk, kOk ] ] ] ]); + + testTextAtOffset(/* aOffset */ 0, BOUNDARY_LINE_START, + kEmbedChar, 0, 1, "contentEditableTable", + /* returned text */ kOk, + /* returned start offset */ kOk, + /* returned end offset */ kOk); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="getTextAtOffset for word boundaries: beginning of a new life" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=853340"> + Bug 853340 + </a> + <a target="_blank" + title="getTextBeforeOffset for word boundaries: evolving" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=855732"> + Bug 855732 + </a> + <a target="_blank" + title=" getTextAfterOffset for line boundary on new rails" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=882292"> + Bug 882292 + </a> + <a target="_blank" + title="getTextAtOffset broken for last object when closing tag is preceded by newline char" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=947170"> + Bug 947170 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="input" value="hello my friend"/> + <div id="div">hello my friend</div> + <div id="editable" contenteditable="true">hello my friend</div> + <textarea id="textarea">hello my friend</textarea> + <iframe id="ta_cntr" + src="data:text/html,<html><body><textarea id='ta'>hello my friend</textarea></body></html>"></iframe> + + <pre> + <div id="ml_div" style="border-style:outset;">oneword + +two words +</div> + <div id="ml_divbr" style="border-style:outset;">oneword<br/><br/>two words<br/></div> + <div id="ml_editable" style="border-style:outset;" contenteditable="true">oneword + +two words +</div> + <div id="ml_editablebr" contenteditable="true" style="border-style:outset;">oneword<br/><br/>two words<br/></div> + <textarea id="ml_textarea" cols="300">oneword + +two words +</textarea> + </pre> + + <iframe id="ht_1" src="data:text/html,<html><body>a <a href=''>b</a> c</body></html>"></iframe> + + <iframe id="ht_2" src="data:text/html,<div contentEditable='true'>foo<br/></div>"></iframe> + <iframe id="ht_3" src="data:text/html,<div contentEditable='true'>foo<br/><br/></div>"></iframe> + + <p id="ht_4">Hello world +</p> + + <ul id="ul1"> + <li id="li1">Item</li> + <li id="li2"></li> + <li id="li3" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li> + <li id="li4">a <a href=''>b</a> c</li> + <li id="li5"><br>hello</li> + </ul> + + <ol id="ol1"> + <li id="li6">Item</li> + <li id="li7"></li> + <li id="li8" style="font-family:monospace; font-size:10pt; width:8ch;">a long and winding road that lead me to your door</li> + <li id="li9">a <a href=''>b</a> c</li> + <li id="li10"><br>hello</li> + </ol> + + <div id="ht_5"> + <div> + <p>sectiounus</p> + <p>seciofarus</p> + </div> + </div> + <div id="line_test_1"> + Line 1 + <center><input type="TEXT"><input value="Button" type="SUBMIT"></center> + Line 3 + </div> + + <div id="block_then_ul"> + <p>Block</p> + <ul><li>Li</li></ul> + </div> + <div id="brInEmbed" contenteditable>a <a href="https://mozilla.org/">b<br>c</a> d</div> + <div id="brInEmbedAndBefore">a<br>b <a href="https://mozilla.org/">c<br>d</a> e</div> + <div id="brInEmbedAndAfter">a <a href="https://mozilla.org/">b<br>c</a> d<br>e</div> + <div id="brInEmbedAndBlockElementAfter">a <a href="https://mozilla.org/">b<br>c</a><p>d</p></div> + <div id="brInEmbedThenTextThenBlockElement">a <a href="https://mozilla.org/">b<br>c</a> d<p>e</p></div> + <div id="noBrInEmbedButOneBefore">a<br>b <a href="https://mozilla.org/">c</a> d</div> + <div id="noBrInEmbedButOneAfter">a <a href="https://mozilla.org/">b</a><br>c</div> + <div id="twoEmbedsWithBRs">a <a href="https://mozilla.org">b<br>c</a><a href="https://mozilla.org">d<br>e</a> f</div> + <span id="inlineBlockWithSpansAndBrs" style="display: inline-block;"><span>a<br>b<br><span></span></span>c</span> + <div id="spansWithWhitespaces"> <!-- Don't indent the following block --> +<span>Line 1<br/> +</span> +<span>Line 2<br/> +</span> +<span>Line 3<br/> +</span> +<span>Line 4<br/> +</span></div><!-- OK to indent again --> + <div id="displayContents">a<ul style="display: contents;"><li style="display: contents;"></li></ul>b</div> + <div id="brAfterWrapped" style="width: 10px;">a b<br>c</div> + <div id="inlineInput">a<br>b <input value="c"> d</div> + <div id="inlineInput2">a<br><input value="b"> c d</div> + <div id="inlineInput3">a<br> b<input value=""> c d</div> + <div id="inlineInput4">a<br><input value="">b c d</div> + <div id="contentEditableTable" contenteditable> + <table style="display: inline-table"> + <thead> + <th>Foo</th> + </thead> + <tbody> + <tr> + <td>Bar</td> + </tr> + </tbody> + </table> + <br> + <table style="display: inline-table"> + <thead> + <th>Foo</th> + </thead> + <tbody> + <tr> + <td>Bar</td> + </tr> + </tbody> + </table> + <br> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_paragraphboundary.html b/accessible/tests/mochitest/text/test_paragraphboundary.html new file mode 100644 index 0000000000..8b270b661c --- /dev/null +++ b/accessible/tests/mochitest/text/test_paragraphboundary.html @@ -0,0 +1,148 @@ +<!DOCTYPE html> +<html> +<head> + <title>Paragraph boundary getText* functions tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + <script type="application/javascript"> + function doTest() { + // First, test the contentEditable. + testTextAtOffset("ce", BOUNDARY_PARAGRAPH, + [[0, 0, kEmbedChar, 0, 1], + [1, 2, kEmbedChar, 1, 2]]); + + // Now, test each paragraph. + var ID = getNode("ce").firstElementChild; + testTextAtOffset(ID, BOUNDARY_PARAGRAPH, + [[0, 15, "hello my friend", 0, 15]]); + ID = getNode("ce").lastElementChild; + testTextAtOffset(ID, BOUNDARY_PARAGRAPH, + [[0, 11, "hello again", 0, 11]]); + + // Test a paragraph whose line forcefully wraps. + testTextAtOffset("forced_wrap", BOUNDARY_PARAGRAPH, + [[0, 2, "ab", 0, 2]]); + + // Test paragraphs with a few line breaks. + testTextAtOffset("forced_br", BOUNDARY_PARAGRAPH, + [[0, 1, "a\n", 0, 2], // a and br treated as a paragraph + [2, 3, "b\n", 2, 4], // b treated as a paragraph, excl 2nd line break + [4, 4, "\n", 4, 5], // second br treated as a separate paragraph + [5, 6, "c", 5, 6]]); // Last paragraph treated as usual + testTextAtOffset("br_at_beginning", BOUNDARY_PARAGRAPH, + [[0, 0, "\n", 0, 1], // br treated as a separate paragraph + [1, 2, "a\n", 1, 3], // a and br treated as a paragraph + [3, 4, "b", 3, 4]]); // b treated as last paragraph + + // Test a paragraph with an embedded link. + testTextAtOffset("pWithLink", BOUNDARY_PARAGRAPH, + [[0, 3, "a" + kEmbedChar + "d", 0, 3]]); + testTextAtOffset("link", BOUNDARY_PARAGRAPH, + [[0, 2, "bc", 0, 2]]); + + // Paragraph with link that contains a line break. + testTextAtOffset("pWithLinkWithBr", BOUNDARY_PARAGRAPH, + [[0, 0, "a" + kEmbedChar, 0, 2], + [1, 1, "a" + kEmbedChar + "d", 0, 3], + [2, 3, kEmbedChar + "d", 1, 3]]); + + // Test a list and list item + testTextAtOffset("ul", BOUNDARY_PARAGRAPH, + [[0, 0, kEmbedChar, 0, 1], + [1, 2, kEmbedChar, 1, 2]]); + testTextAtOffset("li1", BOUNDARY_PARAGRAPH, + [[0, 3, "• a", 0, 3]]); + testTextAtOffset("li2", BOUNDARY_PARAGRAPH, + [[0, 3, "• a", 0, 3]]); + // Test a list item containing multiple text leaf nodes. + testTextAtOffset("liMultiLeaf", BOUNDARY_PARAGRAPH, + [[0, 4, "• ab", 0, 4]]); + + // Test line breaks in a textarea. + testTextAtOffset("textarea", BOUNDARY_PARAGRAPH, + [[0, 1, "a\n", 0, 2], + [2, 3, "b\n", 2, 4], + [4, 4, "\n", 4, 5], + [5, 6, "c", 5, 6]]); + + // Test that a textarea has a blank paragraph at the end if it contains + // a line break as its last character. + testTextAtOffset("textarea_with_trailing_br", BOUNDARY_PARAGRAPH, + [[0, 15, "This is a test.\n", 0, 16], + [16, 16, "", 16, 16]]); + + // Paragraph with a presentational line break. + testTextAtOffset("presentational_br", BOUNDARY_PARAGRAPH, + [[0, 3, "a b", 0, 3]]); + + // Two paragraphs in a div, non-editable case. + testTextAtOffset("two_paragraphs", BOUNDARY_PARAGRAPH, + [[0, 0, kEmbedChar, 0, 1], + [1, 2, kEmbedChar, 1, 2]]); + + // Div containing a paragraph containing a link + testTextAtOffset("divWithParaWithLink", BOUNDARY_PARAGRAPH, + [[0, 0, kEmbedChar, 0, 1], + [1, 2, "b", 1, 2]]); + + // Two text nodes and a br + testTextAtOffset("twoTextNodesAndBr", BOUNDARY_PARAGRAPH, + [[0, 2, "ab\n", 0, 3], + [3, 3, "", 3, 3]]); + + // Link followed by a paragraph. + testTextAtOffset("linkThenPara", BOUNDARY_PARAGRAPH, + [[0, 0, kEmbedChar, 0, 1], + [1, 2, kEmbedChar, 1, 2]]); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="getTextAtOffset for paragraph boundaries" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1520779"> + Bug 1520779 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="ce" contenteditable="true"> + <p>hello my friend</p> + <p>hello again</p> + </div> + <p id="forced_wrap" style="width: 1px; word-break: break-all;">ab</p> + <p id="forced_br">a<br>b<br><br>c</p> + <p id="br_at_beginning"><br>a<br>b</p> + <p id="pWithLink">a<a id="link" href="https://example.com/">bc</a>d</p> + <p id="pWithLinkWithBr">a<a href="#">b<br>c</a>d</p> + <ul id="ul"><li id="li1">a</li><li>b</li></ul> + <style>#li2::marker { content:'\2022\0020'; }</style> + <ul id="ul"><li id="li2">a</li><li>b</li></ul> + <ul><li id="liMultiLeaf">a<span>b</span></li></ul> + <textarea id="textarea">a +b + +c</textarea> <!-- This must be outdented for a correct test case --> + <textarea id="textarea_with_trailing_br">This is a test. +</textarea> <!-- This must be outdented for a correct test case --> + <p id="presentational_br" style="white-space: pre-wrap;">a<span> <br role="presentation"></span>b</p> + <div id="two_paragraphs"><p>a</p><p>b</p></div> + <div id ="divWithParaWithLink"><p><a href="#">a</a></p>b</div> + <p id="twoTextNodesAndBr">a<span>b</span><br></p> + <div id="linkThenPara"><a href="#">a</a><p>b</p></div> +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_passwords.html b/accessible/tests/mochitest/text/test_passwords.html new file mode 100644 index 0000000000..fc184f2d45 --- /dev/null +++ b/accessible/tests/mochitest/text/test_passwords.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<html> +<head> + <title>nsIAccessibleText getText related function tests for text and password inputs</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // regular text and password inputs + // //////////////////////////////////////////////////////////////////////// + + // ////////////////////////////////////////////////////////////////////// + // characterCount and getText for regular text field + + var IDs = [ "username" ]; + testCharacterCount(IDs, 4); + testText(IDs, 0, 4, "test"); + + // ////////////////////////////////////////////////////////////////////// + // characterCount and getText for password field + + IDs = [ "password" ]; + testCharacterCount(IDs, 4); + let password = document.getElementById("password"); + let editor = SpecialPowers.wrap(password).editor; + let passwordMask = editor.passwordMask; + testText(IDs, 0, 4, `${passwordMask}${passwordMask}${passwordMask}${passwordMask}`); + // a11y data is updated at next tick so that we need to refresh here. + editor.unmask(0, 2); + SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0); + testText(IDs, 0, 4, `te${passwordMask}${passwordMask}`); + editor.unmask(2, 4); + SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0); + testText(IDs, 0, 4, `${passwordMask}${passwordMask}st`); + editor.unmask(0, 4); + SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0); + testText(IDs, 0, 4, `test`); + SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="mochitest for getText for password fields" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=415943">Mozilla Bug 415943</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <form action="post.php" method="post"> + <label for="username">User name:</label> + <input id="username" value="test"><br /> + <label for="password">Password:</label> + <input type="password" id="password" value="test"/> + </form> +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_selection.html b/accessible/tests/mochitest/text/test_selection.html new file mode 100644 index 0000000000..ce83e57086 --- /dev/null +++ b/accessible/tests/mochitest/text/test_selection.html @@ -0,0 +1,119 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test text selection functions</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + + <script type="application/javascript"> + + function doTest() { + // Test selection count: clean selection / check count. + testTextAddSelection("div0", 0, 2, 1); // |Test selection... + cleanTextSelections("div0"); + testTextSelectionCount("div0", 0); + + // Test addition: adding two equal selections, the second one should + // not be added. + testTextAddSelection("div1", 7, 9, 1); // Test ad|di|ng two... + testTextAddSelection("div1", 7, 9, 1); // Test ad|di|ng two... + testTextGetSelection("div1", 7, 9, 0); + + // Test overlapping selections: adding three selections, one adjacent. + testTextAddSelection("div2", 0, 3, 1); // |Tes|t adding 3... + testTextAddSelection("div2", 7, 9, 2); // |Tes|t ad|di|ng 3... + testTextAddSelection("div2", 3, 4, 3); // |Tes||t| ad|di|ng 3... + testTextGetSelection("div2", 0, 3, 0); + testTextGetSelection("div2", 3, 4, 1); + testTextGetSelection("div2", 7, 9, 2); + + // Test selection re-ordering: adding two selections. + // NOTE: removeSelections aSelectionIndex is from start of document. + testTextAddSelection("div3", 0, 3, 1); // |Tes|t adding 2... + testTextAddSelection("div3", 7, 9, 2); // |Tes|t ad|di|ng 2... + testTextRemoveSelection("div3", 4, 1); // Test ad|di|ng 2... + + // Test extending existing selection. + // NOTE: setSelectionBounds aSelectionIndex is from start of document. + testTextAddSelection("div4", 4, 5, 1); // Test| |extending... + testTextSetSelection("div4", 4, 9, 6, 1); // Test| exte|nding... + + // Test moving an existing selection. + // NOTE: setSelectionBounds aSelectionIndex is from start of document. + testTextAddSelection("div5", 1, 3, 1); // T|es|t moving... + testTextSetSelection("div5", 5, 9, 6, 1); // Test |movi|ng... + + // Test adding selections to multiple inner elements. + testTextAddSelection("div71", 0, 3, 1); // |Tes|t adding... + testTextAddSelection("div71", 7, 8, 2); // |Tes|t ad|d|ing... + testTextAddSelection("div72", 4, 6, 1); // Test| a|dding... + testTextAddSelection("div72", 7, 8, 2); // Test| a|d|d|ing... + + // Test adding selection to parent element. + // NOTE: If inner elements are represented as embedded chars + // we count their internal selections. + testTextAddSelection("div7", 7, 8, 5); // Test ad|d|ing... + + // Test attempt to selected generated content. + // range's start is clipped to end of generated content. + testTextAddSelection("div8", 1, 8, 1); + testTextGetSelection("div8", 6, 8, 0); + // range's end is expanded to end of container hypertext. + testTextAddSelection("div8", 10, 15, 2); + testTextGetSelection("div8", 10, 23, 1); + + testTextAddSelection("li", 0, 8, 1); + testTextGetSelection("li", 3, 8, 0); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + +</script> +</head> + +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="div0">Test selection count</div> + </br> + <div id="div1">Test adding two equal selections </div> + <div id="div2">Test adding 3 selections one adjacent </div> + <div id="div3">Test adding 2 selections, remove first one </div> + <div id="div4">Test extending a selection </div> + <div id="div5">Test moving a selection </div> + </br> + <div id="div7">Test adding selections to parent element + <div id="div71">Test adding selections to inner element1 </div> + <div id="div72">Test adding selections to inner element2 </div> + </div> + <style> + #div8:before { + content: 'hello '; + } + #div8:after { + content: ', i love you'; + } + </style> + <div id="div8">world</div> + <ol> + <li id="li">Number one</li> + </ol> + +</body> + +</html> diff --git a/accessible/tests/mochitest/text/test_settext_input_event.html b/accessible/tests/mochitest/text/test_settext_input_event.html new file mode 100644 index 0000000000..2f0ecacf30 --- /dev/null +++ b/accessible/tests/mochitest/text/test_settext_input_event.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test that setTextContents only sends one DOM input event</title> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript"> + async function doTest() { + let input = getAccessible("input", [nsIAccessibleEditableText]); + let eventPromise = new Promise(resolve => + document.getElementById("input").addEventListener( + "input", resolve, { once: true })); + + input.setTextContents("goodbye"); + let inputEvent = await eventPromise; + is(inputEvent.target.value, "goodbye", "input set to new value."); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="HyperTextAccessible::ReplaceText causes two distinct DOM 'input' events" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1490840">Mozilla Bug 1490840</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <input id="input" value="hello"> +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_textBounds.html b/accessible/tests/mochitest/text/test_textBounds.html new file mode 100644 index 0000000000..66e7f1a93f --- /dev/null +++ b/accessible/tests/mochitest/text/test_textBounds.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<head> + <title>TextBounds tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + + <script type="application/javascript"> + function doTest() { + // Returned rect should be all 0 if no frame; e.g. display: contents. + testTextBounds( + "displayContents", 0, 0, [0, 0, 0, 0], COORDTYPE_SCREEN_RELATIVE + ); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <section id="displayContents" style="display: contents;"></section> +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_wordboundary.html b/accessible/tests/mochitest/text/test_wordboundary.html new file mode 100644 index 0000000000..49d4d95561 --- /dev/null +++ b/accessible/tests/mochitest/text/test_wordboundary.html @@ -0,0 +1,361 @@ +<!DOCTYPE html> +<html> +<head> + <title>Word boundary text tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + + <script type="application/javascript"> + function doTest() { + // "hello" + // __h__e__l__l__o__ + // 0 1 2 3 4 5 + var ids = [ "i1", "d1", "e1", "t1" ]; + testTextBeforeOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "", 0, 0 ] ]); + testTextBeforeOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, "", 0, 0 ] ]); + + testTextAtOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "hello", 0, 5 ] ]); + testTextAtOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, "hello", 0, 5 ] ]); + + testTextAfterOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "", 5, 5 ] ]); + testTextAfterOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, "", 5, 5 ] ]); + + // "hello " + // __h__e__l__l__o__ __ + // 0 1 2 3 4 5 6 + ids = [ "i2", "d2", "p2", "e2", "t2" ]; + testTextBeforeOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 6, "", 0, 0 ] ]); + testTextBeforeOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, "", 0, 0 ], + [ 6, 6, "hello", 0, 5 ], + ]); + + testTextAtOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 6, "hello ", 0, 6 ] ]); + testTextAtOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 4, "hello", 0, 5 ], + [ 5, 6, " ", 5, 6 ], + ]); + + testTextAfterOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 6, "", 6, 6 ] ]); + testTextAfterOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, " ", 5, 6 ], + [ 6, 6, "", 6, 6 ], + ]); + + // "hello all" + // __h__e__l__l__o__ __a__l__l__ + // 0 1 2 3 4 5 6 7 8 9 + ids = [ "i6", "d6", "e6", "t6" ]; + testTextBeforeOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "", 0, 0 ], + [ 6, 9, "hello ", 0, 6 ]]); + testTextBeforeOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, "", 0, 0 ], + [ 6, 9, "hello", 0, 5 ] ]); + + testTextAtOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "hello ", 0, 6 ], + [ 6, 9, "all", 6, 9 ] ]); + testTextAtOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 4, "hello", 0, 5 ], + [ 5, 9, " all", 5, 9 ] ]); + + testTextAfterOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "all", 6, 9 ], + [ 6, 9, "", 9, 9 ] ]); + testTextAfterOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, " all", 5, 9 ], + [ 6, 9, "", 9, 9 ] ]); + + // " hello all " (with whitespace collapsing) + // __h__e__l__l__o__ __a__l__l__ __ + // 0 1 2 3 4 5 6 7 8 9 10 + ids = [ "d6a", "e6a" ]; + testTextBeforeOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "", 0, 0 ], + [ 6, 10, "hello ", 0, 6 ] ]); + testTextBeforeOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, "", 0, 0 ], + [ 6, 9, "hello", 0, 5 ], + [ 10, 10, " all", 5, 9 ] ]); + + testTextAtOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "hello ", 0, 6 ], + [ 6, 10, "all ", 6, 10 ] ]); + testTextAtOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 4, "hello", 0, 5 ], + [ 5, 8, " all", 5, 9 ], + [ 9, 10, " ", 9, 10 ] ]); + + testTextAfterOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "all ", 6, 10 ], + [ 6, 10, "", 10, 10 ] ]); + testTextAfterOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, " all", 5, 9 ], + [ 6, 9, " ", 9, 10 ], + [ 10, 10, "", 10, 10 ] ]); + + // "hello my friend" + // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__ + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + ids = [ "i7", "d7", "e7", "t7", "w7" ]; + testTextBeforeOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "", 0, 0 ], + [ 6, 8, "hello ", 0, 6 ], + [ 9, 15, "my ", 6, 9 ] ]); + testTextBeforeOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, "", 0, 0 ], + [ 6, 8, "hello", 0, 5 ], + [ 9, 15, " my", 5, 8 ] ]); + + testTextAtOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "hello ", 0, 6 ], + [ 6, 8, "my ", 6, 9 ], + [ 9, 15, "friend", 9, 15] ]); + testTextAtOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 4, "hello", 0, 5 ], + [ 5, 7, " my", 5, 8 ], + [ 8, 15, " friend", 8, 15] ]); + + testTextAfterOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "my ", 6, 9 ], + [ 6, 8, "friend", 9, 15 ], + [ 9, 15, "", 15, 15 ] ]); + testTextAfterOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, " my", 5, 8 ], + [ 6, 8, " friend", 8, 15 ], + [ 9, 15, "", 15, 15 ] ]); + + // "Brave Sir Robin ran" + // __B__r__a__v__e__ __S__i__r__ __ __R__o__b__i__n__ __ __ __r__a__n__ + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + ids = [ "i8", "d8", "e8", "t8" ]; + testTextBeforeOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "", 0, 0 ], + [ 6, 10, "Brave ", 0, 6 ], + [ 11, 18, "Sir ", 6, 11 ], + [ 19, 22, "Robin ", 11, 19 ] ]); + testTextBeforeOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, "", 0, 0 ], + [ 6, 9, "Brave", 0, 5 ], + [ 10, 16, " Sir", 5, 9 ], + [ 17, 22, " Robin", 9, 16 ] ]); + + testTextAtOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "Brave ", 0, 6 ], + [ 6, 10, "Sir ", 6, 11 ], + [ 11, 18, "Robin ", 11, 19 ], + [ 19, 22, "ran", 19, 22 ] ]); + testTextAtOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 4, "Brave", 0, 5 ], + [ 5, 8, " Sir", 5, 9 ], + [ 9, 15, " Robin", 9, 16 ], + [ 16, 22, " ran", 16, 22 ] ]); + + testTextAfterOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 5, "Sir ", 6, 11 ], + [ 6, 10, "Robin ", 11, 19 ], + [ 11, 18, "ran", 19, 22 ], + [ 19, 22, "", 22, 22 ] ]); + testTextAfterOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 5, " Sir", 5, 9 ], + [ 6, 9, " Robin", 9, 16 ], + [ 10, 16, " ran", 16, 22 ], + [ 17, 22, "", 22, 22 ] ]); + + // 'oneword + // ' + // 'two words + // ' + // __o__n__e__w__o__r__d__\n + // 0 1 2 3 4 5 6 7 + // __\n + // 8 + // __t__w__o__ __w__o__r__d__s__\n__ + // 9 10 11 12 13 14 15 16 17 18 19 + + ids = ["ml_div1", "ml_divbr1", "ml_ediv1", "ml_edivbr1", "ml_t1"]; + testTextBeforeOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 8, "", 0, 0 ], + [ 9, 12, "oneword\n\n", 0, 9 ], + [ 13, 19, "two ", 9, 13 ] ]); + testTextBeforeOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 7, "", 0, 0 ], + [ 8, 12, "oneword", 0, 7, + [ [ 8, "ml_divbr1", kOk, kOk, kOk ], + [ 8, "ml_edivbr1", kOk, kOk, kOk ], + [ 9, "ml_divbr1", kOk, kOk, kOk ], + [ 9, "ml_edivbr1", kOk, kOk, kOk ] ] ], + [ 13, 18, "\n\ntwo", 7, 12 ], + [ 19, 19, " words", 12, 18, + [ [ 19, "ml_divbr1", kOk, kOk, kOk ], + [ 19, "ml_edivbr1", kOk, kOk, kOk ] ] ], + ] ); + + testTextAtOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 8, "oneword\n\n", 0, 9 ], + [ 9, 12, "two ", 9, 13 ], + [ 13, 19, "words\n", 13, 19 ] ]); + testTextAtOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 6, "oneword", 0, 7 ], + [ 7, 11, "\n\ntwo", 7, 12 ], + [ 12, 17, " words", 12, 18 ], + [ 18, 19, "\n", 18, 19, + [ [ 18, "ml_divbr1", kOk, kOk, kOk ], + [ 18, "ml_edivbr1", kOk, kOk, kOk ], + [ 19, "ml_divbr1", kOk, kOk, kOk ], + [ 19, "ml_edivbr1", kOk, kOk, kOk ] ] ] ]); + + testTextAfterOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 8, "two ", 9, 13 ], + [ 9, 12, "words\n", 13, 19 ], + [ 13, 19, "", 19, 19 ] ]); + testTextAfterOffset(ids, BOUNDARY_WORD_END, + [ [ 0, 7, "\n\ntwo", 7, 12 ], + [ 8, 12, " words", 12, 18 ], + [ 13, 18, "\n", 18, 19, + [ [ 18, "ml_divbr1", kOk, kOk, kOk ], + [ 18, "ml_edivbr1", kOk, kOk, kOk ] ] ], + [ 19, 19, "", 19, 19 ] ]); + + // a <a href="#">b</a> + // a * + testTextBeforeOffset("cntr_1", BOUNDARY_WORD_START, + [ [ 0, 1, "", 0, 0 ], + [ 2, 3, "a ", 0, 2 ] ]); + + testTextAtOffset("cntr_1", BOUNDARY_WORD_START, + [ [ 0, 1, "a ", 0, 2 ], + [ 2, 3, kEmbedChar, 2, 3 ] ]); + testTextAfterOffset("cntr_1", BOUNDARY_WORD_START, + [ [ 0, 1, kEmbedChar, 2, 3 ], + [ 2, 3, "", 3, 3 ] ]); + + // Punctuation tests. + testTextAtOffset("punc_alone", BOUNDARY_WORD_START, [ + [ 0, 1, "a ", 0, 2 ], + [ 2, 4, "@@ ", 2, 5 ], + [ 5, 6, "b", 5, 6 ] + ]); + testTextAtOffset("punc_begin", BOUNDARY_WORD_START, [ + [ 0, 1, "a ", 0, 2 ], + [ 2, 5, "@@b ", 2, 6 ], + [ 6, 7, "c", 6, 7 ] + ]); + testTextAtOffset("punc_end", BOUNDARY_WORD_START, [ + [ 0, 1, "a ", 0, 2 ], + [ 2, 5, "b@@ ", 2, 6 ], + [ 6, 7, "c", 6, 7 ] + ]); + testTextAtOffset("punc_middle", BOUNDARY_WORD_START, [ + [ 0, 1, "a ", 0, 2 ], + [ 2, 4, "b@@", 2, 5 ], + [ 5, 6, "c ", 5, 7 ], + [ 7, 8, "d", 7, 8 ] + ]); + testTextAtOffset("punc_everywhere", BOUNDARY_WORD_START, [ + [ 0, 1, "a ", 0, 2 ], + [ 2, 6, "@@b@@", 2, 7 ], + [ 7, 10, "c@@ ", 7, 11 ], + [ 11, 12, "d", 11, 12 ] + ]); + + // Multi-word embedded object test. + testTextAtOffset("multiword_embed", BOUNDARY_WORD_START, [ + [ 0, 1, "a ", 0, 2 ], + [ 2, 3, `${kEmbedChar} `, 2, 4, [ + // Word at offset 2 returns end offset 3, should be 4. + [ 2, "multiword_embed", kOk, kOk, kOk ] + ] ], + [ 4, 5, "b", 4, 5 ] + ]); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="i1" value="hello"/> + <div id="d1">hello</div> + <div id="e1" contenteditable="true">hello</div> + <textarea id="t1">hello</textarea> + + <input id="i2" value="hello "/> + <div id="d2"> hello </div> + <pre><div id="p2">hello </div></pre> + <div id="e2" contenteditable="true" style='white-space:pre'>hello </div> + <textarea id="t2">hello </textarea> + + <input id="i6" value="hello all"/> + <div id="d6"> hello all</div> + <div id="e6" contenteditable="true">hello all</div> + <textarea id="t6">hello all</textarea> + + <div id="d6a"> hello all </div> + <div id="e6a" contenteditable="true"> hello all </div> + + <input id="i7" value="hello my friend"/> + <div id="d7"> hello my friend</div> + <div id="e7" contenteditable="true">hello my friend</div> + <textarea id="t7">hello my friend</textarea> + <div id="w7" style="width:1em"> hello my friend</div> + + <input id="i8" value="Brave Sir Robin ran"/> + <pre> + <div id="d8">Brave Sir Robin ran</div> + <div id="e8" contenteditable="true">Brave Sir Robin ran</div> + </pre> + <textarea id="t8" cols="300">Brave Sir Robin ran</textarea> + + <pre> +<div id="ml_div1">oneword + +two words +</div> +<div id="ml_divbr1">oneword<br/><br/>two words<br/></div> +<div id="ml_ediv1" contenteditable="true">oneword + +two words +</div> +<div id="ml_edivbr1" contenteditable="true">oneword<br/><br/>two words<br/></div> +<textarea id="ml_t1" cols="300">oneword + +two words +</textarea> + </pre> + + <div id="cntr_1">a <a href="#">b</a></div> + + <p id="punc_alone">a @@ b</p> + <p id="punc_begin">a @@b c</p> + <p id="punc_end">a b@@ c</p> + <p id="punc_middle">a b@@c d</p> + <p id="punc_everywhere">a @@b@@c@@ d</p> + + <p id="multiword_embed">a <a href="#">x y</a> b</p> +</body> +</html> diff --git a/accessible/tests/mochitest/text/test_words.html b/accessible/tests/mochitest/text/test_words.html new file mode 100644 index 0000000000..7e4fba9154 --- /dev/null +++ b/accessible/tests/mochitest/text/test_words.html @@ -0,0 +1,131 @@ +<!DOCTYPE html> +<html> +<head> + <title>nsIAccessibleText getText related function tests for html:input,html:div and html:textarea</title> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + <script type="application/javascript"> + if (navigator.platform.startsWith("Mac")) { + SimpleTest.expectAssertions(0, 1); + } else { + SimpleTest.expectAssertions(0, 1); + } + + function doTest() { + // "one two" + testWords("div1", ["one", "two"]); + + // "one two" + testWords("div2", ["one", "two"]); + + // "one,two" + testWordCount("div3", 2, kOk); + testWordAt("div3", 0, "one", kTodo); + testWordAt("div3", 1, "two", kOk); + + // "one, two" + testWordCount("div4", 2, kOk); + testWordAt("div4", 0, "one", kTodo); + testWordAt("div4", 1, "two", kOk); + + // "one+two" + testWordCount("div5", 2, kOk); + testWordAt("div5", 0, "one", kTodo); + testWordAt("div5", 1, "two", kOk); + + // "one+two " + testWordCount("div6", 2, kOk); + testWordAt("div6", 0, "one", kTodo); + testWordAt("div6", 1, "two", kOk); + + // "one\ntwo" + testWordCount("div7", 2, kOk); + testWordAt("div7", 0, "one", kOk); + testWordAt("div7", 1, "two", kTodo); + + // "one.two" + testWordCount("div8", 2, kOk); + testWordAt("div8", 0, "one", kTodo); + testWordAt("div8", 1, "two", kOk); + + // "345" + testWords("div9", ["345"]); + + // "3a A4" + testWords("div10", ["3a", "A4"]); + + // "3.1416" + testWords("div11", ["3.1416"], kTodo); + + // "4,261.01" + testWords("div12", ["4,261.01"], kTodo); + + // "カタカナ" + testWords("div13", ["カタカナ"], kOk); + + // "Peter's car" + testWords("div14", ["Peter's", "car"], kTodo); + + // "N.A.T.O." + testWords("div15", ["N.A.T.O."], kTodo); + + // "3+4*5=23" + testWordCount("div16", 4, kOk); + testWordAt("div15", 0, "3", kTodo); + testWordAt("div15", 1, "4", kTodo); + testWordAt("div15", 2, "5", kTodo); + testWordAt("div15", 3, "23", kTodo); + + // "Hello. Friend, are you here?!" + testWordCount("div17", 5, kOk); + testWordAt("div17", 0, "Hello", kTodo); + testWordAt("div17", 1, "Friend", kTodo); + testWordAt("div17", 2, "are", kOk); + testWordAt("div17", 3, "you", kOk); + testWordAt("div17", 4, "here", kTodo); + + testWords("input_1", ["foo", "bar"]); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="nsIAccessibleText test word boundaries" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=452769">Mozilla Bug 452769</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + <div id="div1">one two</div> + <div id="div2">one two</div> + <div id="div3">one,two</div> + <div id="div4">one, two</div> + <div id="div5">one+two</div> + <div id="div6">one+two </div> + <div id="div7">one<br/>two</div> + <div id="div8">one.two</div> + <div id="div9">345</div> + <div id="div10">3a A4</div> + <div id="div11">3.1416</div> + <div id="div12">4,261.01</div> + <div id="div13">カタカナ</div> + <div id="div14">Peter's car</div> + <div id="div15">N.A.T.O.</div> + <div id="div16">3+4*5=23</div> + <div id="div17">Hello. Friend, are you here?!</div> + </pre> + <input id="input_1" type="text" value="foo bar" placeholder="something or other"> + +</body> +</html> diff --git a/accessible/tests/mochitest/textattrs/a11y.ini b/accessible/tests/mochitest/textattrs/a11y.ini new file mode 100644 index 0000000000..6250e663b4 --- /dev/null +++ b/accessible/tests/mochitest/textattrs/a11y.ini @@ -0,0 +1,11 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/moz.png + +[test_general.html] +[test_general.xhtml] +[test_invalid.html] +[test_mathml.html] +[test_spelling.html] +[test_svg.html] diff --git a/accessible/tests/mochitest/textattrs/test_general.html b/accessible/tests/mochitest/textattrs/test_general.html new file mode 100644 index 0000000000..02adcf675a --- /dev/null +++ b/accessible/tests/mochitest/textattrs/test_general.html @@ -0,0 +1,823 @@ +<html> + +<head> + <title>Text attributes tests</title> + <meta charset="utf-8"> + <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style> + .gencontent:before { content: "*"; } + .gencontent:after { content: "*"; } + </style> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script src="../common.js"></script> + <script src="../attributes.js"></script> + <script src="../events.js"></script> + + <script> + var gComputedStyle = null; + + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // area1 + var ID = "area1"; + var defAttrs = buildDefaultTextAttrs(ID, "10pt"); + testDefaultTextAttrs(ID, defAttrs); + + var attrs = {}; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 7); + + attrs = { "font-weight": kBoldFontWeight }; + testTextAttrs(ID, 7, attrs, defAttrs, 7, 11); + + attrs = {}; + testTextAttrs(ID, 12, attrs, defAttrs, 11, 18); + + // //////////////////////////////////////////////////////////////////////// + // area2 + ID = "area2"; + defAttrs = buildDefaultTextAttrs(ID, "14pt"); + testDefaultTextAttrs(ID, defAttrs); + + attrs = {}; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 7); + + attrs = { "font-weight": kBoldFontWeight }; + testTextAttrs(ID, 7, attrs, defAttrs, 7, 12); + + var tempElem = getNode(ID).firstChild.nextSibling.firstChild.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"font-style": gComputedStyle.fontStyle, + "font-weight": kBoldFontWeight }; + testTextAttrs(ID, 13, attrs, defAttrs, 12, 19); + + attrs = { "font-weight": kBoldFontWeight }; + testTextAttrs(ID, 20, attrs, defAttrs, 19, 23); + + attrs = {}; + testTextAttrs(ID, 24, attrs, defAttrs, 23, 30); + + // //////////////////////////////////////////////////////////////////////// + // area3 + ID = "area3"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + testDefaultTextAttrs(ID, defAttrs); + + tempElem = getNode(ID).firstChild.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"color": gComputedStyle.color}; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 6); + + tempElem = tempElem.firstChild.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"color": gComputedStyle.color}; + testTextAttrs(ID, 6, attrs, defAttrs, 6, 26); + + tempElem = tempElem.parentNode; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"color": gComputedStyle.color}; + testTextAttrs(ID, 26, attrs, defAttrs, 26, 27); + + tempElem = tempElem.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"color": gComputedStyle.color, + "background-color": gComputedStyle.backgroundColor}; + testTextAttrs(ID, 27, attrs, defAttrs, 27, 50); + + // //////////////////////////////////////////////////////////////////////// + // area4 + ID = "area4"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + testDefaultTextAttrs(ID, defAttrs); + + tempElem = getNode(ID).firstChild.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"color": gComputedStyle.color}; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 16); + + tempElem = tempElem.nextSibling.firstChild.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"color": gComputedStyle.color}; + testTextAttrs(ID, 16, attrs, defAttrs, 16, 33); + + tempElem = tempElem.parentNode; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"color": gComputedStyle.color}; + testTextAttrs(ID, 34, attrs, defAttrs, 33, 46); + + // //////////////////////////////////////////////////////////////////////// + // area5: "Green!*!RedNormal" + ID = "area5"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + testDefaultTextAttrs(ID, defAttrs); + + // Green + tempElem = getNode(ID).firstChild.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"color": gComputedStyle.color}; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 5); + + // br + attrs = {}; + testTextAttrs(ID, 5, attrs, defAttrs, 5, 6); + + // img, embedded accessible, no attributes + attrs = {}; + testTextAttrs(ID, 6, attrs, {}, 6, 7); + + // br + attrs = {}; + testTextAttrs(ID, 7, attrs, defAttrs, 7, 8); + + // Red + tempElem = tempElem.nextSibling.nextSibling.nextSibling.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"color": gComputedStyle.color}; + testTextAttrs(ID, 9, attrs, defAttrs, 8, 11); + + // Normal + attrs = {}; + testTextAttrs(ID, 11, attrs, defAttrs, 11, 18); + + // //////////////////////////////////////////////////////////////////////// + // area6 (CSS vertical-align property, refer to bug 445938 for details + // and sup and sub elements, refer to bug 735645 for details) + ID = "area6"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + testDefaultTextAttrs(ID, defAttrs); + + attrs = {}; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 5); + + // Embedded object (sup) has no attributes but takes up one character. + testTextAttrs(ID, 5, {}, {}, 5, 6); + + attrs = {}; + testTextAttrs(ID, 6, attrs, defAttrs, 6, 20); + + attrs = { "text-position": "super" }; + testTextAttrs(ID, 20, attrs, defAttrs, 20, 28); + + attrs = {}; + testTextAttrs(ID, 28, attrs, defAttrs, 28, 32); + + // Embedded object (sub) has no attributes but takes up one character. + testTextAttrs(ID, 32, {}, {}, 32, 33); + + attrs = {}; + testTextAttrs(ID, 33, attrs, defAttrs, 33, 38); + + attrs = { "text-position": "sub" }; + testTextAttrs(ID, 38, attrs, defAttrs, 38, 47); + + attrs = {}; + testTextAttrs(ID, 47, attrs, defAttrs, 47, 52); + + attrs = { "text-position": "super" }; + testTextAttrs(ID, 52, attrs, defAttrs, 52, 67); + + attrs = {}; + testTextAttrs(ID, 67, attrs, defAttrs, 67, 72); + + attrs = { "text-position": "sub" }; + testTextAttrs(ID, 72, attrs, defAttrs, 72, 85); + + attrs = {}; + testTextAttrs(ID, 85, attrs, defAttrs, 85, 90); + + attrs = { "text-position": "super" }; + testTextAttrs(ID, 90, attrs, defAttrs, 90, 106); + + attrs = {}; + testTextAttrs(ID, 106, attrs, defAttrs, 106, 111); + + attrs = { "text-position": "sub" }; + testTextAttrs(ID, 111, attrs, defAttrs, 111, 125); + + // //////////////////////////////////////////////////////////////////////// + // area7 + ID = "area7"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + defAttrs.language = "en"; + testDefaultTextAttrs(ID, defAttrs); + + attrs = {"language": "ru"}; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 6); + + attrs = {}; + testTextAttrs(ID, 6, attrs, defAttrs, 6, 7); + + tempElem = getNode(ID).firstChild.nextSibling.nextSibling.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = { "background-color": gComputedStyle.backgroundColor}; + testTextAttrs(ID, 13, attrs, defAttrs, 7, 20); + + attrs = {}; + testTextAttrs(ID, 20, attrs, defAttrs, 20, 21); + + attrs = {"language": "de"}; + testTextAttrs(ID, 21, attrs, defAttrs, 21, 36); + + attrs = {}; + testTextAttrs(ID, 36, attrs, defAttrs, 36, 44); + + attrs = {}; + testTextAttrs(ID, 37, attrs, defAttrs, 36, 44); + + tempElem = tempElem.nextSibling.nextSibling.nextSibling.nextSibling.firstChild.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"color": gComputedStyle.color}; + testTextAttrs(ID, 44, attrs, defAttrs, 44, 51); + + tempElem = tempElem.firstChild.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"font-weight": kBoldFontWeight, + "color": gComputedStyle.color}; + testTextAttrs(ID, 51, attrs, defAttrs, 51, 55); + + tempElem = tempElem.parentNode; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"color": gComputedStyle.color}; + testTextAttrs(ID, 55, attrs, defAttrs, 55, 62); + + // //////////////////////////////////////////////////////////////////////// + // area9, different single style spans in styled paragraph + ID = "area9"; + defAttrs = buildDefaultTextAttrs(ID, "10pt"); + testDefaultTextAttrs(ID, defAttrs); + + attrs = {}; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 6); + + attrs = { "font-size": "12pt" }; + testTextAttrs(ID, 7, attrs, defAttrs, 6, 12); + + attrs = {}; + testTextAttrs(ID, 13, attrs, defAttrs, 12, 21); + + // Walk to the span with the different background color + tempElem = getNode(ID).firstChild.nextSibling.nextSibling.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = { "background-color": gComputedStyle.backgroundColor }; + testTextAttrs(ID, 22, attrs, defAttrs, 21, 36); + + attrs = {}; + testTextAttrs(ID, 37, attrs, defAttrs, 36, 44); + + // Walk from the background color span to the one with font-style + tempElem = tempElem.nextSibling.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = { "font-style": gComputedStyle.fontStyle }; + testTextAttrs(ID, 45, attrs, defAttrs, 44, 61); + + attrs = {}; + testTextAttrs(ID, 62, attrs, defAttrs, 61, 69); + + // Walk from span with font-style to the one with font-family. + tempElem = tempElem.nextSibling.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = { "font-family": kMonospaceFontFamily }; + testTextAttrs(ID, 70, attrs, defAttrs, 69, 83); + + attrs = {}; + testTextAttrs(ID, 84, attrs, defAttrs, 83, 91); + + attrs = { + "text-underline-style": "solid", + "text-underline-color": gComputedStyle.color, + }; + testTextAttrs(ID, 92, attrs, defAttrs, 91, 101); + + attrs = {}; + testTextAttrs(ID, 102, attrs, defAttrs, 101, 109); + + attrs = { + "text-line-through-style": "solid", + "text-line-through-color": gComputedStyle.color, + }; + testTextAttrs(ID, 110, attrs, defAttrs, 109, 122); + + attrs = {}; + testTextAttrs(ID, 123, attrs, defAttrs, 122, 130); + + attrs = { + "text-line-through-style": "solid", + "text-line-through-color": gComputedStyle.color, + }; + testTextAttrs(ID, 131, attrs, defAttrs, 130, 143); + + attrs = {}; + testTextAttrs(ID, 144, attrs, defAttrs, 143, 151); + + attrs = { + "text-line-through-style": "solid", + "text-line-through-color": gComputedStyle.color, + }; + testTextAttrs(ID, 152, attrs, defAttrs, 151, 164); + + attrs = {}; + testTextAttrs(ID, 165, attrs, defAttrs, 164, 172); + + // //////////////////////////////////////////////////////////////////////// + // area10, different single style spans in non-styled paragraph + ID = "area10"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + testDefaultTextAttrs(ID, defAttrs); + + attrs = {}; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 7); + + attrs = { "font-size": "14pt" }; + testTextAttrs(ID, 7, attrs, defAttrs, 7, 13); + + attrs = {}; + testTextAttrs(ID, 13, attrs, defAttrs, 13, 22); + + // Walk to the span with the different background color + tempElem = getNode(ID).firstChild.nextSibling.nextSibling.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = { "background-color": gComputedStyle.backgroundColor }; + testTextAttrs(ID, 23, attrs, defAttrs, 22, 37); + + attrs = {}; + testTextAttrs(ID, 38, attrs, defAttrs, 37, 45); + + // Walk from the background color span to the one with font-style + tempElem = tempElem.nextSibling.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = {"font-style": gComputedStyle.fontStyle}; + testTextAttrs(ID, 46, attrs, defAttrs, 45, 62); + + attrs = {}; + testTextAttrs(ID, 63, attrs, defAttrs, 62, 70); + + // Walk from span with font-style to the one with font-family. + tempElem = tempElem.nextSibling.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem); + attrs = { "font-family": kMonospaceFontFamily }; + testTextAttrs(ID, 71, attrs, defAttrs, 70, 84); + + attrs = {}; + testTextAttrs(ID, 85, attrs, defAttrs, 84, 92); + + attrs = { + "text-underline-style": "solid", + "text-underline-color": gComputedStyle.color, + }; + testTextAttrs(ID, 93, attrs, defAttrs, 92, 102); + + attrs = {}; + testTextAttrs(ID, 103, attrs, defAttrs, 102, 110); + + attrs = { + "text-line-through-style": "solid", + "text-line-through-color": gComputedStyle.color, + }; + testTextAttrs(ID, 111, attrs, defAttrs, 110, 123); + + attrs = {}; + testTextAttrs(ID, 124, attrs, defAttrs, 123, 131); + + // //////////////////////////////////////////////////////////////////////// + // area11, "font-weight" tests + ID = "area11"; + defAttrs = buildDefaultTextAttrs(ID, "12pt", kBoldFontWeight); + testDefaultTextAttrs(ID, defAttrs); + + attrs = { }; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 13); + + attrs = { "font-weight": kNormalFontWeight }; + testTextAttrs(ID, 13, attrs, defAttrs, 13, 20); + + attrs = { }; + testTextAttrs(ID, 20, attrs, defAttrs, 20, 27); + + attrs = { "font-weight": kNormalFontWeight }; + testTextAttrs(ID, 27, attrs, defAttrs, 27, 33); + + attrs = { }; + testTextAttrs(ID, 33, attrs, defAttrs, 33, 51); + + attrs = { "font-weight": kNormalFontWeight }; + testTextAttrs(ID, 51, attrs, defAttrs, 51, 57); + + attrs = { }; + testTextAttrs(ID, 57, attrs, defAttrs, 57, 97); + + // //////////////////////////////////////////////////////////////////////// + // test out of range offset + testTextAttrsWrongOffset("area12", -1); + testTextAttrsWrongOffset("area12", 500); + + // //////////////////////////////////////////////////////////////////////// + // test zero offset on empty hypertext accessibles + ID = "area13"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + attrs = { }; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 0); + + ID = "area14"; + defAttrs = buildDefaultTextAttrs(ID, kInputFontSize, + kNormalFontWeight, kInputFontFamily); + + attrs = { }; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 0); + + // //////////////////////////////////////////////////////////////////////// + // area15, embed char tests, "*plain*plain**bold*bold*" + ID = "area15"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + + // p + testTextAttrs(ID, 0, { }, { }, 0, 1); + // plain + testTextAttrs(ID, 1, { }, defAttrs, 1, 6); + // p + testTextAttrs(ID, 6, { }, { }, 6, 7); + // plain + testTextAttrs(ID, 7, { }, defAttrs, 7, 12); + // p and img + testTextAttrs(ID, 12, { }, { }, 12, 14); + // bold + attrs = { "font-weight": kBoldFontWeight }; + testTextAttrs(ID, 14, attrs, defAttrs, 14, 18); + // p + testTextAttrs(ID, 18, { }, { }, 18, 19); + // bold + attrs = { "font-weight": kBoldFontWeight }; + testTextAttrs(ID, 19, attrs, defAttrs, 19, 23); + // p + testTextAttrs(ID, 23, { }, { }, 23, 24); + + // //////////////////////////////////////////////////////////////////////// + // area16, "font-family" tests + ID = "area16"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + testDefaultTextAttrs(ID, defAttrs); + + attrs = { "font-family": kMonospaceFontFamily }; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 4); + + attrs = { }; + testTextAttrs(ID, 4, attrs, defAttrs, 4, 9); + + attrs = { "font-family": kSerifFontFamily }; + testTextAttrs(ID, 9, attrs, defAttrs, 9, 13); + + attrs = { }; + testTextAttrs(ID, 13, attrs, defAttrs, 13, 18); + + attrs = { "font-family": kAbsentFontFamily }; + testTextAttrs(ID, 18, attrs, defAttrs, 18, 22); + + // bug 1224498 - this fails with 'cursive' fontconfig lookup + if (!LINUX) { + attrs = { }; + testTextAttrs(ID, 22, attrs, defAttrs, 22, 27); + + attrs = { "font-family": kCursiveFontFamily }; + testTextAttrs(ID, 27, attrs, defAttrs, 27, 31); + + attrs = { }; + testTextAttrs(ID, 31, attrs, defAttrs, 31, 45); + } + + // //////////////////////////////////////////////////////////////////////// + // area17, "text-decoration" tests + ID = "area17"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + testDefaultTextAttrs(ID, defAttrs); + + attrs = { + "text-underline-style": "solid", + "text-underline-color": getSystemColor("CanvasText"), + }; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 10); + + attrs = { + "text-underline-style": "solid", + "text-underline-color": "rgb(0, 0, 255)", + }; + testTextAttrs(ID, 10, attrs, defAttrs, 10, 15); + + attrs = { + "text-underline-style": "dotted", + "text-underline-color": getSystemColor("CanvasText"), + }; + testTextAttrs(ID, 15, attrs, defAttrs, 15, 22); + + attrs = { + "text-line-through-style": "solid", + "text-line-through-color": getSystemColor("CanvasText"), + }; + testTextAttrs(ID, 22, attrs, defAttrs, 22, 34); + + attrs = { + "text-line-through-style": "solid", + "text-line-through-color": "rgb(0, 0, 255)", + }; + testTextAttrs(ID, 34, attrs, defAttrs, 34, 39); + + attrs = { + "text-line-through-style": "wavy", + "text-line-through-color": getSystemColor("CanvasText"), + }; + testTextAttrs(ID, 39, attrs, defAttrs, 39, 44); + + // //////////////////////////////////////////////////////////////////////// + // area18, "auto-generation text" tests + ID = "area18"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + testDefaultTextAttrs(ID, defAttrs); + + attrs = { + "auto-generated": "true", + "font-family": "-moz-bullet-font", + }; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 2); + testTextAttrs(ID, 3, { }, defAttrs, 3, 7); + attrs = { + "auto-generated": "true", + }; + testTextAttrs(ID, 7, attrs, defAttrs, 7, 8); + + // //////////////////////////////////////////////////////////////////////// + // area19, "HTML5 mark tag" test + // text enclosed in mark tag will have a different background color + // However, since bug 982125, it is its own accessible. + // Therefore, anything other than the default background color is + // unexpected. + ID = "area19"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + + attrs = {}; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 10); + + ID = "area19mark"; + let defMarkAttrs = buildDefaultTextAttrs(ID, "12pt"); + attrs = {}; + testTextAttrs(ID, 0, attrs, defMarkAttrs, 0, 7); + + ID = "area19"; + attrs = {}; + testTextAttrs(ID, 11, attrs, defAttrs, 11, 22); + + // //////////////////////////////////////////////////////////////////////// + // area20, "aOffset as -1 (Mozilla Bug 789621)" test + + ID = "area20"; + defAttrs = buildDefaultTextAttrs(ID, "15pt"); + testDefaultTextAttrs(ID, defAttrs); + + testTextAttrs(ID, -1, {}, defAttrs, 0, 11); + + // //////////////////////////////////////////////////////////////////////// + // HTML sub tag offset test - verify attributes + ID = "sub_tag"; + defAttrs = buildDefaultTextAttrs(ID, "10pt"); + defAttrs["text-position"] = "sub"; + testDefaultTextAttrs(ID, defAttrs); + testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true); + + // //////////////////////////////////////////////////////////////////////// + // HTML sup tag offset test - verify attributes + ID = "sup_tag"; + defAttrs = buildDefaultTextAttrs(ID, "10pt"); + defAttrs["text-position"] = "super"; + testDefaultTextAttrs(ID, defAttrs); + testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true); + + // //////////////////////////////////////////////////////////////////////// + // ARIA subscript role - verify text-position attribute + ID = "subscript_role"; + defAttrs = { "text-position": "sub" }; + testDefaultTextAttrs(ID, defAttrs, true); + testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true); + + // //////////////////////////////////////////////////////////////////////// + // ARIA superscript role - verify text-position attribute + ID = "superscript_role"; + defAttrs = { "text-position": "super" }; + testDefaultTextAttrs(ID, defAttrs, true); + testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true); + + // //////////////////////////////////////////////////////////////////////// + // test text-position attributes in various situations + ID = "superscript_role_in_div"; + defAttrs = { "text-position": "super" }; + testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true); + + ID = "sub_within_superscript_role"; + defAttrs = { "text-position": "sub" }; + testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true); + + ID = "sup_within_subscript_role"; + defAttrs = { "text-position": "super" }; + testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true); + + ID = "sub_within_sup"; + defAttrs = { "text-position": "sub" }; + testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true); + + ID = "sup_within_sub"; + defAttrs = { "text-position": "super" }; + testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true); + + ID = "css_sub_within_superscript_role"; + attrs = { "text-position": "sub" }; + testTextAttrs(ID, 0, attrs, {}, 0, 11, true); + + ID = "css_super_within_subscript_role"; + attrs = { "text-position": "super" }; + testTextAttrs(ID, 0, attrs, {}, 0, 11, true); + + ID = "sub_with_superscript_role"; + defAttrs = { "text-position": "super" }; + testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true); + + ID = "sup_with_subscript_role"; + defAttrs = { "text-position": "sub" }; + testTextAttrs(ID, 0, {}, defAttrs, 0, 11, true); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body style="font-size: 12pt"> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=345759" + title="Implement text attributes"> + Mozilla Bug 345759 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=473569" + title="Restrict text-position to allowed values"> + Mozilla Bug 473569 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=473576" + title="font-family text attribute should expose actual font used"> + Mozilla Bug 473576 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=523304" + title="expose text-underline-color and text-line-through-color text attributes"> + Mozilla Bug 523304 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=735645" + title="expose sub and sup elements in text attributes"> + Mozilla Bug 735645 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=445516" + title="Support auto-generated text attribute on bullet lists"> + Mozilla Bug 445516 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=789621" + title="getTextAttributes doesn't work with magic offsets"> + Mozilla Bug 789621 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <p id="area1" style="font-size: smaller">Normal <b>Bold</b> Normal</p> + <p id="area2" style="font-size: 120%">Normal <b>Bold <i>Italic </i>Bold</b> Normal</p> + <p id="area3" style="background-color: blue;"> + <span style="color: green; background-color: rgb(0, 0, 255)"> + Green + <span style="color: red">but children are red</span> + </span><span style="color: green; background-color: rgb(255, 255, 0);"> + Another green section. + </span> + </p> + <p id="area4"> + <span style="color: green"> + Green + </span><span style="color: green"> + Green too + <span style="color: red">with red children</span> + Green again + </span> + </p> + <!-- Green!*!RedNormal--> + <p id="area5"> + <span style="color: green">Green</span> + <img src="../moz.png" alt="image"/> + <span style="color: red">Red</span>Normal + </p> + <p id="area6"> + This <sup>sentence</sup> has the word + <span style="vertical-align:super;">sentence</span> in + <sub>superscript</sub> and + <span style="vertical-align:sub;">subscript</span> and + <span style="vertical-align:20%;">superscript 20%</span> and + <span style="vertical-align:-20%;">subscript 20%</span> and + <span style="vertical-align:20px;">superscript 20px</span> and + <span style="vertical-align:-20px;">subscript 20px</span> + </p> + + <p lang="en" id="area7"> + <span lang="ru">Привет</span> + <span style="background-color: blue">Blue BG color</span> + <span lang="de">Ich bin/Du bist</span> + <span lang="en"> + Normal + <span style="color: magenta">Magenta<b>Bold</b>Magenta</span> + </span> + </p> + + <p id="area9" style="font-size: smaller">Small + <span style="font-size: 120%">bigger</span> smaller + <span style="background-color: blue;">background blue</span> normal + <span style="font-style: italic;">Different styling</span> normal + <span style="font-family: monospace;">Different font</span> normal + <span style="text-decoration: underline;">underlined</span> normal + <span style="text-decoration: line-through;">strikethrough</span> normal + <s>strikethrough</s> normal + <strike>strikethrough</strike> normal + </p> + + <p id="area10">Normal + <span style="font-size: 120%">bigger</span> smaller + <span style="background-color: blue;">background blue</span> normal + <span style="font-style: italic;">Different styling</span> normal + <span style="font-family: monospace;">Different font</span> normal + <span style="text-decoration: underline;">underlined</span> normal + <span style="text-decoration: line-through;">strikethrough</span> normal + </p> + + <p id="area11" style="font-weight: bolder;"> + <span style="font-weight: bolder;">bolder</span>bolder + <span style="font-weight: lighter;">lighter</span>bolder + <span style="font-weight: normal;">normal</span>bolder + <b>bold</b>bolder + <span style="font-weight: 400;">normal</span>bolder + <span style="font-weight: 700;">bold</span>bolder + <span style="font-weight: bold;">bold</span>bolder + <span style="font-weight: 900;">bold</span>bolder + </p> + + <p id="area12">hello</p> + <p id="area13"></p> + <input id="area14"> + + <!-- *plain*plain**bold*bold*--> + <div id="area15"><p>embed</p>plain<p>embed</p>plain<p>embed</p><img src="../moz.png" alt="image"/><b>bold</b><p>embed</p><b>bold</b><p>embed</p></div> + + <p id="area16" style="font-family: sans-serif;"> + <span style="font-family: monospace;">text</span>text + <span style="font-family: serif;">text</span>text + <span style="font-family: BodoniThatDoesntExist;">text</span>text + <span style="font-family: Comic Sans MS, cursive;">text</span>text + <span style="font-family: sans-serif, fantasy;">text</span>text + </p> + + <p id="area17"> + <span style="text-decoration-line: underline;">underline + </span><span style="text-decoration: underline; text-decoration-color: blue;">blue + </span><span style="text-decoration: underline; text-decoration-style: dotted;">dotted + </span><span style="text-decoration-line: line-through;">linethrough + </span><span style="text-decoration: line-through; text-decoration-color: blue;">blue + </span><span style="text-decoration: line-through; text-decoration-style: wavy;">wavy + </span> + </p> + + <ul> + <li id="area18" class="gencontent">item</li> + </ul> + + <p id="area19">uncolored + <mark id="area19mark">colored</mark> uncolored + </p> + + <p id="area20" style="font-size: 15pt;">offset test</p> + + <!-- subscript, superscript tests --> + <sub id="sub_tag">offset test</sub> + <sup id="sup_tag">offset test</sup> + <p id="subscript_role" role="subscript">offset test</p> + <p id="superscript_role" role="superscript">offset test</p> + + <div><span id="superscript_role_in_div" role="superscript">offset test</span></div> + <p role="superscript"><sub id="sub_within_superscript_role">offset test</sub></p> + <p role="subscript"><sup id="sup_within_subscript_role">offset test</sup></p> + <sup><sub id="sub_within_sup">offset test</sub></sup>
+ <sub><sup id="sup_within_sub">offset test</sup></sub>
+ <p id="css_sub_within_superscript_role" role="superscript"><span style="vertical-align: sub">offset test</span></p> + <p id="css_super_within_subscript_role" role="subscript"><span style="vertical-align: super">offset test</span></p> + <sub id="sub_with_superscript_role" role="superscript">offset test</sub> + <sup id="sup_with_subscript_role" role="subscript">offset test</sup> + +</body> +</html> diff --git a/accessible/tests/mochitest/textattrs/test_general.xhtml b/accessible/tests/mochitest/textattrs/test_general.xhtml new file mode 100644 index 0000000000..de0556f10c --- /dev/null +++ b/accessible/tests/mochitest/textattrs/test_general.xhtml @@ -0,0 +1,51 @@ +<?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" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Tests: XUL label text interface"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Testing + + function doTests() + { + ////////////////////////////////////////////////////////////////////////// + // XUL label + + testTextAttrs("label1", 0, {}, {}, 0, 5, true); + testTextAttrs("label2", 0, {}, {}, 0, 5, true); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + ]]> + </script> + + <vbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + <label id="label1" value="Hello"/> + <label id="label2">Hello</label> + </vbox> +</window> diff --git a/accessible/tests/mochitest/textattrs/test_invalid.html b/accessible/tests/mochitest/textattrs/test_invalid.html new file mode 100644 index 0000000000..7028b32622 --- /dev/null +++ b/accessible/tests/mochitest/textattrs/test_invalid.html @@ -0,0 +1,59 @@ +<html> + +<head> + <title>Invalid text attribute</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + + <script type="application/javascript"> + function doTests() { + testDefaultTextAttrs("aria_invalid_empty", {}, true); + testDefaultTextAttrs("aria_invalid_true", { "invalid": "true" }, true); + testDefaultTextAttrs("aria_invalid_false", { "invalid": "false" }, true); + testDefaultTextAttrs("aria_invalid_grammar", { "invalid": "grammar" }, true); + testDefaultTextAttrs("aria_invalid_spelling", { "invalid": "spelling" }, true); + testDefaultTextAttrs("aria_invalid_erroneous", { "invalid": "true" }, true); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=445510" + title="Support ARIA-based text attributes"> + Mozilla Bug 445510 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="aria_invalid_empty" aria-invalid="">no invalid</div> + <div id="aria_invalid_true" aria-invalid="true">invalid:true</div> + <div id="aria_invalid_false" aria-invalid="false">invalid:false</div> + <div id="aria_invalid_grammar" aria-invalid="grammar">invalid:grammar</div> + <div id="aria_invalid_spelling" aria-invalid="spelling">invalid:spelling</div> + <div id="aria_invalid_erroneous" aria-invalid="erroneous">invalid:true</div> +</body> +</html> diff --git a/accessible/tests/mochitest/textattrs/test_mathml.html b/accessible/tests/mochitest/textattrs/test_mathml.html new file mode 100644 index 0000000000..e6936ebc7f --- /dev/null +++ b/accessible/tests/mochitest/textattrs/test_mathml.html @@ -0,0 +1,47 @@ +<html> + +<head> + <title>MathML Text attributes tests</title> + <meta charset="utf-8"> + <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script src="../common.js"></script> + <script src="../attributes.js"></script> + <script src="../events.js"></script> + + <script> + function doTest() { + const math = getNode("math"); + const defAttrs = buildDefaultTextAttrs(math, "10pt"); + testDefaultTextAttrs(math, defAttrs); + + for (const id of ["mn", "mi", "annotation", "annotationXml"]) { + testTextAttrs(id, 0, {}, defAttrs, 0, 1); + } + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body style="font-size: 12pt"> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <math id="math" style="font-size: smaller"> + <mn id="mn">1</mn> + <mi id="mi">x</mi> + <!-- tabindex forces creation of an Accessible --> + <annotation id="annotation" tabindex="0">a</annotation> + <annotation-xml id="annotationXml" tabindex="0">a</annotation-xml> + </math> + +</body> +</html> diff --git a/accessible/tests/mochitest/textattrs/test_spelling.html b/accessible/tests/mochitest/textattrs/test_spelling.html new file mode 100644 index 0000000000..b8f3858353 --- /dev/null +++ b/accessible/tests/mochitest/textattrs/test_spelling.html @@ -0,0 +1,52 @@ +<html> + +<head> + <title>Spell check text attribute tests</title> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../attributes.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + async function doTest() { + const misspelledAttrs = {"invalid": "spelling"}; + + let editable = document.getElementById("div_after_misspelling"); + // The attr change event gets fired on the last accessible containing a + // spelling error. + let spellDone = waitForEvent(EVENT_TEXT_ATTRIBUTE_CHANGED, "div_after_misspelling_div2"); + editable.focus(); + await spellDone; + testTextAttrs("div_after_misspelling_div1", 0, {}, {}, 0, 5, true); + testTextAttrs("div_after_misspelling_div1", 5, misspelledAttrs, {}, 5, 9, true); + testTextAttrs("div_after_misspelling_div2", 0, {}, {}, 0, 5, true); + testTextAttrs("div_after_misspelling_div2", 5, misspelledAttrs, {}, 5, 9, true); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- Text attribute offsets for accessibles after first spelling error (bug 1479678) --> + <div id="div_after_misspelling" contenteditable="true" spellcheck="true" lang="en-US"> + <div id="div_after_misspelling_div1">Test tset</div> + <div id="div_after_misspelling_div2">test tset</div> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/textattrs/test_svg.html b/accessible/tests/mochitest/textattrs/test_svg.html new file mode 100644 index 0000000000..c8d5d5f893 --- /dev/null +++ b/accessible/tests/mochitest/textattrs/test_svg.html @@ -0,0 +1,52 @@ +<html> + +<head> + <title>SVG Text attributes tests</title> + <meta charset="utf-8"> + <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script src="../common.js"></script> + <script src="../attributes.js"></script> + <script src="../events.js"></script> + + <script> + function doTest() { + const svg = getNode("svg"); + const defAttrs = buildDefaultTextAttrs(svg, "10pt"); + testDefaultTextAttrs(svg, defAttrs); + testTextAttrs(svg, 0, {}, defAttrs, 0, 2); + + const g = getNode("g"); + testTextAttrs(g, 0, {}, defAttrs, 0, 2); + + const a = getNode("a"); + const aDefAttrs = buildDefaultTextAttrs(a, "10pt"); + testTextAttrs(a, 0, {}, aDefAttrs, 0, 1); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body style="font-size: 12pt"> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <svg id="svg" style="font-size: smaller"> + <foreignobject>f1</foreignobject> + <g id="g"> + <title>g</title> + <foreignobject>f2</foreignobject> + </g> + <text><a href="#" id="a">a</a></text> + </svg> + +</body> +</html> diff --git a/accessible/tests/mochitest/textcaret/a11y.ini b/accessible/tests/mochitest/textcaret/a11y.ini new file mode 100644 index 0000000000..6e2e4ef8d7 --- /dev/null +++ b/accessible/tests/mochitest/textcaret/a11y.ini @@ -0,0 +1,5 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_general.html] diff --git a/accessible/tests/mochitest/textcaret/test_general.html b/accessible/tests/mochitest/textcaret/test_general.html new file mode 100644 index 0000000000..bd949116fd --- /dev/null +++ b/accessible/tests/mochitest/textcaret/test_general.html @@ -0,0 +1,174 @@ +<html> + +<head> + <title>Text accessible caret testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + /** + * Turn on/off the caret browsing mode. + */ + function turnCaretBrowsing(aIsOn) { + Services.prefs.setBoolPref("accessibility.browsewithcaret", aIsOn); + } + + /** + * Test caret offset for the given accessible. + */ + function testCaretOffset(aID, aCaretOffset) { + var acc = getAccessible(aID, [nsIAccessibleText]); + is(acc.caretOffset, aCaretOffset, + "Wrong caret offset for " + aID); + } + + function testCaretOffsets(aList) { + for (var i = 0; i < aList.length; i++) + testCaretOffset(aList[0][0], aList[0][1]); + } + + function queueTraversalList(aList, aFocusNode) { + for (var i = 0 ; i < aList.length; i++) { + var node = aList[i].DOMPoint[0]; + var nodeOffset = aList[i].DOMPoint[1]; + + var textAcc = aList[i].point[0]; + var textOffset = aList[i].point[1]; + var textList = aList[i].pointList; + var invoker = + new moveCaretToDOMPoint(textAcc, node, nodeOffset, textOffset, + ((i == 0) ? aFocusNode : null), + testCaretOffsets.bind(null, textList)); + gQueue.push(invoker); + } + } + + /** + * Do tests. + */ + var gQueue = null; + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + function doTests() { + turnCaretBrowsing(true); + + // test caret offsets + testCaretOffset(document, 0); // because of no selection ranges + testCaretOffset("textbox", -1); + testCaretOffset("textarea", -1); + testCaretOffset("p", -1); + + // test caret move events and caret offsets + gQueue = new eventQueue(); + + gQueue.push(new setCaretOffset("textbox", 1, "textbox")); + gQueue.push(new setCaretOffset("link", 1, "link")); + gQueue.push(new setCaretOffset("heading", 1, document)); + + // a*b*c + var p2Doc = getNode("p2_container").contentDocument; + var traversalList = [ + { // before 'a' + DOMPoint: [ getNode("p2", p2Doc).firstChild, 0 ], + point: [ getNode("p2", p2Doc), 0 ], + pointList: [ [ p2Doc, 0 ] ], + }, + { // after 'a' (before anchor) + DOMPoint: [ getNode("p2", p2Doc).firstChild, 1 ], + point: [ getNode("p2", p2Doc), 1 ], + pointList: [ [ p2Doc, 0 ] ], + }, + { // before 'b' (inside anchor) + DOMPoint: [ getNode("p2_a", p2Doc).firstChild, 0 ], + point: [ getNode("p2_a", p2Doc), 0 ], + pointList: [ + [ getNode("p2", p2Doc), 1 ], + [ p2Doc, 0 ], + ], + }, + { // after 'b' (inside anchor) + DOMPoint: [ getNode("p2_a", p2Doc).firstChild, 1 ], + point: [ getNode("p2_a", p2Doc), 1 ], + pointList: [ + [ getNode("p2", p2Doc), 1 ], + [ p2Doc, 0 ], + ], + }, + { // before 'c' (after anchor) + DOMPoint: [ getNode("p2", p2Doc).lastChild, 0 ], + point: [ getNode("p2", p2Doc), 2 ], + pointList: [ [ p2Doc, 0 ] ], + }, + { // after 'c' + DOMPoint: [ getNode("p2", p2Doc).lastChild, 1 ], + point: [ getNode("p2", p2Doc), 3 ], + pointList: [ [ p2Doc, 0 ] ], + }, + ]; + queueTraversalList(traversalList, getNode("p2", p2Doc)); + + gQueue.onFinish = function() { + turnCaretBrowsing(false); + }; + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=448744" + title="caretOffset should return -1 if the system caret is not currently with in that particular object"> + Bug 448744 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=524115" + title="HyperText accessible should get focus when the caret is positioned inside of it, text is changed or copied into clipboard by ATs"> + Bug 524115 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=546068" + title="Position is not being updated when atk_text_set_caret_offset is used"> + Bug 546068 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=672717" + title="Broken caret when moving into/out of embedded objects with right arrow"> + Bug 672717 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=725581" + title="caretOffset for textarea should be -1 when textarea doesn't have a focus"> + Bug 725581 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="textbox" value="hello"/> + <textarea id="textarea">text<br>text</textarea> + <p id="p" contentEditable="true"><span>text</span><br/>text</p> + <a id="link" href="about:mozilla">about mozilla</a> + <h5 id="heading">heading</h5> + <iframe id="p2_container" + src="data:text/html,<p id='p2' contentEditable='true'>a<a id='p2_a' href='mozilla.org'>b</a>c</p>"></iframe> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/textrange/a11y.ini b/accessible/tests/mochitest/textrange/a11y.ini new file mode 100644 index 0000000000..4e610c61f3 --- /dev/null +++ b/accessible/tests/mochitest/textrange/a11y.ini @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/moz.png + +[test_general.html] +[test_selection.html] diff --git a/accessible/tests/mochitest/textrange/test_general.html b/accessible/tests/mochitest/textrange/test_general.html new file mode 100644 index 0000000000..b7da4ee8aa --- /dev/null +++ b/accessible/tests/mochitest/textrange/test_general.html @@ -0,0 +1,106 @@ +<!DOCTYPE html> +<html> +<head> + <title>Text Range tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + <script type="application/javascript"> + + function doTest() { + // enclosingRange + var input = getAccessible("input", [ nsIAccessibleText ]); + testTextRange(input.enclosingRange, "enclosing range for 'input'", + input, 0, input, 5, "hello", input); + + var ta = getAccessible("textarea", [ nsIAccessibleText ]); + testTextRange(ta.enclosingRange, "enclosing range for 'textarea'", + ta, 0, ta, 5, "hello", ta); + + var iframeDocNode = getNode("iframe").contentDocument; + var iframeDoc = getAccessible(iframeDocNode, [ nsIAccessibleText ]); + testTextRange(iframeDoc.enclosingRange, "enclosing range for iframe doc", + iframeDoc, 0, iframeDoc, 1, "hello", + iframeDoc, [ getNode("p", iframeDocNode) ]); + + // getRangeByChild + var docacc = getAccessible(document, [ nsIAccessibleText ]); + var p1 = getAccessible("p1"); + var p1Range = docacc.getRangeByChild(p1); + testTextRange(p1Range, "range by 'p1' child", + p1, 0, "p1", 11, "text text", + p1, ["p1_img"]); + + testTextRange(docacc.getRangeByChild(getAccessible("p1_img")), + "range by 'p1_img' child", + "p1", 5, "p1", 5, "", + "p1", ["p1_img"]); + + var p2 = getAccessible("p2"); + var p2Range = docacc.getRangeByChild(p2); + testTextRange(p2Range, "range by 'p2' child", + p2, 0, "p2", 11, "text link text", + p2, ["p2_a"]); + + testTextRange(docacc.getRangeByChild(getAccessible("p2_a")), + "range by 'p2_a' child", + "p2_a", 0, "p2_a", 5, "link", + "p2_a", ["p2_img"]); + + // getRangeAtPoint + getNode("p2_a").scrollIntoView(true); + var [x, y] = getPos("p2_a"); + testTextRange(docacc.getRangeAtPoint(x + 1, y + 1), + "range at 'p2_a' top-left edge", + "p2_a", 0, "p2_a", 0, "", + "p2_a"); + + // TextRange::compare + ok(input.enclosingRange.compare(input.enclosingRange), + "input enclosing ranges should be equal"); + + ok(!input.enclosingRange.compare(ta.enclosingRange), + "input and textarea enclosing ranges can't be equal"); + + // TextRange::compareEndPoints + var res = p1Range.compareEndPoints(EndPoint_End, p2Range, EndPoint_Start); + is(res, -1, "p1 range must be lesser with p2 range"); + + res = p2Range.compareEndPoints(EndPoint_Start, p1Range, EndPoint_End); + is(res, 1, "p2 range must be greater with p1 range"); + + res = p1Range.compareEndPoints(EndPoint_Start, p1Range, EndPoint_Start); + is(res, 0, "p1 range must be equal with p1 range"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Implement Text accessible text range methods" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=975065">Bug 975065</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="input" value="hello"> + <textarea id="textarea">hello</textarea> + <iframe id="iframe" src="data:text/html,<html><body><p id='p'>hello</p></body></html>"></iframe> + <p id="p1">text <img id="p1_img", src="../moz.png"> text</p> + <p id="p2">text <a id="p2_a" href="www">link<img id="p2_img", src="../moz.png"></a> text</p> + +</body> +</html> diff --git a/accessible/tests/mochitest/textrange/test_selection.html b/accessible/tests/mochitest/textrange/test_selection.html new file mode 100644 index 0000000000..2a5d4da5c2 --- /dev/null +++ b/accessible/tests/mochitest/textrange/test_selection.html @@ -0,0 +1,144 @@ +<!DOCTYPE html> +<html> +<head> + <title>Text Range selection tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../text.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + <script type="application/javascript"> + + function doTest() { + var sel = window.getSelection(); + var p = getNode("p1"); + var a = getNode("p2_a"); + + var range = document.createRange(); + sel.addRange(range); + + // the accessible is contained by the range + range.selectNode(p); + + var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange); + + testTextRange(a11yrange, "selection range #1", document, 3, document, 4); + + ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #1."); + testTextRange(a11yrange, "cropped range #1", a, 0, a, 5); + + // the range is contained by the accessible + range.selectNode(a); + a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange); + + testTextRange(a11yrange, "selection range #2", p, 5, p, 6); + + ok(a11yrange.crop(getAccessible(p)), "Range failed to crop #2."); + testTextRange(a11yrange, "cropped range #2", p, 5, p, 6); + + // the range starts before the accessible and ends inside it + range.setStart(p, 0); + range.setEndAfter(a.firstChild, 4); + a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange); + + testTextRange(a11yrange, "selection range #3", p, 0, a, 4); + + ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #3."); + testTextRange(a11yrange, "cropped range #3", a, 0, a, 4); + + // the range starts inside the accessible and ends after it + range.setStart(a.firstChild, 1); + range.setEndAfter(p); + a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange); + + testTextRange(a11yrange, "selection range #4", a, 1, document, 4); + + ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #4."); + testTextRange(a11yrange, "cropped range #4", a, 1, a, 5); + + // the range ends before the accessible + range.setStart(p.firstChild, 0); + range.setEnd(p.firstChild, 4); + a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange); + + testTextRange(a11yrange, "selection range #5", p, 0, p, 4); + ok(!a11yrange.crop(getAccessible(a)), "Crop #5 succeeded while it shouldn't"); + + // the range starts after the accessible + range.setStart(p.lastChild, 0); + range.setEnd(p.lastChild, 4); + a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange); + + testTextRange(a11yrange, "selection range #6", p, 6, p, 10); + + ok(!a11yrange.crop(getAccessible(a)), "Crop #6 succeeded while it shouldn't"); + + // crop a range by a table + range.selectNode(getNode("c2")); + a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange); + + testTextRange(a11yrange, "selection range #7", document, 4, document, 5); + + ok(a11yrange.crop(getAccessible("table")), "Range failed to crop #7."); + testTextRange(a11yrange, "cropped range #7", "table", 0, "table", 1); + + // test compare points for selection with start in nested node + range.setStart(a.firstChild, 2); + range.setEnd(p.lastChild, 3); + a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange); + var res = a11yrange.compareEndPoints(EndPoint_Start, a11yrange, EndPoint_End); + is(res, -1, "start must be lesser than end"); + + res = a11yrange.compareEndPoints(EndPoint_End, a11yrange, EndPoint_Start); + is(res, 1, "end must be greater than start"); + + // Crop a range to its next sibling. + range.selectNode(getNode("c3p1").firstChild); + a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange); + testTextRange(a11yrange, "selection range #8", "c3p1", 0, "c3p1", 1); + ok(!a11yrange.crop(getAccessible("c3p2")), "Crop #8 succeeded but shouldn't have."); + // Crop a range to its previous sibling. + range.selectNode(getNode("c3p2").firstChild); + a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange); + testTextRange(a11yrange, "selection range #9", "c3p2", 0, "c3p2", 1); + ok(!a11yrange.crop(getAccessible("c3p1")), "Crop #9 succeeded but shouldn't have."); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Implement IAccessible2_3::selectionRanges" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1233118">Bug 1233118</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <p id="p1">text <a id="p2_a" href="www">link<img id="p2_img", src="../moz.png"></a> text</p> + + <div id="c2">start<table id="table"><tr><td>cell</td></tr></table>end</div> + + <div id="c3"><p id="c3p1">a</p><p id="c3p2">b</p></div> +</body> +</html> diff --git a/accessible/tests/mochitest/textselection/a11y.ini b/accessible/tests/mochitest/textselection/a11y.ini new file mode 100644 index 0000000000..6581af56db --- /dev/null +++ b/accessible/tests/mochitest/textselection/a11y.ini @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_general.html] +[test_userinput.html] diff --git a/accessible/tests/mochitest/textselection/test_general.html b/accessible/tests/mochitest/textselection/test_general.html new file mode 100644 index 0000000000..92e7988a87 --- /dev/null +++ b/accessible/tests/mochitest/textselection/test_general.html @@ -0,0 +1,221 @@ +<html> + +<head> + <title>Text selection testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + /** + * Helper function to test selection bounds. + * @param {string} aID The ID to test. + * @param {nsIAccessibleText} acc The accessible to test. + * @param {int} index The selection's index to test. + * @param {array} offsets The start and end offset to test against. + * @param {string} msgStart The start of the message to return in test + * messages. + */ + function testSelectionBounds(aID, acc, index, offsets, msgStart) { + const [expectedStart, expectedEnd] = offsets; + const startOffset = {}, endOffset = {}; + acc.getSelectionBounds(index, startOffset, endOffset); + + is(startOffset.value, Math.min(expectedStart, expectedEnd), + msgStart + ": Wrong start offset for " + aID); + is(endOffset.value, Math.max(expectedStart, expectedEnd), + msgStart + ": Wrong end offset for " + aID); + } + + /** + * Test adding selections to accessibles. + * @param {string} aID The ID of the element to test. + * @param {array} aSelections Array of selection start and end indices. + */ + async function addSelections(aID, aSelections) { + info("Test adding selections to " + aID); + const hyperText = getAccessible(aID, [ nsIAccessibleText ]); + const initialSelectionCount = hyperText.selectionCount; + + // Multiple selection changes will be coalesced, so just listen for one. + const selectionChange = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, aID); + for (let [startOffset, endOffset] of aSelections) { + hyperText.addSelection(startOffset, endOffset); + } + await selectionChange; + + is(hyperText.selectionCount, + aSelections.length + initialSelectionCount, + "addSelection: Wrong selection count for " + aID); + + for (let i in aSelections) { + testSelectionBounds(aID, hyperText, initialSelectionCount + i, + aSelections[i], "addSelection"); + } + + is(hyperText.caretOffset, aSelections[hyperText.selectionCount -1][1], + "addSelection: caretOffset not at selection end for " + aID); + } + + /** + * Test changing selections in accessibles. + * @param {string} aID The ID of the element to test. + * @param {int} aIndex The index of the selection to change. + * @param {array} aSelection Array of the selection's new start and end + * indices. + */ + async function changeSelection(aID, aIndex, aSelection) { + info("Test changing the selection of " + aID + " at index " + aIndex); + const [startOffset, endOffset] = aSelection; + const hyperText = getAccessible(aID, [ nsIAccessibleText ]); + + const selectionChanged = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, aID); + hyperText.setSelectionBounds(aIndex, startOffset, endOffset); + await selectionChanged; + + testSelectionBounds(aID, hyperText, aIndex, + aSelection, "setSelectionBounds"); + + is(hyperText.caretOffset, endOffset, + "setSelectionBounds: caretOffset not at selection end for " + aID); + } + + /** + * Test removing all selections from accessibles. + * @param {string} aID The ID of the element to test. + */ + async function removeSelections(aID) { + info("Testing removal of all selections from " + aID); + const hyperText = getAccessible(aID, [ nsIAccessibleText ]); + + let selectionsRemoved = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, document); + const selectionCount = hyperText.selectionCount; + for (let i = 0; i < selectionCount; i++) { + hyperText.removeSelection(0); + } + await selectionsRemoved; + + is(hyperText.selectionCount, 0, + "removeSelection: Wrong selection count for " + aID); + } + + /** + * Test that changing the DOM selection is reflected in the accessibles. + * @param {string} aID The container ID to test in + * @param {string} aNodeID1 The start node of the selection + * @param {int} aNodeOffset1 The offset where the selection should start + * @param {string} aNodeID2 The node in which the selection should end + * @param {int} aNodeOffset2 The index at which the selection should end + * @param {array} aTests An array of accessibles and their start and end + * offsets to test. + */ + async function changeDOMSelection(aID, aNodeID1, aNodeOffset1, + aNodeID2, aNodeOffset2, + aTests) { + info("Test that DOM selection changes are reflected in the accessibles"); + + let selectionChanged = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, aID); + // HyperTextAccessible::GetSelectionDOMRanges ignores hidden selections. + // Here we may be focusing an editable element (and thus hiding the + // main document selection), so blur it so that we test what we want to + // test. + document.activeElement.blur(); + + const sel = window.getSelection(); + const range = document.createRange(); + range.setStart(getNode(aNodeID1), aNodeOffset1); + range.setEnd(getNode(aNodeID2), aNodeOffset2); + sel.addRange(range); + await selectionChanged; + + for (let i = 0; i < aTests.length; i++) { + const text = getAccessible(aTests[i][0], nsIAccessibleText); + is(text.selectionCount, 1, + "setSelectionBounds: Wrong selection count for " + aID); + testSelectionBounds(aID, text, 0, [aTests[i][1], aTests[i][2]], + "setSelectionBounds"); + } + } + + /** + * Test expected and unexpected events for selecting + * all text and focusing both an input and text area. We expect a caret + * move, but not a text selection change. + * @param {string} aID The ID of the element to test. + */ + async function eventsForSelectingAllTextAndFocus(aID) { + info("Test expected caretMove and unexpected textSelection events for " +aID); + let events = waitForEvents({ + expected: [[EVENT_TEXT_CARET_MOVED, aID]], + unexpected: [[EVENT_TEXT_SELECTION_CHANGED, aID]]}, aID); + selectAllTextAndFocus(aID); + await events; + } + + /** + * Do tests + */ + + async function doTests() { + await addSelections("paragraph", [[1, 3], [6, 10]]); + await changeSelection("paragraph", 0, [2, 4]); + await removeSelections("paragraph"); + + // reverse selection + await addSelections("paragraph", [[1, 3], [10, 6]]); + await removeSelections("paragraph"); + + await eventsForSelectingAllTextAndFocus("textbox"); + await changeSelection("textbox", 0, [1, 3]); + + // reverse selection + await changeSelection("textbox", 0, [3, 1]); + + await eventsForSelectingAllTextAndFocus("textarea"); + await changeSelection("textarea", 0, [1, 3]); + + await changeDOMSelection("c1", "c1_span1", 0, "c1_span2", 0, + [["c1", 2, 2]]); + await changeDOMSelection("c2", "c2", 0, "c2_div2", 1, + [["c2", 0, 3], ["c2_div2", 0, 2]]); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=688126" + title="nsIAccessibleText::setSelectionBounds doesn't fire text selection changed events in some cases"> + Bug 688126 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=688124" + title="no text selection changed event when selection is removed"> + Bug 688124 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <p id="paragraph">hello world</p> + <input id="textbox" value="hello"/> + <textarea id="textarea">hello</textarea> + <div id="c1">hi<span id="c1_span1"></span><span id="c1_span2"></span>hi</div> + <div id="c2">hi<div id="c2_div2">hi</div></div> + +</body> +</html> diff --git a/accessible/tests/mochitest/textselection/test_userinput.html b/accessible/tests/mochitest/textselection/test_userinput.html new file mode 100644 index 0000000000..1aa0b2abb6 --- /dev/null +++ b/accessible/tests/mochitest/textselection/test_userinput.html @@ -0,0 +1,76 @@ +<html> + +<head> + <title>Text selection by user input</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + async function doTests() { + // Tab to 't2' and then tab out of it: it must not have any selection. + info("Select all text in t1 and focus it"); + let focused = waitForEvent(EVENT_FOCUS, "t1"); + // Simulate tabbing to t1 by selecting all text before focusing it. + selectAllTextAndFocus("t1"); + await focused; + + info("Tab to t2"); + const t2 = getNode("t2"); + focused = waitForEvent(EVENT_FOCUS, t2); + synthesizeKey("VK_TAB"); + await focused; + + info("Tab to t3 and make sure there is no selection in t2 afterwards"); + const t3 = getNode("t3"); + focused = waitForEvent(EVENT_FOCUS, t3); + synthesizeKey("VK_TAB"); + await focused; + const prevFocus = getAccessible(t2, [ nsIAccessibleText ]); + is(prevFocus.selectionCount, 0, + "Wrong selection count for t2"); + + let exceptionCaught = false; + try { + const startOffsetObj = {}, endOffsetObj = {}; + prevFocus.getSelectionBounds(0, startOffsetObj, endOffsetObj); + } catch (e) { + exceptionCaught = true; + } + + ok(exceptionCaught, "No selection was expected for t2"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=440590" + title="Text selection information is not updated when HTML and XUL entries lose focus"> + Bug 440590 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input type="text" id="t1" maxlength="3" size="3" value="1"> + <input type="text" id="t2" maxlength="3" size="3" value="1"> + <input type="text" id="t3" maxlength="3" size="3" value="1"> + +</body> +</html> diff --git a/accessible/tests/mochitest/tree/a11y.ini b/accessible/tests/mochitest/tree/a11y.ini new file mode 100644 index 0000000000..c2a78c76a7 --- /dev/null +++ b/accessible/tests/mochitest/tree/a11y.ini @@ -0,0 +1,58 @@ +[DEFAULT] +support-files = + dockids.html + wnd.xhtml + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/formimage.png + !/accessible/tests/mochitest/letters.gif + !/accessible/tests/mochitest/moz.png + !/accessible/tests/mochitest/tree/wnd.xhtml + !/dom/media/test/bug461281.ogg + +[test_applicationacc.xhtml] +skip-if = true # Bug 561508 +[test_aria_display_contents.html] +[test_aria_globals.html] +[test_aria_grid.html] +[test_aria_imgmap.html] +[test_aria_list.html] +[test_aria_menu.html] +[test_aria_owns.html] +[test_aria_presentation.html] +[test_aria_table.html] +[test_brokencontext.html] +[test_button.xhtml] +[test_canvas.html] +[test_combobox.xhtml] +[test_cssflexbox.html] +[test_cssoverflow.html] +[test_display_contents.html] +[test_divs.html] +[test_dochierarchy.html] +[test_dockids.html] +[test_filectrl.html] +[test_formctrl.html] +[test_formctrl.xhtml] +[test_gencontent.html] +[test_groupbox.xhtml] +[test_html_in_mathml.html] +[test_iframe.html] +[test_image.xhtml] +[test_img.html] +[test_invalid_img.xhtml] +[test_invalidationlist.html] +[test_list.html] +[test_map.html] +[test_media.html] +[test_select.html] +[test_svg.html] +[test_tabbox.xhtml] +[test_tabbrowser.xhtml] +skip-if = (os == 'linux' && debug) || (os == 'win' && ccov) # Bug 1389365 || bug 1423218 +[test_table.html] +[test_table_2.html] +[test_table_3.html] +[test_tree.xhtml] +[test_txtcntr.html] +[test_txtctrl.html] +[test_txtctrl.xhtml] diff --git a/accessible/tests/mochitest/tree/dockids.html b/accessible/tests/mochitest/tree/dockids.html new file mode 100644 index 0000000000..e964cd759e --- /dev/null +++ b/accessible/tests/mochitest/tree/dockids.html @@ -0,0 +1,32 @@ +<!doctype html> +<html> + <head> + <link rel="next" href="http://www.mozilla.org"> + <style> + head, link, a { display: block; } + link:after { content: "Link to " attr(href); } + </style> + <script> + window.onload = function() { + document.documentElement.appendChild(document.createElement("input")); + + var l = document.createElement("link"); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + l.href = "http://www.mozilla.org"; + l.textContent = "Another "; + document.documentElement.appendChild(l); + + l = document.createElement("a"); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + l.href = "http://www.mozilla.org"; + l.textContent = "Yet another link to mozilla"; + document.documentElement.appendChild(l); + }; + </script> + </head> + <body> + Hey, I'm a <body> with three links that are not inside me and an input + that's not inside me. + </body> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_applicationacc.xhtml b/accessible/tests/mochitest/tree/test_applicationacc.xhtml new file mode 100644 index 0000000000..5e00a2878f --- /dev/null +++ b/accessible/tests/mochitest/tree/test_applicationacc.xhtml @@ -0,0 +1,73 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible Application Accessible hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + // Note: bug 560239 can be tested if this test runs in standalone mode only. + + var gURL = "../tree/wnd.xhtml" + var gWnd = window.openDialog(gURL, "wnd", "chrome,width=600,height=600"); + + function doTest() + { + // Application accessible should contain two root document accessibles, + // one is for browser window, another one is for open dialog window. + var accTree = { + role: ROLE_APP_ROOT, + children: [ + { + role: ROLE_CHROME_WINDOW, + name: "Accessibility Chrome Test Harness - Minefield" + }, + { + role: ROLE_CHROME_WINDOW, + name: "Empty Window" + } + ] + }; + testAccessibleTree(getApplicationAccessible(), accTree); + + gWnd.close(); + + SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + + // We need to open dialog window before accessibility is started. + addLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=560239" + title="no children of application accessible for windows open before accessibility was started"> + Mozilla Bug 560239 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/tree/test_aria_display_contents.html b/accessible/tests/mochitest/tree/test_aria_display_contents.html new file mode 100644 index 0000000000..5c6f7f20fb --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_display_contents.html @@ -0,0 +1,173 @@ +<!DOCTYPE html> +<html> +<head> + <title>ARIA and style="display: contents;"</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // Test ARIA grids that have display: contents; on different elements. + // They should all have equivalent trees. + var accTree = + { TABLE: [ + { ROW: [ + { role: ROLE_COLUMNHEADER, + children: [ { TEXT_LEAF: [ ] }, ] + }, + { role: ROLE_COLUMNHEADER, + children: [ { TEXT_LEAF: [ ] }, ] + }, + ] }, + { ROW: [ + { ROWHEADER: [ + { TEXT_LEAF: [ ] }, + ] }, + { GRID_CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + + testAccessibleTree("gridWithoutDisplayContents", accTree); + testAccessibleTree("gridWithDisplayContents", accTree); + testAccessibleTree("gridWithDisplayContentsRow", accTree); + testAccessibleTree("gridWithDisplayContentsColHeader", accTree); + testAccessibleTree("gridWithDisplayContentsRowHeader", accTree); + testAccessibleTree("gridWithDisplayContentsGridCell", accTree); + + // Test divs with ARIA roles and attributes and display: contents to + // verify that Accessibles are created appropriately. + accTree = + { SECTION: [ + { LIST: [ + { LISTITEM: [ + { TEXT_LEAF: [ ] } + ] }, + ] }, + { SECTION: [ + { LISTITEM: [ + { TEXT_LEAF: [ ] } + ] }, + ] }, + { LISTITEM: [ + { TEXT_LEAF: [ ] } + ] }, + ] }; + testAccessibleTree("container", accTree); + + // Test paragraph with display: contents. It should create a generic + // Accessible that reports the role correctly. + accTree = + { SECTION: [ + { PARAGRAPH: [ { TEXT_LEAF: [ ] } ] }, + { TEXT_LEAF: [ ] }, // space between paragraphs + { TEXT_LEAF: [ ] }, + ] }; + testAccessibleTree("paragraphContainer", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Element with ARIA role and display: contents doesn't get an accessible" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1494196"> + Mozilla Bug 1494196 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="gridWithoutDisplayContents" role="grid"> + <div role="row">
+ <div role="columnheader">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader">row1</div>
+ <div role="gridcell">cell1</div>
+ </div> + </div> + <div id="gridWithDisplayContents" role="grid" style="display:contents;"> + <div role="row">
+ <div role="columnheader">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader">row1</div>
+ <div role="gridcell">cell1</div>
+ </div> + </div> + <div id="gridWithDisplayContentsRow" role="grid"> + <div role="row" style="display:contents;">
+ <div role="columnheader">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader">row1</div>
+ <div role="gridcell">cell1</div>
+ </div> + </div> + <div id="gridWithDisplayContentsColHeader" role="grid"> + <div role="row">
+ <div role="columnheader" style="display:contents;">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader">row1</div>
+ <div role="gridcell">cell1</div>
+ </div> + </div> + <div id="gridWithDisplayContentsRowHeader" role="grid"> + <div role="row">
+ <div role="columnheader">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader" style="display:contents;">row1</div>
+ <div role="gridcell">cell1</div>
+ </div> + </div> + <div id="gridWithDisplayContentsGridCell" role="grid"> + <div role="row">
+ <div role="columnheader">col1</div>
+ <div role="columnheader">col2</div>
+ </div>
+ <div role="row">
+ <div role="rowheader">row1</div>
+ <div role="gridcell" style="display:contents;">cell1</div>
+ </div> + </div> + + <div id="container"> + <div role="list" style="display: contents;"> + <div role="listitem">test</div> + </div> + <div aria-label="test" style="display: contents;"> + <div role="listitem">test</div> + </div> + <div role="none" style="display: contents;"> + <div role="listitem">test</div> + </div> + </div> + + <div id="paragraphContainer"> + <p style="display: contents;">test</p> + <p style="display: contents;" role="none">test</p> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_aria_globals.html b/accessible/tests/mochitest/tree/test_aria_globals.html new file mode 100644 index 0000000000..bb5fe14cdf --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_globals.html @@ -0,0 +1,127 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test Global ARIA States and Accessible Creation</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + var globalIds = [ + "atomic", + "busy", + "controls", + "describedby", + "description", + "disabled", + "dropeffect", + "flowto", + "grabbed", + "haspopup", + "invalid", + "label", + "labelledby", + "live", + "owns", + "relevant", + ]; + + // Elements having ARIA global state or properties or referred by another + // element must be accessible. + ok(isAccessible("pawn"), + "Must be accessible because referred by another element."); + + for (let idx = 0; idx < globalIds.length; idx++) { + ok(isAccessible(globalIds[idx]), + "Must be accessible becuase of aria-" + globalIds[idx] + + " presence"); + } + + // Unfocusable elements, having ARIA global state or property with a valid + // IDREF value, and an inherited presentation role. A generic accessible + // is created (to prevent table cells text jamming). + ok(!isAccessible("td_nothing", nsIAccessibleTableCell), + "inherited presentation role takes a place"); + + for (let idx = 0; idx < globalIds.length; idx++) { + ok(isAccessible("td_" + globalIds[idx]), + "Inherited presentation role must be ignored becuase of " + + "aria-" + globalIds[idx] + " presence"); + } + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Update universal ARIA attribute support to latest spec" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=551978"> + Mozilla Bug 551978 + </a> + <a target="_blank" + title="Presentational table related elements referred or having global ARIA attributes must be accessible" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=809751"> + Mozilla Bug 809751 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- Test that global aria states and properties are enough to cause the + creation of accessible objects --> + <div id="global_aria_states_and_props" role="group"> + <span id="pawn"></span> + <span id="atomic" aria-atomic="true"></span> + <span id="busy" aria-busy="false"></span> + <span id="controls" aria-controls="pawn"></span> + <span id="describedby" aria-describedby="pawn"></span> + <span id="description" aria-description="hi"></span> + <span id="disabled" aria-disabled="true"></span> + <span id="dropeffect" aria-dropeffect="move"></span> + <span id="flowto" aria-flowto="pawn"></span> + <span id="grabbed" aria-grabbed="false"></span> + <span id="haspopup" aria-haspopup="false"></span> + <span id="invalid" aria-invalid="false"></span> + <span id="label" aria-label="hi"></span> + <span id="labelledby" aria-labelledby="label"></span> + <span id="live" aria-live="polite"></span> + <span id="owns" aria-owns="pawn"></span> + <span id="relevant" aria-relevant="additions"></span> + </div> + + <table role="presentation"> + <tr> + <td id="td_nothing"></td> + <td id="td_atomic" aria-atomic="true"></td> + <td id="td_busy" aria-busy="false"></td> + <td id="td_controls" aria-controls="pawn"></td> + <td id="td_describedby" aria-describedby="pawn"></td> + <td id="td_description" aria-description="hi"></td> + <td id="td_disabled" aria-disabled="true"></td> + <td id="td_dropeffect" aria-dropeffect="move"></td> + <td id="td_flowto" aria-flowto="pawn"></td> + <td id="td_grabbed" aria-grabbed="false"></td> + <td id="td_haspopup" aria-haspopup="false"></td> + <td id="td_invalid" aria-invalid="false"></td> + <td id="td_label" aria-label="hi"></td> + <td id="td_labelledby" aria-labelledby="label"></td> + <td id="td_live" aria-live="polite"></td> + <td id="td_owns" aria-owns="pawn"></td> + <td id="td_relevant" aria-relevant="additions"></td> + </tr> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_aria_grid.html b/accessible/tests/mochitest/tree/test_aria_grid.html new file mode 100644 index 0000000000..80ff97095b --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_grid.html @@ -0,0 +1,318 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML table tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // grid having rowgroups + + var accTree = + { TABLE: [ + { GROUPING: [ + { ROW: [ + { GRID_CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }, + ] }; + + testAccessibleTree("grid", accTree); + + // //////////////////////////////////////////////////////////////////////// + // strange grids (mix of ARIA and HTML tables) + + accTree = { + role: ROLE_TABLE, + children: [ + { // div@role="row" + role: ROLE_ROW, + tagName: "DIV", + children: [ + { // caption text leaf + role: ROLE_TEXT_LEAF, + name: "caption", + children: [ ], + }, + { // th generic accessible + role: ROLE_TEXT_CONTAINER, + children: [ + { // th text leaf + role: ROLE_TEXT_LEAF, + name: "header1", + children: [ ], + }, + ], + }, + { // td@role="columnheader" + role: ROLE_COLUMNHEADER, + name: "header2", + children: [ { TEXT_LEAF: [ ] } ], + }, + ], + }, + ], + }; + testAccessibleTree("strange_grid1", accTree); + + accTree = { + role: ROLE_TABLE, + children: [ + { // tr@role="row" + role: ROLE_ROW, + tagName: "TR", + children: [ + { // td implicit role="gridcell" + role: ROLE_GRID_CELL, + children: [ + { // td text leaf + role: ROLE_TEXT_LEAF, + name: "cell1", + children: [ ], + }, + ], + }, + { // td@role="gridcell" + role: ROLE_GRID_CELL, + name: "cell2", + children: [ { TEXT_LEAF: [ ] } ], + }, + ], + }, + ], + }; + testAccessibleTree("strange_grid2", accTree); + + accTree = { + role: ROLE_TABLE, + children: [ + { // div@role="row" + role: ROLE_ROW, + children: [ + { // div@role="gridcell" + role: ROLE_GRID_CELL, + children: [ + { // td generic accessible + role: ROLE_TEXT_CONTAINER, + children: [ + { // text leaf from presentational table + role: ROLE_TEXT_LEAF, + name: "cell3", + children: [ ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + testAccessibleTree("strange_grid3", accTree); + + accTree = { + role: ROLE_TABLE, + children: [ + { // div@role="row" + role: ROLE_ROW, + children: [ + { // div@role="gridcell" + role: ROLE_GRID_CELL, + children: [ + { // table + role: ROLE_TABLE, + children: [ + { // tr + role: ROLE_ROW, + children: [ + { // td + role: ROLE_CELL, + children: [ + { // caption text leaf of presentational table + role: ROLE_TEXT_LEAF, + name: "caption", + children: [ ], + }, + { // td generic accessible + role: ROLE_TEXT_CONTAINER, + children: [ + { // td text leaf of presentational table + role: ROLE_TEXT_LEAF, + name: "cell4", + children: [ ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + + testAccessibleTree("strange_grid4", accTree); + + // //////////////////////////////////////////////////////////////////////// + // grids that could contain whitespace accessibles but shouldn't. + + accTree = + { TREE_TABLE: [ + { ROW: [ + { GRID_CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + { GRID_CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + { GRID_CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + + testAccessibleTree("whitespaces-grid", accTree); + + // grids that could contain text container accessibles but shouldn't. + + accTree = + { TABLE: [ + { ROW: [ + { GRID_CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + { GRID_CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + { ROW: [ + { GRID_CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + { GRID_CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + + testAccessibleTree("gridWithPresentationalBlockElement", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Support ARIA role rowgroup" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=525909"> + Mozilla Bug 525909 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="grid" role="grid"> + <div role="rowgroup"> + <div role="row"> + <div role="gridcell">cell</div> + </div> + </div> + </div> + + <div id="strange_grid1" role="grid"> + <div role="row"> + <table role="presentation"> + <caption>caption</caption> + <tr> + <th>header1</th> + <td role="columnheader">header2</td> + </tr> + </table> + </div> + </div> + + <div id="strange_grid2" role="grid"> + <table role="presentation"> + <tr role="row"> + <td id="implicit_gridcell">cell1</td> + <td role="gridcell">cell2</td> + </tr> + </table> + </div> + + <div id="strange_grid3" role="grid"> + <div role="row"> + <div role="gridcell"> + <table role="presentation"> + <tr> + <td>cell3</td> + </tr> + </table> + </div> + </div> + </div> + + <div id="strange_grid4" role="grid"> + <div role="row"> + <div role="gridcell"> + <table> + <tr> + <td> + <table role="presentation"> + <caption>caption</caption> + <tr><td>cell4</td></tr> + </table> + </td> + </tr> + </table> + </div> + </div> + </div> + + <div role="treegrid" id="whitespaces-grid"> + <div role="row" aria-selected="false" tabindex="-1"> + <span role="gridcell">03:30PM-04:30PM</span> + <span role="gridcell" style="font-weight:bold;">test</span> + <span role="gridcell">a user1</span> + </div> + </div> + + <div id="gridWithPresentationalBlockElement" role="grid"> + <span style="display: block;"> + <div role="row"> + <div role="gridcell">Cell 1</div> + <div role="gridcell">Cell 2</div> + </div> + </span> + <span style="display: block;"> + <div role="row"> + <span style="display: block;"> + <div role="gridcell">Cell 3</div> + <div role="gridcell">Cell 4</div> + </span> + </div> + </span> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_aria_imgmap.html b/accessible/tests/mochitest/tree/test_aria_imgmap.html new file mode 100644 index 0000000000..0cd4867cae --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_imgmap.html @@ -0,0 +1,104 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test usemap elements and ARIA</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + // gA11yEventDumpToConsole = true; + function doPreTest() { + waitForImageMap("imagemap", doTest); + } + + function doTest() { + var accTree = { + role: ROLE_IMAGE_MAP, + children: [ + { + role: ROLE_ENTRY, + name: "first name", + }, + { + role: ROLE_ENTRY, + name: "last name", + }, + { + role: ROLE_RADIOBUTTON, + name: "male", + }, + { + role: ROLE_RADIOBUTTON, + name: "female", + }, + { + role: ROLE_CHECKBUTTON, + name: "have bike", + }, + { + role: ROLE_EDITCOMBOBOX, + name: "bike model", + }, + { + role: ROLE_CHECKBUTTON, + name: "have car", + }, + { + role: ROLE_CHECKBUTTON, + name: "have airplane", + }, + { + role: ROLE_PUSHBUTTON, + name: "submit", + }, + ], + }; + + // Test image map tree structure, roles, and names. + testAccessibleTree("imagemap", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doPreTest); + </script> + +</head> +<body> + +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=548291" + title="Accessible tree of ARIA image maps"> +Mozilla Bug 548291 +</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +</pre> + +<img id="imagemap" src="../formimage.png" width="219" height="229" border="0" usemap="#ariaMap"> +<map id="ariaMap" name="ariaMap"> + <area id="t1" role="textbox" shape="rect" tabindex="0" alt="" title="first name" coords="4,20,108,48" href="#" /> + <area id="t2" role="textbox" shape="rect" alt="" title="last name" coords="111,21,215,50" href="#" /> + <area id="rb1" role="radio" aria-checked="true" shape="circle" alt="" title="male" coords="60,75,11" href="#" /> + <area id="rb2" role="radio" shape="circle" alt="" title="female" coords="73,94,11" href="#" /> + <area id="cb1" role="checkbox" aria-checked="true" shape="rect" alt="" title="have bike" coords="95,123,118,145" href="#" /> + <area id="cbox" role="combobox" shape="rect" alt="" title="bike model" coords="120,124,184,146" href="#" /> + <area id="cb2" role="checkbox" shape="rect" alt="" title="have car" coords="90,145,114,164" href="#" /> + <area id="cb3" role="checkbox" shape="rect" alt="" title="have airplane" coords="130,163,152,184" href="#" /> + <area id="b1" role="button" shape="rect" alt="" title="submit" coords="4,198,67,224" href="#" /> +</map> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_aria_list.html b/accessible/tests/mochitest/tree/test_aria_list.html new file mode 100644 index 0000000000..f3642c801d --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_list.html @@ -0,0 +1,90 @@ +<!DOCTYPE html> +<html> +<head> + <title>ARIA lists</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // list + + var accTree = + { LIST: [ + { LISTITEM: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }; + + testAccessibleTree("list", accTree); + + // //////////////////////////////////////////////////////////////////////// + // strange list (mix of ARIA and HTML) + + accTree = { // div@role="list" + role: ROLE_LIST, + children: [ + { // li + role: ROLE_TEXT_CONTAINER, + children: [ + { // li text leaf + role: ROLE_TEXT_LEAF, + name: "item1", + children: [ ], + }, + ], + }, + { // li@role="listitem" + role: ROLE_LISTITEM, + children: [ + { // text leaf + role: ROLE_TEXT_LEAF, + name: "item2", + children: [ ], + }, + ], + }, + ], + }; + + testAccessibleTree("strange_list", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Build the context dependent tree" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=804461"> + Mozilla Bug 804461 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="list" role="list"> + <div role="listitem">item1</div> + </div> + + <div id="strange_list" role="list"> + <ul role="presentation"> + <li>item1</li> + <li role="listitem">item2</li> + </ul> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_aria_menu.html b/accessible/tests/mochitest/tree/test_aria_menu.html new file mode 100644 index 0000000000..2f1f6645db --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_menu.html @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test accessible tree when ARIA role menuitem is used</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // Menuitem with no popup. + let tree = + { SECTION: [ // container + { MENUPOPUP: [ // menu + { MENUITEM: [ + { LISTITEM_MARKER: [] }, // bullet + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }; + testAccessibleTree("menu", tree); + + // Menuitem with explicit no popup. + tree = + { SECTION: [ // container + { MENUPOPUP: [ // menu + { MENUITEM: [ + { LISTITEM_MARKER: [] }, // bullet + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }; + testAccessibleTree("menu_nopopup", tree); + + // Menuitem with popup. + tree = + { SECTION: [ // container + { MENUPOPUP: [ // menu + { PARENT_MENUITEM: [ // menuitem with aria-haspopup="true" + { LISTITEM_MARKER: [] }, // bullet + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }; + testAccessibleTree("menu_popup", tree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=786566" + title="ARIA menuitem acting as submenu should have PARENT_MENUITEM role"> + Mozilla Bug 786566 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="menu"> + <ul role="menu"> + <li role="menuitem">Normal Menu</li> + </ul> + </div> + + <div id="menu_nopopup"> + <ul role="menu"> + <li role="menuitem" aria-haspopup="false">Menu with explicit no popup</li> + </ul> + </div> + + <div id="menu_popup"> + <ul role="menu"> + <li role="menuitem" aria-haspopup="true">Menu with popup</li> + </ul> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_aria_owns.html b/accessible/tests/mochitest/tree/test_aria_owns.html new file mode 100644 index 0000000000..a01968521b --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_owns.html @@ -0,0 +1,197 @@ +<!DOCTYPE html> +<html> + +<head> + <title>@aria-owns attribute testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Tests + // ////////////////////////////////////////////////////////////////////////// + + // enableLogging("tree,verbose"); // debug stuff + + var gQueue = null; + + function doTest() { + var tree = + { SECTION: [ // t1_1 + { HEADING: [ // t1_2 + // no kids, no loop + ] }, + ] }; + testAccessibleTree("t1_1", tree); + + tree = + { SECTION: [ // t2_1 + { GROUPING: [ // t2_2 + { HEADING: [ // t2_3 + // no kids, no loop + ] }, + ] }, + ] }; + testAccessibleTree("t2_1", tree); + + tree = + { SECTION: [ // t3_3 + { GROUPING: [ // t3_1 + { NOTE: [ // t3_2 + { HEADING: [ // DOM child of t3_2 + // no kids, no loop + ] }, + ] }, + ] }, + ] }; + testAccessibleTree("t3_3", tree); + + tree = + { SECTION: [ // t4_1 + { GROUPING: [ // DOM child of t4_1, aria-owns ignored + // no kids, no loop + ] }, + ] }; + testAccessibleTree("t4_1", tree); + + tree = + { SECTION: [ // t5_1 + { GROUPING: [ // DOM child of t5_1 + { NOTE: [ // t5_2 + { HEADING: [ // DOM child of t5_2 + { FORM: [ // t5_3 + { TOOLTIP: [ // DOM child of t5_3 + // no kids, no loop + ]}, + ]}, + ]}, + ] }, + ] }, + ] }; + testAccessibleTree("t5_1", tree); + + tree = + { SECTION: [ // t6_1 + { RADIOBUTTON: [ ] }, + { CHECKBUTTON: [ ] }, // t6_3, rearranged by aria-owns + { PUSHBUTTON: [ ] }, // t6_2, rearranged by aria-owns + ] }; + testAccessibleTree("t6_1", tree); + + tree = + { SECTION: [ // ariaowns_container + { SECTION: [ // ariaowns_self + { SECTION: [ // ariaowns_uncle + ] }, + ] }, + ] }; + testAccessibleTree("ariaowns_container", tree); + + tree = + { TABLE: [ + { ROW: [ + { GRID_CELL: [ + { TEXT_LEAF: [] }, + ] }, + { GRID_CELL: [ + { TEXT_LEAF: [] }, + ] }, + ] }, + { ROW: [ + { GRID_CELL: [ + { TEXT_LEAF: [] }, + ] }, + { GRID_CELL: [ + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }; + testAccessibleTree("grid", tree); + + tree = + { SECTION: [ // presentation_owner + // Can't own ancestor, so no children. + ] }; + testAccessibleTree("presentation_owner", tree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + + </script> +</head> + +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- simple loop --> + <div id="t1_1" aria-owns="t1_2"></div> + <div id="t1_2" aria-owns="t1_1" role="heading"></div> + + <!-- loop --> + <div id="t2_2" aria-owns="t2_3" role="group"></div> + <div id="t2_1" aria-owns="t2_2"></div> + <div id="t2_3" aria-owns="t2_1" role="heading"></div> + + <!-- loop #2 --> + <div id="t3_1" aria-owns="t3_2" role="group"></div> + <div id="t3_2" role="note"> + <div aria-owns="t3_3" role="heading"></div> + </div> + <div id="t3_3" aria-owns="t3_1"></div> + + <!-- self loop --> + <div id="t4_1"><div aria-owns="t4_1" role="group"></div></div> + + <!-- natural and aria-owns hierarchy --> + <div id="t5_2" role="note"><div aria-owns="t5_3" role="heading"></div></div> + <div id="t5_1"><div aria-owns="t5_2" role="group"></div></div> + <div id="t5_3" role="form"><div aria-owns="t5_1" role="tooltip"></div></div> + + <!-- rearrange children --> + <div id="t6_1" aria-owns="t6_3 t6_2"> + <div id="t6_2" role="button"></div> + <div id="t6_3" role="checkbox"></div> + <div role="radio"></div> + </div> + + <div id="ariaowns_container"> + <div id="ariaowns_self" + aria-owns="aria_ownscontainer ariaowns_self ariaowns_uncle"></div> + </div> + <div id="ariaowns_uncle"></div> + + <!-- grid --> + <div aria-owns="grid-row2" role="grid" id="grid"> + <div role="row"> + <div role="gridcell">cell 1,1</div> + <div role="gridcell">cell 1,2</div> + </div> + </div> + <div role="row" id="grid-row2"> + <div role="gridcell">cell 2,1</div> + <div role="gridcell">cell 2,2</div> + </div> + + <!-- Owned child which is an ancestor of its owner but didn't yet exist when + aria-owns relocation was processed (bug 1485097). --> + <div id="presentation" role="presentation"> + <div id="presentation_owner" aria-owns="presentation"></div> + </div> +</body> + +</html> diff --git a/accessible/tests/mochitest/tree/test_aria_presentation.html b/accessible/tests/mochitest/tree/test_aria_presentation.html new file mode 100644 index 0000000000..5680193441 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_presentation.html @@ -0,0 +1,176 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test accessible tree when ARIA role presentation is used</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // Presentation role don't allow accessible. + var tree = + { SECTION: [ // container + { TEXT_LEAF: [ ] }, // child text of 'presentation' node + { TEXT_LEAF: [ ] }, // child text of 'none' node + ] }; + testAccessibleTree("div_cnt", tree); + + // Focusable element, 'presentation' and 'none' roles are ignored. + tree = + { SECTION: [ // container + { PUSHBUTTON: [ // button having 'presentation' role + { TEXT_LEAF: [ ] }, + ] }, + { PUSHBUTTON: [ // button having 'none' role + { TEXT_LEAF: [ ] }, + ] }, + ] }; + testAccessibleTree("btn_cnt", tree); + + // Presentation table, no table structure is exposed. + tree = + { SECTION: [ // container + { TEXT_CONTAINER: [ // td generic accessible inside 'presentation' table + { TEXT_LEAF: [ ] }, // cell text + ] }, + { TEXT_CONTAINER: [ // td generic accessible inside 'none' table + { TEXT_LEAF: [ ] }, // cell text + ] }, + ] }; + testAccessibleTree("tbl_cnt", tree); + + // Focusable table, 'presentation' and 'none' roles are ignored. + tree = + { SECTION: [ // container + { TABLE: [ // table having 'presentation' role + { ROW: [ // tr + { CELL: [ // td + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }, + { TABLE: [ // table having 'none' role + { ROW: [ // tr + { CELL: [ // td + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }, + ] }; + testAccessibleTree("tblfocusable_cnt", tree); + + // Presentation list, expose generic accesisble for list items. + tree = + { SECTION: [ // container + { TEXT_CONTAINER: [ // li generic accessible inside 'presentation' role + { TEXT_LEAF: [ ] }, // li text + ] }, + { TEXT_CONTAINER: [ // li generic accessible inside 'none' role + { TEXT_LEAF: [ ] }, // li text + ] }, + ] }; + testAccessibleTree("list_cnt", tree); + + // Has ARIA globals or referred by ARIA relationship, role='presentation' + // and role='none' are ignored. + tree = + { SECTION: [ // container + { LABEL: [ // label, has aria-owns + { TEXT_LEAF: [ ] }, + { LABEL: [ // label, referenced by aria-owns + { TEXT_LEAF: [ ] }, + ] }, + ] }, + { LABEL: [ // label, has aria-owns + { TEXT_LEAF: [ ] }, + { LABEL: [ // label, referenced by aria-owns + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + testAccessibleTree("airaglobalprop_cnt", tree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=548291" + title="Accessible tree of ARIA image maps"> + Bug 548291 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=666504" + title="Ignore role presentation on focusable elements"> + Bug 666504 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=971212" + title="Implement ARIA role=none"> + Bug 971212 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="div_cnt"><div role="presentation">t</div><div role="none">t</div></div> + + <div id="btn_cnt"><button role="presentation">btn</button><button role="none">btn</button></div> + + <div id="tbl_cnt"> + <table role="presentation"> + <tr> + <td>cell</td> + </tr> + </table> + <table role="none"> + <tr> + <td>cell</td> + </tr> + </table> + </div> + + <div id="tblfocusable_cnt"> + <table role="presentation" tabindex="0"> + <tr> + <td>cell</td> + </tr> + </table> + <table role="none" tabindex="0"> + <tr> + <td>cell</td> + </tr> + </table> + </div> + + <div id="list_cnt"> + <ul role="presentation"> + <li>item</li> + </ul> + <ul role="none"> + <li>item</li> + </ul> + </div> + + <div id="airaglobalprop_cnt"><label + role="presentation" aria-owns="ariaowned">has aria-owns</label><label + role="presentation" id="ariaowned">referred by aria-owns</label><label + role="none" aria-owns="ariaowned2">has aria-owns</label><label + role="none" id="ariaowned2">referred by aria-owns</label></div> + +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_aria_table.html b/accessible/tests/mochitest/tree/test_aria_table.html new file mode 100644 index 0000000000..22375faf59 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_table.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<html> +<head> + <title>ARIA table tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // table having rowgroups + + var accTree = + { TABLE: [ + { GROUPING: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }, + ] }; + + testAccessibleTree("table", accTree); + + // tables that could contain text container accessibles but shouldn't. + + accTree = + { TABLE: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + + testAccessibleTree("tableWithPresentationalBlockElement", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="support ARIA table and cell roles" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173364"> + Bug 1173364 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="table" role="table"> + <div role="rowgroup"> + <div role="row"> + <div role="cell">cell</div> + </div> + </div> + </div> + + <div id="tableWithPresentationalBlockElement" role="table"> + <span style="display: block;"> + <div role="row"> + <div role="cell">Cell 1</div> + <div role="cell">Cell 2</div> + </div> + </span> + <span style="display: block;"> + <div role="row"> + <span style="display: block;"> + <div role="cell">Cell 3</div> + <div role="cell">Cell 4</div> + </span> + </div> + </span> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_brokencontext.html b/accessible/tests/mochitest/tree/test_brokencontext.html new file mode 100644 index 0000000000..fbdefd398f --- /dev/null +++ b/accessible/tests/mochitest/tree/test_brokencontext.html @@ -0,0 +1,214 @@ +<!DOCTYPE html> +<html> +<head> + <title>Broken context hierarchy</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + /** + * Return true if TD element has a generic accessible. + */ + function isTDGeneric(aID) { + return isAccessible(aID) && !isAccessible(aID, nsIAccessibleTableCell); + } + + function checkIfNotAccessible(aID) { + ok(!isAccessible(aID), "'" + aID + "' shouldn't be accessible"); + } + function checkIfTDGeneric(aID) { + ok(isTDGeneric(aID), "'" + aID + "' shouldn't have cell accessible"); + } + + function doTest() { + // ////////////////////////////////////////////////////////////////////////// + // HTML table elements outside table context. + + // HTML table role="presentation" + checkIfNotAccessible("tr_in_presentation_table"); + checkIfTDGeneric("th_in_presentation_table"); + checkIfTDGeneric("td_in_presentation_table"); + + // HTML table role="button" + var tree = + { PUSHBUTTON: [ // table + { TEXT_CONTAINER: [ // tr + { TEXT_CONTAINER: [ // th + { TEXT_LEAF: [ ] }, + ] }, + { TEXT_CONTAINER: [ // td + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + testAccessibleTree("button_table", tree); + + // ////////////////////////////////////////////////////////////////////////// + // HTML list elements outside list context. + + ok(!isAccessible("presentation_ul"), + "presentational ul shouldn't be accessible"); + ok(isAccessible("item_in_presentation_ul"), + "li in presentational ul should have generic accessible"); + ok(isAccessible("styleditem_in_presentation_ul"), + "list styled span in presentational ul should have generic accessible"); + + ok(!isAccessible("presentation_ol"), + "presentational ol shouldn't be accessible"); + ok(isAccessible("item_in_presentation_ol"), + "li in presentational ol should have generic accessible"); + + ok(!isAccessible("presentation_dl"), + "presentational dl shouldn't be accessible"); + ok(!isAccessible("dt_in_presentation_dl"), + "dt in presentational dl shouldn't be accessible"); + ok(!isAccessible("dd_in_presentation_dl"), + "dd in presentational dl shouldn't be accessible"); + + tree = + { PUSHBUTTON: [ // ul + { TEXT_CONTAINER: [ // li + { LISTITEM_MARKER: [ ] }, + { TEXT_LEAF: [ ] }, + ] }, + { TEXT_CONTAINER: [ // span styled as a list + { LISTITEM_MARKER: [ ] }, + { TEXT_LEAF: [ ] }, + ] }, + ] }; + testAccessibleTree("button_ul", tree); + + tree = + { PUSHBUTTON: [ // ol + { TEXT_CONTAINER: [ // li + { LISTITEM_MARKER: [ ] }, + { TEXT_LEAF: [ ] }, + ] }, + ] }; + testAccessibleTree("button_ol", tree); + + tree = + { PUSHBUTTON: [ // dl + { TEXT_CONTAINER: [ // dt + { TEXT_LEAF: [ ] }, + ] }, + { TEXT_CONTAINER: [ // dd + { TEXT_LEAF: [ ] }, + ] }, + ] }; + testAccessibleTree("button_dl", tree); + + // ////////////////////////////////////////////////////////////////////////// + // Styled as HTML table elements, accessible is created by tag name + + tree = + { LINK: [ // a + { TEXT_LEAF: [ ] }, + ] }; + testAccessibleTree("a_as_td", tree); + + tree = + { HEADING: [ + { TEXT_LEAF: [ ] }, + ] }; + testAccessibleTree("h1_as_td", tree); + testAccessibleTree("h2_as_td", tree); + testAccessibleTree("h3_as_td", tree); + testAccessibleTree("h4_as_td", tree); + testAccessibleTree("h5_as_td", tree); + testAccessibleTree("h6_as_td", tree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=706849" + title="Create accessible by tag name as fallback if table descendant style is used out of table context"> + Bug 706849 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=804461" + title="Build the context dependent tree "> + Bug 804461 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=945435" + title="Create generic accessible for td to not jamm the cell text"> + Bug 945435 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- HTML table elements out of table --> + <table role="presentation"> + <tr id="tr_in_presentation_table"> + <th id="th_in_presentation_table">not a header</th> + <td id="td_in_presentation_table">not a cell</td> + </tr> + </table> + + <table role="button" id="button_table"> + <tr id="tr_in_button_table"> + <th id="th_in_button_table">not a header</th> + <td id="td_in_button_table">not a cell</td> + </tr> + </table> + + <!-- HTML list elements out of list --> + <ul role="presentation" id="presentation_ul"> + <li id="item_in_presentation_ul">item</li> + <span id="styleditem_in_presentation_ul" + style="display:list-item">Oranges</span> + </ul> + + <ol role="presentation" id="presentation_ol"> + <li id="item_in_presentation_ol">item</li> + </ol> + + <dl role="presentation" id="presentation_dl"> + <dt id="dt_in_presentation_dl">term</dt> + <dd id="dd_in_presentation_dl">definition</dd> + </dl> + + <ul role="button" id="button_ul"> + <li id="item_in_button_ul">item</li> + <span id="styleditem_in_button_ul" + style="display:list-item">Oranges</span> + </ul> + + <ol role="button" id="button_ol"> + <li id="item_in_button_ul">item</li> + </ol> + + <dl role="button" id="button_dl"> + <dt id="dt_in_button_dl">term</ld> + <dd id="dd_in_button_dl">definition</dd> + </dl> + + <!-- styled as HTML table elements --> + <a id="a_as_td" style="display:table-cell;" href="http://www.google.com">Google</a> + <h1 id="h1_as_td" style="display: table-cell;">h1</h1> + <h2 id="h2_as_td" style="display: table-cell;">h2</h2> + <h3 id="h3_as_td" style="display: table-cell;">h3</h3> + <h4 id="h4_as_td" style="display: table-cell;">h4</h4> + <h5 id="h5_as_td" style="display: table-cell;">h5</h5> + <h6 id="h6_as_td" style="display: table-cell;">h6</h6> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_button.xhtml b/accessible/tests/mochitest/tree/test_button.xhtml new file mode 100644 index 0000000000..fec453b717 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_button.xhtml @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL button hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // button + + var accTree = { + role: ROLE_PUSHBUTTON, + name: "hello", + children: [ ] + }; + testAccessibleTree("button1", accTree); + + ////////////////////////////////////////////////////////////////////////// + // toolbarbutton + + accTree = { + role: ROLE_PUSHBUTTON, + name: "hello", + children: [ ] + }; + testAccessibleTree("button2", accTree); + + ////////////////////////////////////////////////////////////////////////// + // toolbarbutton with type="checkbox" + + accTree = { + role: ROLE_TOGGLE_BUTTON, + name: "hello", + children: [ ] + }; + testAccessibleTree("button3", accTree); + + SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292" + title="Ensure accessible children for toolbarbutton types 'menu'"> + Mozilla Bug 249292 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <button id="button1" label="hello"/> + <toolbarbutton id="button2" label="hello"/> + <toolbarbutton id="button3" type="checkbox" label="hello"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/tree/test_canvas.html b/accessible/tests/mochitest/tree/test_canvas.html new file mode 100644 index 0000000000..804bcc1f6e --- /dev/null +++ b/accessible/tests/mochitest/tree/test_canvas.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=495912 +--> +<head> + <title>File Input Control tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + var accTree = + { CANVAS: [ + { CHECKBUTTON: [] }, + { ENTRY: [] }, + ] }; + + testAccessibleTree("canvas", accTree); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Expose alternative content in Canvas element to ATs" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912">Mozilla Bug 495912</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <canvas id="canvas" tabindex="0"><input type="checkbox"><input></canvas> + + <script type="text/javascript"> + var c = document.getElementById("canvas"); + var cxt = c.getContext("2d"); + cxt.fillStyle = "#005500"; + cxt.fillRect(0, 0, 150, 75); + </script> + +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_combobox.xhtml b/accessible/tests/mochitest/tree/test_combobox.xhtml new file mode 100644 index 0000000000..36e4e716c9 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_combobox.xhtml @@ -0,0 +1,116 @@ +<?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" + xmlns:html="http://www.w3.org/1999/xhtml" + title="Accessible XUL menulist and textbox @autocomplete hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // menulist + + var selectedOptionChildren = []; + if (MAC) { + // checkmark is part of the Mac menu styling + selectedOptionChildren = [{ + role: ROLE_STATICTEXT, + children: [] + }]; + } + + var accTree = { + role: ROLE_COMBOBOX, + children: [ + { + role: ROLE_COMBOBOX_LIST, + children: [ + { + role: ROLE_COMBOBOX_OPTION, + children: selectedOptionChildren + }, + { + role: ROLE_COMBOBOX_OPTION, + children: [] + } + ] + } + ] + }; + + testAccessibleTree("menulist", accTree); + + ////////////////////////////////////////////////////////////////////////// + // textbox@type=autocomplete #1 (history) + + accTree = { + // html:input + role: ROLE_ENTRY, + children: [ + { + // #text + role: ROLE_TEXT_LEAF, + name: "http://mochi.test:8888/redirect-a11y.html", + children: [] + } + ], + }; + + testAccessibleTree("autocomplete", accTree); + + SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292" + title="Ensure accessible children for toolbarbutton types 'menu'"> + Mozilla Bug 249292 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660" + title="Cache rendered text on a11y side"> + Mozilla Bug 626660 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <menulist id="menulist"> + <menupopup> + <menuitem label="item"/> + <menuitem label="item"/> + </menupopup> + </menulist> + + <html:input is="autocomplete-input" + id="autocomplete" + value="http://mochi.test:8888/redirect-a11y.html"/> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/tree/test_cssflexbox.html b/accessible/tests/mochitest/tree/test_cssflexbox.html new file mode 100644 index 0000000000..72fafba0a2 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_cssflexbox.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<html> + +<head> + <title>CSS flexbox tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // Ensure that flexbox ordering and absolute positioning do not affect + // the accessibility tree. + // Note that there is no accessible for a div with display:flex style. + var accTree = { + role: ROLE_SECTION, + children: [ + { // Bug 1277559. Button outside the flexed content + role: ROLE_PUSHBUTTON, + name: "Button", + }, + { // Visually first button in the 3 button row + role: ROLE_PUSHBUTTON, + name: "First", + }, + { // Flushed right third button in the 3 button row + role: ROLE_PUSHBUTTON, + name: "Second", + }, + { // Middle button in the 3 button row + role: ROLE_PUSHBUTTON, + name: "Third", + }, // end bug 1277559 + { // Bug 962558: DOM first, Order 2. + role: ROLE_PUSHBUTTON, + name: "two, tab first", + }, + { // DOM order second, flex order 1 + role: ROLE_PUSHBUTTON, + name: "one, tab second", + }, // end bug 962558 + ], + }; + testAccessibleTree("flex_elements", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="flex_elements"> + <button type="button">Button</button> + <div style="position: relative; display: flex; width: 200px;"> + <button type="button" style="order: 1">First</button> + <button type="button" style="order: 2; position: absolute; right: 0">Second</button> + <button type="button" style="order: 3">Third</button> + </div> + <div style="display: flex"> + <button id="two" style="order: 2">two, tab first</button> + <button id="one" style="order: 1">one, tab second</button> + </div> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_cssoverflow.html b/accessible/tests/mochitest/tree/test_cssoverflow.html new file mode 100644 index 0000000000..87898fef6c --- /dev/null +++ b/accessible/tests/mochitest/tree/test_cssoverflow.html @@ -0,0 +1,135 @@ +<html> + +<head> + <title>CSS overflow testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style> + a.link:focus { + overflow: scroll; + } + </style> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function focusAnchor(aID) { + this.linkNode = getNode(aID); + this.link = getAccessible(this.linkNode); + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getAccessible, this.linkNode), + ]; + + this.invoke = function focusAnchor_invoke() { + this.linkNode.focus(); + }; + + this.check = function focusAnchor_check(aEvent) { + is(this.link, aEvent.accessible, + "Focus should be fired against new link accessible!"); + }; + + this.getID = function focusAnchor_getID() { + return "focus a:focus{overflow:scroll} #1"; + }; + } + + function tabAnchor(aID) { + this.linkNode = getNode(aID); + this.link = getAccessible(this.linkNode); + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getAccessible, this.linkNode), + ]; + + this.invoke = function tabAnchor_invoke() { + synthesizeKey("VK_TAB", { shiftKey: false }); + }; + + this.check = function tabAnchor_check(aEvent) { + is(this.link, aEvent.accessible, + "Focus should be fired against new link accessible!"); + }; + + this.getID = function tabAnchor_getID() { + return "focus a:focus{overflow:scroll} #2"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + function doTests() { + // Shift+Tab not working, and a test timeout, bug 746977 + if (MAC) { + todo(false, "Shift+tab isn't working on OS X, needs to be disabled until bug 746977 is fixed!"); + SimpleTest.finish(); + return; + } + + gQueue = new eventQueue(); + + // CSS 'overflow: scroll' property setting and unsetting causes accessible + // recreation (and fire show/hide events). For example, the focus and + // blur of HTML:a with ':focus {overflow: scroll; }' CSS style causes its + // accessible recreation. The focus event should be fired on new + // accessible. + gQueue.push(new focusAnchor("a")); + gQueue.push(new tabAnchor("a2")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=591163" + title="mochitest for bug 413777: focus the a:focus {overflow: scroll;} shouldn't recreate HTML a accessible"> + Mozilla Bug 591163 + </a><br> + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275 + </a><br> + <a target="_blank" + title="Text control frames should accept dynamic changes to the CSS overflow property" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=686247"> + Mozilla Bug 686247 + </a><br> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <div> + <a id="a" class="link" href="www">link</a> + </div> + <div> + <a id="a2" class="link" href="www">link2</a> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_display_contents.html b/accessible/tests/mochitest/tree/test_display_contents.html new file mode 100644 index 0000000000..8393a35b41 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_display_contents.html @@ -0,0 +1,92 @@ +<!DOCTYPE html> +<html> + +<head> +<title>CSS display:contents tests</title> +<link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script type="application/javascript" + src="../common.js"></script> +<script type="application/javascript" + src="../role.js"></script> + +<script type="application/javascript"> +function doTest() { + let tree = + { LIST: [ + { LISTITEM: [ + { LISTITEM_MARKER: [] }, + { TEXT_LEAF: [] }, + ]}, + { LISTITEM: [ + { LISTITEM_MARKER: [] }, + { TEXT_LEAF: [] }, + ]}, + ] }; + testAccessibleTree("ul", tree); + + tree = + { TABLE: [ + { ROW: [ + { CELL: [{ TEXT_LEAF: [] } ] }, + { CELL: [{ TEXT_LEAF: [] } ] }, + ]}, + ] }; + testAccessibleTree("tableTableContents", tree); + testAccessibleTree("tableTrContents", tree); + testAccessibleTree("tableTdContents", tree); + + tree = + { TABLE: [ + { GROUPING : [ + { ROW: [ + { CELL: [{ TEXT_LEAF: [] } ] }, + { CELL: [{ TEXT_LEAF: [] } ] }, + ]}, + ]}, + ] }; + testAccessibleTree("tableTbodyContents", tree); + + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addA11yLoadEvent(doTest); +</script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <ul id="ul" style="display: contents;"> + <li>Supermarket 1</li> + <li>Supermarket 2</li> + </ul> + + <!-- The summary attribute in these tables ensures they are treated as data + tables. --> + <table id="tableTableContents" summary="summary" style="display: contents;"> + <tr><td>a</td><td>b</td></tr> + </table> + <table id="tableTrContents" summary="table" style="display: block;"> + <tr style="display: contents;"><td>a</td><td>b</td></tr> + </table> + <table id="tableTdContents" summary="summary"> + <tr> + <td style="display: contents;">a</td> + <td style="display: contents;">b</td> + </tr> + </table> + <table id="tableTbodyContents" summary="summary" style="display: block;"> + <tbody style="display: contents;"> + <tr><td>a</td><td>b</td></tr> + </tbody> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_divs.html b/accessible/tests/mochitest/tree/test_divs.html new file mode 100644 index 0000000000..24d610aef2 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_divs.html @@ -0,0 +1,351 @@ +<!DOCTYPE html> +<html> + +<head> +<title>div element creation tests</title> +<link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script type="application/javascript" + src="../common.js"></script> +<script type="application/javascript" + src="../role.js"></script> +<script type="application/javascript" + src="../attributes.js"></script> + +<script type="application/javascript"> +function getAccessibleDescendantFor(selector) { + return gAccService.getAccessibleDescendantFor(document.querySelector(selector)); +} + +function doTest() { + // All below test cases are wrapped in a div which always gets rendered. + // c1 through c10 are the containers, the actual test cases are inside. + + // c1: Expose the div with text content + let tree = + { role: ROLE_SECTION, // outer div with ID + children: [ + { role: ROLE_SECTION, // inner div + children: [ + { TEXT_LEAF: [] }, + ], // end children inner div + }, // end inner div + ], // end children outer div + }; + testAccessibleTree("c1", tree); + + // c2: Only the outermost and innermost divs are exposed. + // The middle one is skipped. This is identical to the above tree. + testAccessibleTree("c2", tree); + + // c3: Make sure the inner div with ID is exposed, but the middle one is + // skipped. + tree = + { role: ROLE_SECTION, // outer div with ID + children: [ + { role: ROLE_SECTION, // inner div + attributes: { id: "b" }, + children: [ + { TEXT_LEAF: [] }, + ], // end children inner div + }, // end inner div + ], // end children outer div + }; + testAccessibleTree("c3", tree); + + // c4: Expose all three divs including the middle one due to its ID. + tree = + { role: ROLE_SECTION, // outer div with ID + children: [ + { role: ROLE_SECTION, // middle div + attributes: { id: "a" }, + children: [ + { role: ROLE_SECTION, // inner div + attributes: { id: "b" }, + children: [ + { TEXT_LEAF: [] }, + ], // end children inner div + }, // end inner div + ], // end children middle div + }, // end middle div + ], // end children outer div + }; + testAccessibleTree("c4", tree); + + // c5: Expose the inner div with class b due to its text contents. + tree = + { role: ROLE_SECTION, // outer div with ID + children: [ + { role: ROLE_SECTION, // inner div with class and text + attributes: { class: "b" }, + children: [ + { TEXT_LEAF: [] }, + ], // end children inner div + }, // end inner div + ], // end children outer div + }; + testAccessibleTree("c5", tree); + + // c6: Expose the outer div due to its ID, and the two inner divs due to + // their text contents. Skip the middle one. + tree = + { role: ROLE_SECTION, // outer div with ID + children: [ + { role: ROLE_SECTION, // first inner div + children: [ + { TEXT_LEAF: [] }, + ], // end children first inner div + }, // end first inner div + { role: ROLE_SECTION, // second inner div + children: [ + { TEXT_LEAF: [] }, + ], // end children second inner div + }, // end second inner div + ], // end children outer div + }; + testAccessibleTree("c6", tree); + + // c7: Expose all three divs including the middle one due to it being block + // breaking. + tree = + { role: ROLE_SECTION, // outer div with ID + children: [ + { role: ROLE_SECTION, // middle div + children: [ + { TEXT_LEAF: [] }, // foo + { role: ROLE_SECTION, // inner div + children: [ + { TEXT_LEAF: [] }, // bar + ], // end children inner div + }, // end inner div + { TEXT_LEAF: [] }, // baz + ], // end children middle div + }, // end middle div + ], // end children outer div + }; + testAccessibleTree("c7", tree); + + // c8: Expose all divs due to them all being text block breakers. + tree = + { role: ROLE_SECTION, // outer div with ID + children: [ + { role: ROLE_SECTION, // foo div + children: [ + { TEXT_LEAF: [] }, // foo + { role: ROLE_SECTION, // baz div + children: [ + { role: ROLE_SECTION, // bar div + children: [ + { TEXT_LEAF: [] }, // bar + ], // end children bar div + }, // end bar div + { TEXT_LEAF: [] }, // baz + ], // end children baz div + }, // end baz div + ], // end children foo div + }, // end foo div + ], // end children outer div + }; + testAccessibleTree("c8", tree); + + // c9: The same, but in a different nesting order. + tree = + { role: ROLE_SECTION, // outer div with ID + children: [ + { role: ROLE_SECTION, // c div + children: [ + { role: ROLE_SECTION, // b div + children: [ + { role: ROLE_SECTION, // a div + children: [ + { TEXT_LEAF: [] }, // a + ], // end children a div + }, // end a div + { TEXT_LEAF: [] }, // b + ], // end children b div + }, // end b div + { TEXT_LEAF: [] }, // c + ], // end children c div + }, // end foo div + ], // end children outer div + }; + testAccessibleTree("c9", tree); + + // c10: Both inner divs must be exposed so there is a break after b. + tree = + { role: ROLE_SECTION, // outer div with ID + children: [ + { role: ROLE_SECTION, // first inner div + children: [ + { TEXT_LEAF: [] }, // a + { TEXT_LEAF: [] }, // b + ], // end children first inner div + }, // end first inner div + { role: ROLE_SECTION, // second inner div + children: [ + { TEXT_LEAF: [] }, // c + { TEXT_LEAF: [] }, // d + ], // end children second inner div + }, // end second inner div + ], // end children outer div + }; + testAccessibleTree("c10", tree); + + // c11: A div must be exposed if it contains a br element. + tree = + { role: ROLE_SECTION, // outer div + children: [ + { role: ROLE_SECTION, // First line + children: [ + { TEXT_LEAF: [] }, // text + ], // end children first line + }, // end first line + { role: ROLE_SECTION, // Second line + children: [ + { WHITESPACE: [] }, // whitespace + ], // end children second line + }, // end second line + { role: ROLE_SECTION, // Third line + children: [ + { TEXT_LEAF: [] }, // text + ], // end children third line + }, // end third line + ], // end children outer div + }; + testAccessibleTree("c11", tree); + + // c12: Div shouldn't be rendered if first/last child text node is invisible. + tree = + { role: ROLE_SECTION, // outer div + children: [ + { role: ROLE_PARAGRAPH, + children: [ + { TEXT_LEAF: [] }, // text + ], + }, + ], // end children outer div + }; + testAccessibleTree("c12", tree); + + // c13: Div should be rendered if there is an inline frame after/before + // invisible text nodes. + tree = + { role: ROLE_SECTION, // outer div + children: [ + { TEXT_LEAF: [] }, // l1 + { role: ROLE_SECTION, // l2 + children: [ + { TEXT_LEAF: [] }, // l2 + ], // end children l2 + }, + ], // end children outer div + }; + testAccessibleTree("c13", tree); + + // c14: Div should be rendered if it contains an inline-block. + tree = + { role: ROLE_SECTION, // outer div + children: [ + { TEXT_LEAF: [] }, // l1 + { role: ROLE_SECTION, // l2 + children: [ + { role: ROLE_PUSHBUTTON, + children: [ + { TEXT_LEAF: [] }, + ], + }, + ], // end children l2 + }, + ], // end children outer div + }; + testAccessibleTree("c14", tree); + + // c15: Div should be rendered if previous sibling is text. + tree = + { role: ROLE_SECTION, // outer div + children: [ + { TEXT_LEAF: [] }, // l1 + { SECTION: [] }, // Block break + { TEXT_LEAF: [] }, // l2 + ], // end children outer div + }; + testAccessibleTree("c15", tree); + + // Test getting descendants of unrendered nodes. + ok(!getAccessibleDescendantFor("#c16 > span"), + "Span has no accessible children"); + + ok(!getAccessibleDescendantFor("#c17 > span"), + "Span with relocated child should return null"); + + is(getAccessibleDescendantFor("#c12 > div").role, ROLE_PARAGRAPH, + "Descendant has correct role") + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); +addA11yLoadEvent(doTest); +</script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- Expose the div if it has plain text contents --> + <div id="c1"><div>foo</div></div> + + <!-- Expose the outer and inner div, skip the middle one. --> + <div id="c2"><div><div>foo</div></div></div> + + <!-- Expose the outer and inner divs due to the ID, but skip the middle one. --> + <div id="c3"><div><div id="b">foo</div></div></div> + + <!-- Expose all three divs and their IDs. --> + <div id="c4"><div id="a"><div id="b">foo</div></div></div> + + <!-- Expose outer and inner divs, due to text content, not class. --> + <div id="c5"><div class="a"><div class="b">foo</div></div></div> + + <!-- Expose the outer and two inner divs, skip the single middle one. --> + <div id="c6"><div><div>foo</div><div>bar</div></div></div> + + <!-- Expose all divs due to the middle one being block breaking. --> + <div id="c7"><div>foo<div>bar</div>baz</div></div> + + <!-- Expose all divs due to them all being text block breakers. --> + <div id="c8"><div>foo<div><div>bar</div>baz</div></div></div> + <div id="c9"><div><div><div>a</div>b</div>c</div></div> + + <!-- Both inner divs need to be rendered so there is a break after b. --> + <div id="c10"><div><b>a</b>b</div><div><b>c</b><b>d</b></div></div> + + <!-- Div must be rendered if it contains a br --> + <div id="c11"><div>first line.</div><div><br /></div><div>third line</div></div> + + <!-- Inner div shouldn't be rendered because although its first and last + children are text nodes, they are invisible. + --> + <div id="c12"><div> <p>Test</p> </div></div> + + <!-- Inner div should be rendered because despite the first/last invisible + text nodes, there is also an inline frame. + --> + <div id="c13">l1<div> <span>l2 </span> </div></div> + + <!-- Inner div should be rendered because it contains an inline-block. --> + <div id="c14">l1<div><button>l2</button></div></div> + + <!-- Inner div should be rendered because previous sibling is text. --> + <div id="c15">l1<div></div>l2</div> + + <div id="c16">hello <span></span> world</div> + + <div id="c17"><div aria-owns="c"></div>hello <span><button id="c">b</button></span> world</div> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_dochierarchy.html b/accessible/tests/mochitest/tree/test_dochierarchy.html new file mode 100644 index 0000000000..4822cecb84 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_dochierarchy.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test document hierarchy</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + function doTest() { + // tabDoc and testDoc are different documents depending on whether test + // is running in standalone mode or not. + + var root = getRootAccessible(); + var tabDoc = window.parent ? + getAccessible(window.parent.document, [nsIAccessibleDocument]) : + getAccessible(document, [nsIAccessibleDocument]); + var testDoc = getAccessible(document, [nsIAccessibleDocument]); + var iframeDoc = getAccessible("iframe").firstChild. + QueryInterface(nsIAccessibleDocument); + + is(root.parentDocument, null, + "Wrong parent document of root accessible"); + ok(root.childDocumentCount >= 1, + "Wrong child document count of root accessible"); + + var tabDocumentFound = false; + for (var i = 0; i < root.childDocumentCount && !tabDocumentFound; i++) { + tabDocumentFound = root.getChildDocumentAt(i) == tabDoc; + } + ok(tabDocumentFound, + "Tab document not found in children of the root accessible"); + + is(tabDoc.parentDocument, root, + "Wrong parent document of tab document"); + is(tabDoc.childDocumentCount, 1, + "Wrong child document count of tab document"); + is(tabDoc.getChildDocumentAt(0), (tabDoc == testDoc ? iframeDoc : testDoc), + "Wrong child document at index 0 of tab document"); + + if (tabDoc != testDoc) { + is(testDoc.parentDocument, tabDoc, + "Wrong parent document of test document"); + is(testDoc.childDocumentCount, 1, + "Wrong child document count of test document"); + is(testDoc.getChildDocumentAt(0), iframeDoc, + "Wrong child document at index 0 of test document"); + } + + is(iframeDoc.parentDocument, (tabDoc == testDoc ? tabDoc : testDoc), + "Wrong parent document of iframe document"); + is(iframeDoc.childDocumentCount, 0, + "Wrong child document count of iframe document"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=592913" + title="Provide a way to quickly determine whether an accessible object is a descendant of a tab document"> + Mozilla Bug 592913 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <iframe src="about:mozilla" id="iframe"></iframe> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_dockids.html b/accessible/tests/mochitest/tree/test_dockids.html new file mode 100644 index 0000000000..b19a68624a --- /dev/null +++ b/accessible/tests/mochitest/tree/test_dockids.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test document hierarchy</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + // gA11yEventDumpToConsole = true; + // enableLogging("tree,verbose"); + function doTest() { + var tree = + { DOCUMENT: [ + { TEXT_CONTAINER: [ // head + { TEXT_CONTAINER: [ // link + { STATICTEXT: [] }, // generated content + { STATICTEXT: [] }, // generated content + ] }, + ] }, + { TEXT_LEAF: [ ] }, // body text + { ENTRY: [ ] }, // input under document element + { TEXT_CONTAINER: [ // link under document element + { TEXT_LEAF: [ ] }, // link content + { STATICTEXT: [ ] }, // generated content + { STATICTEXT: [ ] }, // generated content + ] }, + { LINK: [ // anchor under document element + { TEXT_LEAF: [ ] }, // anchor content + ] }, + ] }; + testAccessibleTree(getNode("iframe").contentDocument, tree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887" + title="Elements appended outside the body aren't accessible"> + Mozilla Bug 608887 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <iframe src="dockids.html" id="iframe"></iframe> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_filectrl.html b/accessible/tests/mochitest/tree/test_filectrl.html new file mode 100644 index 0000000000..f0cd5baa5b --- /dev/null +++ b/accessible/tests/mochitest/tree/test_filectrl.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=483573 +--> +<head> + <title>File Input Control tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + var accTree = { + role: ROLE_GROUPING, + children: [ + { + role: ROLE_PUSHBUTTON, + }, + { + role: ROLE_LABEL, + children: [ + { + role: ROLE_TEXT_LEAF, + }, + ], + }, + ], + }; + testAccessibleTree("filectrl", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Expose HTML5 video and audio elements' embedded controls through accessibility APIs" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=483573">Mozilla Bug 483573</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input type="file" id="filectrl" /> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_formctrl.html b/accessible/tests/mochitest/tree/test_formctrl.html new file mode 100644 index 0000000000..3046ec2bf7 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_formctrl.html @@ -0,0 +1,125 @@ +<!DOCTYPE html> +<html> + +<head> + <title>HTML form controls tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // input@type="checkbox" + var accTree = { + role: ROLE_CHECKBUTTON, + children: [ ], + }; + + testAccessibleTree("checkbox", accTree); + + // input@type="radio" + accTree = { + role: ROLE_RADIOBUTTON, + children: [ ], + }; + + testAccessibleTree("radio", accTree); + + // input@type="button" and input@type="submit" + // button + accTree = { + role: ROLE_PUSHBUTTON, + children: [ + { + role: ROLE_TEXT_LEAF, // XXX Bug 567203 + }, + ], + }; + + testAccessibleTree("btn1", accTree); + testAccessibleTree("submit", accTree); + testAccessibleTree("btn2", accTree); + + // input@type="image" + accTree = { + role: ROLE_PUSHBUTTON, + children: [ + { + role: ROLE_STATICTEXT, + }, + ], + }; + testAccessibleTree("image_submit", accTree); + + // input@type="range" + accTree = { SLIDER: [ ] }; + testAccessibleTree("range", accTree); + + // input@type="number" + accTree = { SPINBUTTON: [ ] }; + testAccessibleTree("number", accTree); + + // output + accTree = { + role: ROLE_STATUSBAR, + children: [ + { + role: ROLE_TEXT_LEAF, + }, + ], + }; + testAccessibleTree("output", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Fix O(n^2) access to all the children of a container" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045"> + Bug 342045 + </a> + <a target="_blank" + title="add test for role of input type='image'" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=524521"> + Bug 524521 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=558036" + title="make HTML <output> accessible"> + Bug 558036 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764" + title="make HTML5 input@type=range element accessible"> + Bug 559764 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input type="checkbox" id="checkbox"> + <input type="radio" id="radio"> + <input type="button" value="button" id="btn1"> + <button id="btn2">button</button> + + <input type="submit" id="submit"> + <input type="image" id="image_submit"> + <input type="range" id="range"> + <input type="number" id="number"> + <output id="output">1337</output> + +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_formctrl.xhtml b/accessible/tests/mochitest/tree/test_formctrl.xhtml new file mode 100644 index 0000000000..d85a97171f --- /dev/null +++ b/accessible/tests/mochitest/tree/test_formctrl.xhtml @@ -0,0 +1,129 @@ +<?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"?> + +<!-- Firefox toolbar --> +<?xml-stylesheet href="chrome://browser/content/browser.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL checkbox and radio hierarchy tests"> + + <!-- Firefox toolbar --> + <script type="application/javascript" + src="chrome://browser/content/browser.js"/> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + // checkbox + var accTree = { + role: ROLE_CHECKBUTTON, + children: [ ] + }; + + testAccessibleTree("checkbox", accTree); + + // radiogroup + accTree = { + role: ROLE_RADIO_GROUP, + children: [ + { + role: ROLE_RADIOBUTTON, + children: [ ] + }, + { + role: ROLE_RADIOBUTTON, + children: [ ] + } + ] + }; + + testAccessibleTree("radiogroup", accTree); + + // toolbar + accTree = { + role: ROLE_TOOLBAR, + name: "My toolbar", + children: [ + { + role: ROLE_PUSHBUTTON, + name: "hello", + children: [ ] + } + ] + }; + + testAccessibleTree("toolbar", accTree); + + // toolbar + accTree = { + role: ROLE_TOOLBAR, + name: "My second toolbar", + children: [ + { + role: ROLE_PUSHBUTTON, + name: "hello", + children: [ ] + } + ] + }; + + testAccessibleTree("toolbar2", accTree); + + if (!SEAMONKEY) + testAccessibleTree("tb_customizable", { TOOLBAR: [] }); + + SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045" + title="Fix O(n^2) access to all the children of a container"> + Mozilla Bug 342045 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <checkbox id="checkbox" label="checkbox"/> + <radiogroup id="radiogroup"> + <radio label="radio1"/> + <radio label="radio2"/> + </radiogroup> + <toolbar id="toolbar" toolbarname="My toolbar"> + <toolbarbutton id="button1" label="hello"/> + </toolbar> + <toolbar id="toolbar2" toolbarname="2nd" aria-label="My second toolbar"> + <toolbarbutton id="button2" label="hello"/> + </toolbar> + + <toolbar id="tb_customizable" customizable="true"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/tree/test_gencontent.html b/accessible/tests/mochitest/tree/test_gencontent.html new file mode 100644 index 0000000000..3cd5e35e9a --- /dev/null +++ b/accessible/tests/mochitest/tree/test_gencontent.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Generated content tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style> + .gentext:before { + content: "START" + } + .gentext:after { + content: "END" + } + </style> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // :before and :after pseudo styles + var accTree = { + role: ROLE_SECTION, + children: [ + { + role: ROLE_STATICTEXT, + name: "START", + }, + { + role: ROLE_TEXT_LEAF, + name: "MIDDLE", + }, + { + role: ROLE_STATICTEXT, + name: "END", + }, + ], + }; + + testAccessibleTree("gentext", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Clean up our tree walker" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=530081"> + Mozilla Bug 530081 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div class="gentext" id="gentext">MIDDLE</div> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_groupbox.xhtml b/accessible/tests/mochitest/tree/test_groupbox.xhtml new file mode 100644 index 0000000000..ba873d68c8 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_groupbox.xhtml @@ -0,0 +1,63 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL groupbox hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + var accTree = + { GROUPING: [ + { LABEL: [ + { STATICTEXT: [ ] } + ] }, + { CHECKBUTTON: [ ] } + ] }; + testAccessibleTree("groupbox", accTree); + + SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045" + title="Fix O(n^2) access to all the children of a container"> + Mozilla Bug 342045 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <groupbox id="groupbox"> + <label value="Some caption"/> + <checkbox label="some checkbox label" /> + </groupbox> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/tree/test_html_in_mathml.html b/accessible/tests/mochitest/tree/test_html_in_mathml.html new file mode 100644 index 0000000000..214658de4a --- /dev/null +++ b/accessible/tests/mochitest/tree/test_html_in_mathml.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML in MathML Tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // <label> is generally not valid in MathML and shouldn't create an HTMl + // label. + testAccessibleTree("invalidLabel", { + MATHML_MATH: [ { + TEXT: [ { + TEXT_LEAF: [] + } ], + } ], + }); + + // HTML is valid inside <mtext>, so <label> should create an HTMl label. + testAccessibleTree("validLabel", { + MATHML_MATH: [ { + MATHML_TEXT: [ { + LABEL: [ { + TEXT_LEAF: [] + } ], + } ], + } ], + }); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <math id="invalidLabel"> + <label>Text</label> + </math> + + <math id="validLabel"> + <mtext> + <label>Text</label> + </mtext> + </math> + +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_iframe.html b/accessible/tests/mochitest/tree/test_iframe.html new file mode 100644 index 0000000000..e0e691550b --- /dev/null +++ b/accessible/tests/mochitest/tree/test_iframe.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<html> +<head> + <title>Outer document accessible tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + var accTree = { + role: ROLE_INTERNAL_FRAME, + children: [ + { + role: ROLE_DOCUMENT, + }, + ], + }; + + testAccessibleTree("iframe", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Fix O(n^2) access to all the children of a container" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045"> + Mozilla Bug 342045 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <iframe id="iframe" src="about:mozilla"> + +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_image.xhtml b/accessible/tests/mochitest/tree/test_image.xhtml new file mode 100644 index 0000000000..ee7a6eabb1 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_image.xhtml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL textbox and textarea hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + var accTree = { + role: ROLE_GRAPHIC, + children: [] + }; + testAccessibleTree("image", accTree); + + SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1403231" + title="Remove the image XBL binding"> + Mozilla Bug 1403231 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <image id="image" src="../moz.png" tooltiptext="hello"/> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/tree/test_img.html b/accessible/tests/mochitest/tree/test_img.html new file mode 100644 index 0000000000..c2a7351a15 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_img.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML img tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + // gA11yEventDumpToConsole = true; + function doPreTest() { + waitForImageMap("imgmap", doTest); + } + + function doTest() { + // image map + var accTree = { + role: ROLE_IMAGE_MAP, + children: [ + { + role: ROLE_LINK, + children: [], + }, + { + role: ROLE_LINK, + children: [], + }, + ], + }; + + testAccessibleTree("imgmap", accTree); + + // img + accTree = { + role: ROLE_GRAPHIC, + children: [], + }; + + testAccessibleTree("img", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doPreTest); + </script> + +</head> +<body> + + <a target="_blank" + title="Fix O(n^2) access to all the children of a container" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045"> + Mozilla Bug 342045 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <map name="atoz_map"> + <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b" + coords="17,0,30,14" alt="b" shape="rect"> + <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a" + coords="0,0,13,14" alt="a" shape="rect"> + </map> + + <img id="imgmap" width="447" height="15" + usemap="#atoz_map" + src="../letters.gif"> + + <img id="img" src="../moz.png"> + +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_invalid_img.xhtml b/accessible/tests/mochitest/tree/test_invalid_img.xhtml new file mode 100644 index 0000000000..3d02900cd8 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_invalid_img.xhtml @@ -0,0 +1,48 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>invalid html img</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script> + <![CDATA[ + function doTest() { + document.getElementsByTagName("img")[0].firstChild.data = "2"; + + var accTree = { + role: ROLE_GRAPHIC, + children: [], + }; + testAccessibleTree("the_img", accTree); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> +</head> +<body> + + <a target="_blank" + title="use HyperTextAccessible for invalid img" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=852129"> + Mozilla Bug 852129 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <img id="the_img">1</img> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_invalidationlist.html b/accessible/tests/mochitest/tree/test_invalidationlist.html new file mode 100644 index 0000000000..bad908f3c8 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_invalidationlist.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test document hierarchy</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + function doTest() { + var tree = + {SECTION: [ + { role: ROLE_PUSHBUTTON, name: "Hello" }, + { SECTION: [ + { TEXT: [ { role: ROLE_TEXT_LEAF, name: "Hello" } ] }, + { role: ROLE_TEXT_LEAF, name: "World" } + ]} + ]}; + testAccessibleTree("container", tree); + dumpTree("container"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=673757" + title="Do not process invalidation list while tree is created"> + Mozilla Bug 673757 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="container"> + <div role="button" aria-labelledby="a"></div> + <div> + <span id="a">Hello</span><span id="b">World</span> + </div> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_list.html b/accessible/tests/mochitest/tree/test_list.html new file mode 100644 index 0000000000..46acda1516 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_list.html @@ -0,0 +1,346 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML ul/li element tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function listItemTree(aBulletText, aName, aSubtree) { + var obj = { + role: ROLE_LISTITEM, + children: [ + { + role: ROLE_LISTITEM_MARKER, + name: aBulletText, + }, + { + role: ROLE_TEXT_LEAF, + name: aName, + }, + ], + }; + + if (aSubtree) + obj.children.push(aSubtree); + + return obj; + } + + function doTest() { + // list1 + var discAccTree = { + role: ROLE_LIST, + children: [ + new listItemTree(kDiscBulletText, "Oranges"), + new listItemTree(kDiscBulletText, "Apples"), + new listItemTree(kDiscBulletText, "Bananas"), + ], + }; + + testAccessibleTree("list1", discAccTree); + + // list2 + var circleAccTree = { + role: ROLE_LIST, + children: [ + new listItemTree(kCircleBulletText, "Oranges"), + new listItemTree(kCircleBulletText, "Apples"), + new listItemTree(kCircleBulletText, "Bananas"), + ], + }; + + testAccessibleTree("list2", circleAccTree); + + // list3 + var squareAccTree = { + role: ROLE_LIST, + children: [ + new listItemTree(kSquareBulletText, "Oranges"), + new listItemTree(kSquareBulletText, "Apples"), + new listItemTree(kSquareBulletText, "Bananas"), + ], + }; + + testAccessibleTree("list3", squareAccTree); + + // list4 + var nestedAccTree = { + role: ROLE_LIST, + children: [ + new listItemTree("1. ", "Oranges"), + new listItemTree("2. ", "Apples"), + new listItemTree("3. ", "Bananas", circleAccTree), + ], + }; + + testAccessibleTree("list4", nestedAccTree); + + // dl list + var tree = + { DEFINITION_LIST: [ // dl + { TERM: [ // dt + { TEXT_LEAF: [] }, + ] }, + { DEFINITION: [ // dd + { TEXT_LEAF: [] }, + ] }, + { TERM: [ // dt + { TEXT_LEAF: [] }, + ] }, + { DEFINITION: [ // dd + { TEXT_LEAF: [] }, + ] }, + ] }; + + testAccessibleTree("list5", tree); + + // dl list inside ordered list + tree = + { LIST: [ // ol + { LISTITEM: [ // li + { LISTITEM_MARKER: [ ] }, + { DEFINITION_LIST: [ // dl + { TERM: [ // dt + { TEXT_LEAF: [] }, + ] }, + { DEFINITION: [ // dd + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }, + ] }; + + testAccessibleTree("list6", tree); + + // li having no display:list-item style + tree = + { LIST: [ // ul + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + { TEXT_LEAF: [] }, + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree("list7", tree); + + tree = + { LIST: [ // ul + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree("list8", tree); + + // span having display:list-item style + testAccessibleTree("list9", discAccTree); + + // dl with div grouping dt/dd + tree = + { DEFINITION_LIST: [ // dl + { TERM: [ // dt + { TEXT_LEAF: [] }, + ] }, + { DEFINITION: [ // dd + { TEXT_LEAF: [] }, + ] }, + { TERM: [ // dt + { TEXT_LEAF: [] }, + ] }, + { DEFINITION: [ // dd + { TEXT_LEAF: [] }, + ] }, + ] }; + + testAccessibleTree("list10", tree); + + // list-style-image + testAccessibleTree("list11", discAccTree); + + // list-style: none + tree = + { LIST: [ // ul + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree("list12", tree); + + // ::marker with content + tree = { // ol + role: ROLE_LIST, + children: [ + { // li + role: ROLE_LISTITEM, + children: [ + { // ::marker content text and counter + role: ROLE_LISTITEM_MARKER, + name: "foo1", + }, + { + role: ROLE_TEXT_LEAF, + name: "Oranges", + }, + ], + }, + { // li + role: ROLE_LISTITEM, + children: [ + { // ::marker content text and counter + role: ROLE_LISTITEM_MARKER, + name: "foo2", + }, + { + role: ROLE_TEXT_LEAF, + name: "Apples", + }, + ], + }, + ], + }; + testAccessibleTree("list13", tree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Fix O(n^2) access to all the children of a container" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=342045"> + Mozilla Bug 342045 + </a> + <a target="_blank" + title="Wrong accessible is created for HTML:li having block display style" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=507555"> + Mozilla Bug 507555 + </a> + <a target="_blank" + title="Bullets of nested not ordered lists have one and the same character." + href="https://bugzilla.mozilla.org/show_bug.cgi?id=604587"> + Mozilla Bug 604587 + </a> + <a target="_blank" + title="Fix list bullets for DL list (crash [@ nsBulletFrame::GetListItemText])" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=629114"> + Mozilla Bug 629114 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <ul id="list1"> + <li id="l1_li1">Oranges</li> + <li id="l1_li2">Apples</li> + <li id="l1_li3">Bananas</li> + </ul> + + <ul id="list2" style="list-style-type: circle"> + <li id="l2_li1">Oranges</li> + <li id="l2_li2">Apples</li> + <li id="l2_li3">Bananas</li> + </ul> + + <ul id="list3" style="list-style-type: square"> + <li id="l3_li1">Oranges</li> + <li id="l3_li2">Apples</li> + <li id="l3_li3">Bananas</li> + </ul> + + <ol id="list4"> + <li id="li4">Oranges</li> + <li id="li5">Apples</li> + <li id="li6">Bananas<ul> + <li id="n_li4">Oranges</li> + <li id="n_li5">Apples</li> + <li id="n_li6">Bananas</li> + </ul> + </li> + </ol> + + <dl id="list5"> + <dt>item1</dt><dd>description</dd> + <dt>item2</td><dd>description</dd> + </dl> + + <ol id="list6"> + <li> + <dl id="dl"> + <dt>item1</dt><dd>description</dd> + </dl> + </li> + </ol> + + <!-- display style different than list-item --> + <ul id="list7"> + <li id="l7_li1" style="display:inline-block;">Oranges</li> + <li id="l7_li2" style="display:inline-block;">Apples</li> + </ul> + + <ul id="list8"> + <li id="l8_li1" style="display:inline; float:right;">Oranges</li> + <li id="l8_li2" style="display:inline; float:right;">Apples</li> + </ul> + + <!-- list-item display style --> + <ul id="list9"> + <span id="l9_li1" style="display:list-item">Oranges</span> + <span id="l9_li2" style="display:list-item">Apples</span> + <span id="l9_li3" style="display:list-item">Bananas</span> + </ul> + + <!-- dl with div grouping dd/dt elements (bug 1476347) --> + <dl id="list10"> + <div><dt>item1</dt><dd>description</dd></div> + <div><dt>item2</td><dd>description</dd></div> + </dl> + + <!-- list-style-image --> + <ul id="list11" + style="list-style-type: none; list-style-image: url('../moz.png');"> + <li>Oranges</li> + <li>Apples</li> + <li>Bananas</li> + </ul> + + <!-- list-style: none --> + <ul id="list12" style="list-style: none;"> + <li>Oranges</li> + <li>Apples</li> + </ul> + + <!-- ::marker with content --> + <style> + #list13 li { + counter-increment: list13counter; + } + #list13 li::marker { + content: 'foo' counter(list13counter); + } + </style> + <ol id="list13"> + <li>Oranges</li> + <li>Apples</li> + </ol> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_map.html b/accessible/tests/mochitest/tree/test_map.html new file mode 100644 index 0000000000..72de628f9f --- /dev/null +++ b/accessible/tests/mochitest/tree/test_map.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML map accessible tree tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // map used as imagemap, not accessible + var accTree = + { SECTION: [ ] }; + + testAccessibleTree("imagemapcontainer", accTree); + + // map group. Imagemaps are inlines by default, so TEXT_CONTAINER. + accTree = + { TEXT_CONTAINER: [ + { PARAGRAPH: [ + { TEXT_LEAF: [ ] }, + { LINK: [ + { TEXT_LEAF: [ ] }, + ] }, + { TEXT_LEAF: [ ] }, + { LINK: [ + { TEXT_LEAF: [ ] }, + ] }, + { TEXT_LEAF: [ ] }, + ] }, + ] }; + + testAccessibleTree("mapgroup", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Map used for grouping is not accessible under certain circumstances" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=627718"> + Mozilla Bug 627718 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="imagemapcontainer"> + <map name="atoz_map"> + <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b" + coords="17,0,30,14" alt="b" shape="rect"> + <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#a" + coords="0,0,13,14" alt="a" shape="rect"> + </map> + </div> + + <img id="imgmap" width="447" height="15" + usemap="#atoz_map" + src="../letters.gif"> + + <map id="mapgroup" title="Navigation Bar" name="mapgroup"> + <p> + [<a href="#how">Bypass navigation bar</a>] + [<a href="home.html">Home</a>] + </p> + </map> + +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_media.html b/accessible/tests/mochitest/tree/test_media.html new file mode 100644 index 0000000000..5d9fe0ef24 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_media.html @@ -0,0 +1,127 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=483573 +--> +<head> + <title>HTML5 audio/video tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + + async function loadAudioSource() { + /** + * Setting the source dynamically and wait for it to load, + * so we can test the accessibility tree of the control in its ready and + * stable state. + * + * See bug 1484048 comment 25 for discussion on how it switches UI when + * loading a statically declared source. + */ + const bufferA11yShown = waitForEvent( + EVENT_SHOW, + evt => evt.accessible.role == ROLE_TEXT_LEAF && + evt.accessible.indexInParent == 2 && + evt.accessible.parent.role == ROLE_STATUSBAR + ); + await new Promise(resolve => { + let el = document.getElementById("audio"); + el.addEventListener("canplaythrough", resolve, {once: true}); + el.src = "../bug461281.ogg"; + }); + // Wait for this to be reflected in the a11y tree. + await bufferA11yShown; + + // Give Fluent time to update the value of the scrubber asynchronously. + let scrubber = document.getElementById("audio") + .openOrClosedShadowRoot.getElementById("scrubber"); + await SimpleTest.promiseWaitForCondition(() => + scrubber.getAttribute("aria-valuetext") == "0:00 / 0:02"); + + doTest(); + } + + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // test the accessible tree + + var accTree = { + role: ROLE_GROUPING, + children: [ + { // start/stop button + role: ROLE_PUSHBUTTON, + name: "Play", + children: [], + }, + { // buffer bar + role: ROLE_STATUSBAR, + children: [ + { + role: ROLE_TEXT_LEAF, + name: "Loading:", + }, + { + role: ROLE_TEXT_LEAF, + name: " ", + }, + { + role: ROLE_TEXT_LEAF, + // The name is the percentage buffered; e.g. "0%", "100%". + // We can't check it here because it might be different + // depending on browser caching. + }, + ], + }, + { // slider of progress bar + role: ROLE_SLIDER, + name: "Position", + value: "0:00 / 0:02", + children: [], + }, + { // mute button + role: ROLE_PUSHBUTTON, + name: "Mute", + children: [], + }, + { // slider of volume bar + role: ROLE_SLIDER, + children: [], + name: "Volume", + }, + ], + }; + testAccessibleTree("audio", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(loadAudioSource); + </script> +</head> +<body> + + <a target="_blank" + title="Expose HTML5 video and audio elements' embedded controls through accessibility APIs" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=483573">Mozilla Bug 483573</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <audio id="audio" controls="true"></audio> + + <div id="eventDump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_select.html b/accessible/tests/mochitest/tree/test_select.html new file mode 100644 index 0000000000..80c17b9ef4 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_select.html @@ -0,0 +1,121 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML select control tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + var accTree = { + role: ROLE_LISTBOX, + children: [ + { + role: ROLE_GROUPING, + children: [ + { + role: ROLE_STATICTEXT, + children: [ ], + }, + { + role: ROLE_OPTION, + children: [ + { + role: ROLE_TEXT_LEAF, + }, + ], + }, + { + role: ROLE_OPTION, + children: [ + { + role: ROLE_TEXT_LEAF, + }, + ], + }, + ], + }, + { + role: ROLE_OPTION, + children: [ + { + role: ROLE_TEXT_LEAF, + }, + ], + }, + ], + }; + testAccessibleTree("listbox", accTree); + + accTree = { + role: ROLE_COMBOBOX, + children: [ + { + role: ROLE_COMBOBOX_LIST, + children: [ + { + role: ROLE_GROUPING, + children: [ + { + role: ROLE_COMBOBOX_OPTION, + children: [ ], + }, + { + role: ROLE_COMBOBOX_OPTION, + children: [ ], + }, + ], + }, + { + role: ROLE_COMBOBOX_OPTION, + children: [ ], + }, + ], + }, + ], + }; + testAccessibleTree("combobox", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="remove all the code in #ifdef COMBO_BOX_WITH_THREE_CHILDREN" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=506616"> + Mozilla Bug 506616 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select id="listbox" size="4"> + <optgroup label="Colors"> + <option>Red</option> + <option>Blue</option> + </optgroup> + <option>Animal</option> + </select> + + <select id="combobox"> + <optgroup label="Colors"> + <option>Red</option> + <option>Blue</option> + </optgroup> + <option>Animal</option> + </select> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_svg.html b/accessible/tests/mochitest/tree/test_svg.html new file mode 100644 index 0000000000..4a071d0f4a --- /dev/null +++ b/accessible/tests/mochitest/tree/test_svg.html @@ -0,0 +1,127 @@ +<!DOCTYPE html> +<html> +<head> + <title>SVG Tree Tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // svgText + var accTree = { + role: ROLE_DIAGRAM, + children: [ + { + role: ROLE_TEXT_CONTAINER, + children: [ + { + role: ROLE_TEXT_LEAF, + }, + ], + }, + ], + }; + testAccessibleTree("svgText", accTree); + + // svg1 + accTree = { + role: ROLE_DIAGRAM, + children: [] + }; + testAccessibleTree("svg1", accTree); + + // svg2 + accTree = { + role: ROLE_DIAGRAM, + children: [ + { + role: ROLE_GROUPING, + children: [] + } + ] + }; + testAccessibleTree("svg2", accTree); + + // svg3 + accTree = { + role: ROLE_DIAGRAM, + children: [ + { + role: ROLE_GRAPHIC, + children: [] + } + ] + }; + testAccessibleTree("svg3", accTree); + + // svg4 + accTree = { + role: ROLE_DIAGRAM, + children: [ + { + role: ROLE_GROUPING, + children: [ + { + role: ROLE_GRAPHIC, + children: [] + } + ] + } + ] + }; + testAccessibleTree("svg4", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <svg id="svgText"> + <text>This is some text</text> + </svg> + + <!-- no accessible objects --> + <svg id="svg1"> + <g id="g1"> + <rect width="300" height="100" id="rect1" style="fill:#00f" /> + </g> + </svg> + + <svg id="svg2"> + <g id="g2"> + <title>g</title> + <rect width="300" height="100" id="rect2" style="fill:#00f" /> + </g> + </svg> + + <svg id="svg3"> + <g id="g3"> + <rect width="300" height="100" id="rect3" style="fill:#00f"> + <title>rect</title> + </rect> + </g> + </svg> + + <svg id="svg4"> + <g id="g4"> + <title>g</title> + <rect width="300" height="100" id="rect4" style="fill:#00f"> + <title>rect</title> + </rect> + </g> + </svg> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_tabbox.xhtml b/accessible/tests/mochitest/tree/test_tabbox.xhtml new file mode 100644 index 0000000000..27e9222873 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_tabbox.xhtml @@ -0,0 +1,108 @@ +<?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="Accessible XUL tabbox hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // tabbox + + var accTree = { + role: ROLE_PAGETABLIST, + children: [ + { + role: ROLE_PAGETAB, + children: [ + { + role: ROLE_STATICTEXT, + children: [], + } + ] + }, + { + role: ROLE_PAGETAB, + children: [ + { + role: ROLE_STATICTEXT, + children: [], + } + ] + } + ] + }; + testAccessibleTree("tabs", accTree); + + accTree = { + role: ROLE_PANE, + children: [ + { + role: ROLE_PROPERTYPAGE, + children: [] + }, + { + role: ROLE_PROPERTYPAGE, + children: [] + } + ] + }; + testAccessibleTree("tabpanels", accTree); + + SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=540389" + title=" WARNING: Bad accessible tree!: [tabbrowser tab] "> + Mozilla Bug 540389 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=552944" + title="No relationship between tabs and associated property page in new tabbrowser construct"> + Mozilla Bug 552944 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tabbox> + <tabs id="tabs"> + <tab label="tab1"/> + <tab label="tab2"/> + </tabs> + <tabpanels id="tabpanels"> + <tabpanel/> + <tabpanel/> + </tabpanels> + </tabbox> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/tree/test_tabbrowser.xhtml b/accessible/tests/mochitest/tree/test_tabbrowser.xhtml new file mode 100644 index 0000000000..401ea1a2b1 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_tabbrowser.xhtml @@ -0,0 +1,261 @@ +<?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="Accessible XUL tabbrowser hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // invoker + function testTabHierarchy() + { + this.eventSeq = [ + new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 0), + new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1) + ]; + + this.invoke = function testTabHierarchy_invoke() + { + var docURIs = ["about:license", "about:mozilla"]; + tabBrowser().loadTabs(docURIs, { + inBackground: false, + replace: true, + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + // Flush layout, so as to guarantee that the a11y tree is constructed. + browserDocument().documentElement.getBoundingClientRect(); + } + + this.finalCheck = function testTabHierarchy_finalCheck(aEvent) + { + //////////////////// + // Tab bar + //////////////////// + var tabsAccTree = { + // xul:tabs + role: ROLE_PAGETABLIST, + children: [ + // Children depend on application (UI): see below. + ] + }; + + // SeaMonkey and Firefox tabbrowser UIs differ. + if (SEAMONKEY) { + SimpleTest.ok(true, "Testing SeaMonkey tabbrowser UI."); + + tabsAccTree.children.splice(0, 0, + { + // xul:toolbarbutton ("Open a new tab") + role: ROLE_PUSHBUTTON, + children: [] + }, + { + // xul:tab ("about:license") + role: ROLE_PAGETAB, + children: [] + }, + { + // tab ("about:mozilla") + role: ROLE_PAGETAB, + children: [] + }, + { + // xul:toolbarbutton ("List all tabs") + role: ROLE_PUSHBUTTON, + children: [ + { + // xul:menupopup + role: ROLE_MENUPOPUP, + children: [] + } + ] + }, + { + // xul:toolbarbutton ("Close current tab") + role: ROLE_PUSHBUTTON, + children: [] + } + ); + } else { + SimpleTest.ok(true, "Testing Firefox tabbrowser UI."); + let newTabChildren = []; + if (SpecialPowers.getBoolPref("privacy.userContext.enabled")) { + newTabChildren = [ + { + role: ROLE_MENUPOPUP, + children: [] + } + ]; + } + + // NB: The (3) buttons are not visible, unless manually hovered, + // probably due to size reduction in this test. + tabsAccTree.children.splice(0, 0, + { + // xul:tab ("about:license") + role: ROLE_PAGETAB, + children: [ + { + // xul:text, i.e. the tab label text + role: ROLE_TEXT_LEAF, + children: [] + } + ] + }, + { + // tab ("about:mozilla") + role: ROLE_PAGETAB, + children: [ + { + // xul:text, i.e. the tab label text + role: ROLE_TEXT_LEAF, + children: [] + } + ] + }, + { + // xul:toolbarbutton ("Open a new tab") + role: ROLE_PUSHBUTTON, + children: newTabChildren + } + // "List all tabs" dropdown + // XXX: This child(?) is not present in this test. + // I'm not sure why (though probably expected). + ); + } + + testAccessibleTree(tabBrowser().tabContainer, tabsAccTree); + + //////////////////// + // Tab contents + //////////////////// + var tabboxAccTree = { + // xul:tabpanels + role: ROLE_PANE, + children: [ + { + // xul:notificationbox + role: ROLE_PROPERTYPAGE, + children: [ + { + // xul:browser + role: ROLE_INTERNAL_FRAME, + children: [ + { + // #document ("about:license") + role: ROLE_DOCUMENT + // children: [ ... ] // Ignore document content. + } + ] + } + ] + }, + { + // notificationbox + role: ROLE_PROPERTYPAGE, + children: [ + { + // browser + role: ROLE_INTERNAL_FRAME, + children: [ + { + // #document ("about:mozilla") + role: ROLE_DOCUMENT + // children: [ ... ] // Ignore document content. + } + ] + } + ] + }, + { + // notificationbox + role: ROLE_PROPERTYPAGE, + children: [ + { + // browser + role: ROLE_INTERNAL_FRAME, + children: [ + { + // #document ("about:newtab" preloaded) + role: ROLE_DOCUMENT + // children: [ ... ] // Ignore document content. + } + ] + } + ] + } + ] + }; + + testAccessibleTree(tabBrowser().tabbox.tabpanels, tabboxAccTree); + } + + this.getID = function testTabHierarchy_getID() + { + return "hierarchy of tabs"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + gA11yEventDumpToConsole = true; + //enableLogging("tree,verbose,stack"); + + var gQueue = null; + function doTest() + { + SimpleTest.requestCompleteLog(); + + // Load documents into tabs and wait for docLoadComplete events caused by these + // documents load before we start the test. + gQueue = new eventQueue(); + + gQueue.push(new testTabHierarchy()); + gQueue.onFinish = function() { closeBrowserWindow(); } + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTest); + ]]> + </script> + + <vbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=540389" + title=" WARNING: Bad accessible tree!: [tabbrowser tab] "> + Mozilla Bug 540389 + </a><br/> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=552944" + title="No relationship between tabs and associated property page in new tabbrowser construct"> + Mozilla Bug 552944 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="eventdump"></vbox> + </vbox> + +</window> diff --git a/accessible/tests/mochitest/tree/test_table.html b/accessible/tests/mochitest/tree/test_table.html new file mode 100644 index 0000000000..5f34c12067 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_table.html @@ -0,0 +1,507 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML table tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // //////////////////////////////////////////////////////////////////////// + // tables having captions + + // Two captions, first is used, second is ignored. + var accTree = + { TABLE: [ + { CAPTION: [ + { + role: ROLE_TEXT_LEAF, + name: "caption", + }, + ] }, + { ROW: [ + { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] }, + { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] }, + ] }, + { ROW: [ + { CELL: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ { TEXT_LEAF: [ ] } ] }, + ] }, + { ROW: [ + { CELL: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ { TEXT_LEAF: [ ] } ] }, + ] }, + { ROW: [ + { CELL: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ { TEXT_LEAF: [ ] } ] }, + ] }, + ] }; + + testAccessibleTree("table", accTree); + + // One caption, empty text, caption is included. + accTree = + { TABLE: [ + { CAPTION: [ ] }, + { ROW: [ + { CELL: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ { TEXT_LEAF: [ ] } ] }, + ] }, + ] }; + + testAccessibleTree("table_caption_empty", accTree); + + // Two captions, first has empty text, second is ignored. + accTree = + { TABLE: [ + { CAPTION: [ ] }, + { ROW: [ + { CELL: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ { TEXT_LEAF: [ ] } ] }, + ] }, + ] }; + + testAccessibleTree("table_caption_firstempty", accTree); + + // One caption, placed in the end of table. In use. + accTree = + { TABLE: [ + { CAPTION: [ + { + role: ROLE_TEXT_LEAF, + name: "caption", + }, + ] }, + { ROW: [ + { CELL: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ { TEXT_LEAF: [ ] } ] }, + ] }, + ] }; + + testAccessibleTree("table_caption_intheend", accTree); + + // One caption, collapsed to zero width and height. In use. + accTree = + { TABLE: [ + { CAPTION: [ + { + role: ROLE_TEXT_LEAF, + name: "caption", + }, + ] }, + { ROW: [ + { CELL: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ { TEXT_LEAF: [ ] } ] }, + ] }, + ] }; + + testAccessibleTree("table_caption_collapsed", accTree); + + // //////////////////////////////////////////////////////////////////////// + // table2 (consist of one column) + + accTree = { + role: ROLE_TABLE, + children: [ + { + role: ROLE_ROW, + children: [ + { + role: ROLE_COLUMNHEADER, + }, + ], + }, + { + role: ROLE_ROW, + children: [ + { + role: ROLE_CELL, + }, + ], + }, + ], + }; + + testAccessibleTree("table2", accTree); + + // //////////////////////////////////////////////////////////////////////// + // table3 (consist of one row) + + accTree = { + role: ROLE_TABLE, + children: [ + { + role: ROLE_ROW, + children: [ + { + role: ROLE_ROWHEADER, + }, + { + role: ROLE_CELL, + }, + ], + }, + ], + }; + + testAccessibleTree("table3", accTree); + + // /////////////////////////////////////////////////////////////////////// + // table4 (display: table-row) + accTree = + { TABLE: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] } ], + }; + testAccessibleTree("table4", accTree); + + // /////////////////////////////////////////////////////////////////////// + // table5 (tbody with display: block should not get accessible) + accTree = + { TABLE: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + testAccessibleTree("table5", accTree); + + // /////////////////////////////////////////////////////////////////////// + // log table + accTree = + { TABLE: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + testAccessibleTree("logtable", accTree); + + // /////////////////////////////////////////////////////////////////////// + // display:block table + accTree = + { TABLE: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + testAccessibleTree("block_table", accTree); + + // /////////////////////////////////////////////////////////////////////// + // display:inline table + accTree = + { TABLE: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + testAccessibleTree("inline_table1", accTree); + + // /////////////////////////////////////////////////////////////////////// + // display:inline table + accTree = + { TABLE: [ + { ROW: [ + { CELL: [ + { TABLE: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }, + ] }, + ] }, + ] }; + testAccessibleTree("table_containing_inlinetable", accTree); + + // /////////////////////////////////////////////////////////////////////// + // table with a cell that has display:block + accTree = + { TABLE: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + testAccessibleTree("table_containing_block_cell", accTree); + + // /////////////////////////////////////////////////////////////////////// + // A table with all elements being display:block, including a row group. + // This makes us fall back to the ARIAGridRowAccessible. + // Strange example from Gmail. + accTree = + { TABLE: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }; + testAccessibleTree("table_all_display_block", accTree); + + // /////////////////////////////////////////////////////////////////////// + // A table with a display:block tbody that has an aria role + // The tbody should get an accessible with the desired role. + accTree = + { TABLE: [ + { DIALOG: [ + { TEXT_CONTAINER: [ + { TEXT_CONTAINER: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }, + ] }; + testAccessibleTree("table_with_block_tbody_and_role", accTree); + + // /////////////////////////////////////////////////////////////////////// + // A table with a display:block tbody that is focusable + // The tbody should get a grouping accessible. + accTree = + { TABLE: [ + { GROUPING: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] }, + ] }, + ] }, + ] }, + ] }; + testAccessibleTree("table_with_focusable_block_tbody", accTree); + + // /////////////////////////////////////////////////////////////////////// + // Test that the CSS position property doesn't stop th elements from + // reporting the proper columnheader, rowheader roles. + accTree = + { TABLE: [ + { ROW: [ + { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] }, + { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] }, + { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] }, + { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] }, + { COLUMNHEADER: [ { TEXT_LEAF: [ ] } ] }, + ] }, + { ROW: [ + { ROWHEADER: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ ] }, + { ROWHEADER: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ ] }, + { ROWHEADER: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ ] }, + { ROWHEADER: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ ] }, + { ROWHEADER: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ ] }, + ] }, + ] }; + testAccessibleTree("table_containing_pos_styled_th", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="When a table has only one column per row and that column happens to be a column header its role is exposed wrong" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=529621"> + Mozilla Bug 529621 + </a> + <a target="_blank" + title="when div has display style table-row" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=727722"> + Mozilla Bug 727722 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <table id="table"> + <thead> + <tr> + <th>col1</th><th>col2</th> + </tr> + </thead> + <caption>caption</caption> + <tbody> + <tr> + <td>cell1</td><td>cell2</td> + </tr> + </tbody> + <tr> + <td>cell3</td><td>cell4</td> + </tr> + <caption>caption2</caption> + <tfoot> + <tr> + <td>cell5</td><td>cell6</td> + </tr> + </tfoot> + </table> + + <table id="table_caption_empty"> + <caption></caption> + <tr> + <td>cell1</td><td>cell2</td> + </tr> + </table> + + <table id="table_caption_firstempty"> + <caption></caption> + <tr> + <td>cell1</td><td>cell2</td> + </tr> + <caption>caption</caption> + </table> + + <table id="table_caption_intheend"> + <tr> + <td>cell1</td><td>cell2</td> + </tr> + <caption>caption</caption> + </table> + + <table id="table_caption_collapsed"> + <caption style="width: 0; height: 0">caption</caption> + <tr> + <td>cell1</td><td>cell2</td> + </tr> + </table> + <table id="table2"> + <thead> + <tr> + <th>colheader</th> + </tr> + </thead> + <tbody> + <tr> + <td>bla</td> + </tr> + </tbody> + </table> + + <table id="table3"> + <tr> + <th>rowheader</th> + <td>cell</td> + </tr> + </table> + + <table id="table4"> + <div style="display: table-row"> + <td>cell1</td> + </div> + </table> + + <table id="table5"> + <tbody style="display:block;"> + <tr> + <td>bla</td> + </tr> + </tbody> + </table> + + <table id="logtable" role="log"><tr><td>blah</td></tr></table> + + <table id="block_table" style="display: block;"> + <tr> + <td>bla</td> + </tr> + </table> + + <table id="inline_table1" border="1" style="display:inline"> + <tr> + <td>table1 cell1</td> + <td>table1 cell2</td> + </tr> + </table> + + <table id="table_containing_inlinetable"><tr><td> + <table id="inline_table2" border="1" style="display:inline"> + <tr id="tr_in_inline_table2"> + <td id="td_in_inline_table2">cell</td> + </tr> + </table> + </td></tr></table> + + <table id="table_containing_block_cell"> + <tr> + <td>Normal cell</td> + <td style="display: block;">Block cell</td> + </tr> + </table> + <table id="table_all_display_block" style="display:block;"> + <tbody style="display:block;"> + <tr style="display:block;"> + <td style="display:block;">text</td> + </tr> + </tbody> + </table> + + <table id="table_with_block_tbody_and_role"> + <tbody style="display:block;" role="dialog"> + <tr> + <td>text</td> + </tr> + </tbody> + </table> + + <table id="table_with_focusable_block_tbody"> + <tbody style="display:block;" tabindex="0"> + <tr> + <td>text</td> + </tr> + </tbody> + </table> + + <table id="table_containing_pos_styled_th"> + <tr> + <th style="position: static">static colheader</th> + <th style="position: relative">relative colheader</th> + <th style="position: absolute">absolute colheader</th> + <th style="position: fixed">fixed colheader</th> + <th style="position: sticky">sticky colheader</th> + </tr> + <tr> + <th style="position: static">static rowheader</th> + <td/> + <th style="position: relative">relative rowheader</th> + <td/> + <th style="position: absolute">absolute rowheader</th> + <td/> + <th style="position: fixed">fixed rowheader</th> + <td/> + <th style="position: sticky">sticky rowheader</th> + <td/> + </tr> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_table_2.html b/accessible/tests/mochitest/tree/test_table_2.html new file mode 100644 index 0000000000..c0faece7e5 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_table_2.html @@ -0,0 +1,242 @@ +<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> +<link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + +<style> +.responsive-table { + width: 100%; + margin-bottom: 1.5em; +} +.responsive-table thead { + position: absolute; + clip: rect(1px 1px 1px 1px); + /* IE6, IE7 */ + clip: rect(1px, 1px, 1px, 1px); + padding: 0; + border: 0; + height: 1px; + width: 1px; + overflow: hidden; +} +.responsive-table thead th { + background-color: #1d96b2; + border: 1px solid #1d96b2; + font-weight: normal; + text-align: center; + color: white; +} +.responsive-table thead th:first-of-type { + text-align: left; +} +.responsive-table tbody, +.responsive-table tr, +.responsive-table th, +.responsive-table td { + display: block; + padding: 0; + text-align: left; + white-space: normal; +} +.responsive-table th, +.responsive-table td { + padding: .5em; + vertical-align: middle; +} +.responsive-table caption { + margin-bottom: 1em; + font-size: 1em; + font-weight: bold; + text-align: center; +} +.responsive-table tfoot { + font-size: .8em; + font-style: italic; +} +.responsive-table tbody tr { + margin-bottom: 1em; + border: 2px solid #1d96b2; +} +.responsive-table tbody tr:last-of-type { + margin-bottom: 0; +} +.responsive-table tbody th[scope="row"] { + background-color: #1d96b2; + color: white; +} +.responsive-table tbody td[data-type=currency] { + text-align: right; +} +.responsive-table tbody td[data-title]:before { + content: attr(data-title); + float: left; + font-size: .8em; + color: rgba(94, 93, 82, 0.75); +} +.responsive-table tbody td { + text-align: right; + border-bottom: 1px solid #1d96b2; +} + +.responsive-table { + font-size: .9em; +} +.responsive-table thead { + position: relative; + clip: auto; + height: auto; + width: auto; + overflow: auto; +} +.responsive-table tr { + display: table-row; +} +.responsive-table th, +.responsive-table td { + display: table-cell; + padding: .5em; +} + +.responsive-table caption { + font-size: 1.5em; +} +.responsive-table tbody { + display: table-row-group; +} +.responsive-table tbody tr { + display: table-row; + border-width: 1px; +} +.responsive-table tbody tr:nth-of-type(even) { + background-color: rgba(94, 93, 82, 0.1); +} +.responsive-table tbody th[scope="row"] { + background-color: transparent; + color: #5e5d52; + text-align: left; +} +.responsive-table tbody td { + text-align: center; +} +.responsive-table tbody td[data-title]:before { + content: none; +} +</style> + +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script type="application/javascript" + src="../common.js"></script> +<script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../table.js"></script> + +<script type="application/javascript"> + +const COLHEADER = ROLE_COLUMNHEADER; +const ROWHEADER = ROLE_ROWHEADER; +const CELL = ROLE_CELL; +const TEXT_LEAF = ROLE_TEXT_LEAF; + +function doTest() { + let accTree = + { TABLE: [ + { CAPTION: [ + { + role: ROLE_TEXT_LEAF, + name: "Top 10 Grossing Animated Films of All Time", + }, + ] }, + { TEXT_CONTAINER: [ + { ROW: [ + { role: COLHEADER, name: "Film Title" }, + { role: COLHEADER, name: "Released" }, + { role: COLHEADER, name: "Studio" }, + { role: COLHEADER, name: "Worldwide Gross" }, + { role: COLHEADER, name: "Domestic Gross" }, + { role: COLHEADER, name: "Foreign Gross" }, + { role: COLHEADER, name: "Budget" }, + ] }, + ] }, + { ROW: [ + { role: CELL }, + ] }, + { ROW: [ + { role: ROWHEADER, name: "Toy Story 3" }, + { CELL: [ { role: TEXT_LEAF, name: "2010" }] }, + { CELL: [ { role: TEXT_LEAF, name: "Disney Pixar" }] }, + { CELL: [ { role: TEXT_LEAF, name: "$1,063,171,911" }] }, + { CELL: [ { role: TEXT_LEAF, name: "$415,004,880" }] }, + { CELL: [ { role: TEXT_LEAF, name: "$648,167,031" }] }, + { CELL: [ { role: TEXT_LEAF, name: "$200,000,000" }] }, + ] }, + { ROW: [ + { role: ROWHEADER, name: "Shrek Forever After" }, + { CELL: [ { role: TEXT_LEAF, name: "2010" }] }, + { CELL: [ { role: TEXT_LEAF, name: "Dreamworks" }] }, + { CELL: [ { role: TEXT_LEAF, name: "$752,600,867" }] }, + { CELL: [ { role: TEXT_LEAF, name: "$238,736,787" }] }, + { CELL: [ { role: TEXT_LEAF, name: "$513,864,080" }] }, + { CELL: [ { role: TEXT_LEAF, name: "$165,000,000" }] }, + ] }, + ] }; + + testAccessibleTree("table", accTree); + + SimpleTest.finish(); +} +SimpleTest.waitForExplicitFinish(); +addA11yLoadEvent(doTest); +</script> +</head> + +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <table class="responsive-table" id="table"> + <caption>Top 10 Grossing Animated Films of All Time</caption> + <thead> + <tr> + <th scope="col">Film Title</th> + <th scope="col">Released</th> + <th scope="col">Studio</th> + <th scope="col">Worldwide Gross</th> + <th scope="col">Domestic Gross</th> + <th scope="col">Foreign Gross</th> + <th scope="col">Budget</th> + </tr> + </thead> + <tfoot> + <tr> + <td colspan="7">Sources: <a href="http://en.wikipedia.org/wiki/List_of_highest-grossing_animated_films" rel="external">Wikipedia</a> & <a href="http://www.boxofficemojo.com/genres/chart/?id=animation.htm" rel="external">Box Office Mojo</a>. Data is current as of March 12, 2014</td> + </tr> + </tfoot> + <tbody> + <tr> + <th scope="row">Toy Story 3</th> + <td data-title="Released">2010</td> + <td data-title="Studio">Disney Pixar</td> + <td data-title="Worldwide Gross" data-type="currency">$1,063,171,911</td> + <td data-title="Domestic Gross" data-type="currency">$415,004,880</td> + <td data-title="Foreign Gross" data-type="currency">$648,167,031</td> + <td data-title="Budget" data-type="currency">$200,000,000</td> + </tr> + <tr> + <th scope="row">Shrek Forever After</th> + <td data-title="Released">2010</td> + <td data-title="Studio">Dreamworks</td> + <td data-title="Worldwide Gross" data-type="currency">$752,600,867</td> + <td data-title="Domestic Gross" data-type="currency">$238,736,787</td> + <td data-title="Foreign Gross" data-type="currency">$513,864,080</td> + <td data-title="Budget" data-type="currency">$165,000,000</td> + </tr> + </tbody> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_table_3.html b/accessible/tests/mochitest/tree/test_table_3.html new file mode 100644 index 0000000000..60af4d7f82 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_table_3.html @@ -0,0 +1,244 @@ +<!DOCTYPE HTML PUBLIC "-//w3c//dtd html 4.0 transitional//en"> +<html> +<head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> +<link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + +<style> +.responsive-table { + width: 100%; + margin-bottom: 1.5em; +} +.responsive-table thead { + position: absolute; + clip: rect(1px 1px 1px 1px); + /* IE6, IE7 */ + clip: rect(1px, 1px, 1px, 1px); + padding: 0; + border: 0; + height: 1px; + width: 1px; + overflow: hidden; +} +.responsive-table thead th { + background-color: #1d96b2; + border: 1px solid #1d96b2; + font-weight: normal; + text-align: center; + color: white; +} +.responsive-table thead th:first-of-type { + text-align: left; +} +.responsive-table tbody, +.responsive-table tr, +.responsive-table th, +.responsive-table td { + display: block; + padding: 0; + text-align: left; + white-space: normal; +} +.responsive-table th, +.responsive-table td { + padding: .5em; + vertical-align: middle; +} +.responsive-table caption { + margin-bottom: 1em; + font-size: 1em; + font-weight: bold; + text-align: center; +} +.responsive-table tfoot { + font-size: .8em; + font-style: italic; +} +.responsive-table tbody tr { + margin-bottom: 1em; + border: 2px solid #1d96b2; +} +.responsive-table tbody tr:last-of-type { + margin-bottom: 0; +} +.responsive-table tbody th[scope="row"] { + background-color: #1d96b2; + color: white; +} +.responsive-table tbody td[data-type=currency] { + text-align: right; +} +.responsive-table tbody td[data-title]:before { + content: attr(data-title); + float: left; + font-size: .8em; + color: #1d96b2; + font-weight: bold; +} +.responsive-table tbody td { + text-align: right; + border-bottom: 1px solid #1d96b2; +} + +/* float everything */ +.responsive-table tbody tr { + float: left; + width: 48%; + margin-left: 2%; +} +</style> + +<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script type="application/javascript" + src="../common.js"></script> +<script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../table.js"></script> + +<script type="application/javascript"> + +const COLHEADER = ROLE_COLUMNHEADER; +const ROWHEADER = ROLE_ROWHEADER; +const CELL = ROLE_CELL; +const STATICTEXT = ROLE_STATICTEXT; +const TEXT_LEAF = ROLE_TEXT_LEAF; +const GROUPING = ROLE_GROUPING; + +function doTest() { + let accTree = + { TABLE: [ + { CAPTION: [ + { + role: ROLE_TEXT_LEAF, + name: "Top 10 Grossing Animated Films of All Time", + }, + ] }, + { TEXT_CONTAINER: [ + { ROW: [ + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Film Title" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Released" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Studio" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Worldwide Gross" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Domestic Gross" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Foreign Gross" } ] }, + { COLUMNHEADER: [ { role: TEXT_LEAF, name: "Budget" } ] }, + ] }, + ] }, + { ROW: [ + { role: CELL }, + ] }, + { ROW: [ + { ROWHEADER: [ { role: TEXT_LEAF, name: "Toy Story 3" } ] }, + { CELL: [ + { role: STATICTEXT, name: "Released" }, + { role: TEXT_LEAF, name: "2010" }, + ] }, + { CELL: [ + { role: STATICTEXT, name: "Studio" }, + { role: TEXT_LEAF, name: "Disney Pixar" }, + ] }, + { CELL: [ + { role: STATICTEXT, name: "Worldwide Gross" }, + { role: TEXT_LEAF, name: "$1,063,171,911" }, + ] }, + { CELL: [ + { role: STATICTEXT, name: "Domestic Gross" }, + { role: TEXT_LEAF, name: "$415,004,880" }, + ] }, + { CELL: [ + { role: STATICTEXT, name: "Foreign Gross" }, + { role: TEXT_LEAF, name: "$648,167,031" }, + ] }, + { CELL: [ + { role: STATICTEXT, name: "Budget" }, + { role: TEXT_LEAF, name: "$200,000,000" }, + ]}, + ] }, + { ROW: [ + { ROWHEADER: [ { role: TEXT_LEAF, name: "Shrek Forever After" } ] }, + { CELL: [ + { role: STATICTEXT, name: "Released" }, + { role: TEXT_LEAF, name: "2010" }, + ] }, + { CELL: [ + { role: STATICTEXT, name: "Studio" }, + { role: TEXT_LEAF, name: "Dreamworks" }, + ] }, + { CELL: [ + { role: STATICTEXT, name: "Worldwide Gross" }, + { role: TEXT_LEAF, name: "$752,600,867" }, + ] }, + { CELL: [ + { role: STATICTEXT, name: "Domestic Gross" }, + { role: TEXT_LEAF, name: "$238,736,787" }, + ] }, + { CELL: [ + { role: STATICTEXT, name: "Foreign Gross" }, + { role: TEXT_LEAF, name: "$513,864,080" }, + ] }, + { CELL: [ + { role: STATICTEXT, name: "Budget" }, + { role: TEXT_LEAF, name: "$165,000,000" }, + ] }, + ] }, + ] }; + + testAccessibleTree("table", accTree); + + SimpleTest.finish(); +} +SimpleTest.waitForExplicitFinish(); +addA11yLoadEvent(doTest); +</script> +</head> + +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <table class="responsive-table" id="table"> + <caption>Top 10 Grossing Animated Films of All Time</caption> + <thead> + <tr> + <th scope="col">Film Title</th> + <th scope="col">Released</th> + <th scope="col">Studio</th> + <th scope="col">Worldwide Gross</th> + <th scope="col">Domestic Gross</th> + <th scope="col">Foreign Gross</th> + <th scope="col">Budget</th> + </tr> + </thead> + <tfoot> + <tr> + <td colspan="7">Sources: <a href="http://en.wikipedia.org/wiki/List_of_highest-grossing_animated_films" rel="external">Wikipedia</a> & <a href="http://www.boxofficemojo.com/genres/chart/?id=animation.htm" rel="external">Box Office Mojo</a>. Data is current as of March 12, 2014</td> + </tr> + </tfoot> + <tbody> + <tr> + <th scope="row">Toy Story 3</th> + <td data-title="Released">2010</td> + <td data-title="Studio">Disney Pixar</td> + <td data-title="Worldwide Gross" data-type="currency">$1,063,171,911</td> + <td data-title="Domestic Gross" data-type="currency">$415,004,880</td> + <td data-title="Foreign Gross" data-type="currency">$648,167,031</td> + <td data-title="Budget" data-type="currency">$200,000,000</td> + </tr> + <tr> + <th scope="row">Shrek Forever After</th> + <td data-title="Released">2010</td> + <td data-title="Studio">Dreamworks</td> + <td data-title="Worldwide Gross" data-type="currency">$752,600,867</td> + <td data-title="Domestic Gross" data-type="currency">$238,736,787</td> + <td data-title="Foreign Gross" data-type="currency">$513,864,080</td> + <td data-title="Budget" data-type="currency">$165,000,000</td> + </tr> + </tbody> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_tree.xhtml b/accessible/tests/mochitest/tree/test_tree.xhtml new file mode 100644 index 0000000000..e22b3faa9d --- /dev/null +++ b/accessible/tests/mochitest/tree/test_tree.xhtml @@ -0,0 +1,182 @@ +<?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="Accessible XUL tree hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Accessible tree testers + + function getTreeItemAccTree(aTableRole, acolumnCount) + { + var treeItemRole; + switch (aTableRole) { + case ROLE_LIST: + treeItemRole = ROLE_LISTITEM; + break; + case ROLE_OUTLINE: + treeItemRole = ROLE_OUTLINEITEM; + break; + case ROLE_TABLE: case ROLE_TREE_TABLE: + treeItemRole = ROLE_ROW; + break; + } + + var accTree = { + role: treeItemRole, + children: [] + }; + + if (aTableRole == ROLE_TABLE || aTableRole == ROLE_TREE_TABLE) { + for (var idx = 0; idx < acolumnCount; idx++) { + var cellAccTree = { + role: ROLE_GRID_CELL, + children: [] + }; + accTree.children.push(cellAccTree); + } + } + + return accTree; + } + + function testAccessibleTreeFor(aTree, aRole) + { + var accTreeForColumns = { + role: ROLE_LIST, + children: [] + }; + + var accTreeForTree = { + role: aRole, + children: [ + accTreeForColumns + ] + }; + + var view = aTree.view; + var columnCount = aTree.columns.count; + + for (let idx = 0; idx < columnCount; idx++) + accTreeForColumns.children.push({ COLUMNHEADER: [ ] }); + if (!aTree.hasAttribute("hidecolumnpicker")) { + accTreeForColumns.children.push({ PUSHBUTTON: [ ] }); + accTreeForColumns.children.push({ MENUPOPUP: [ ] }); + } + + for (let idx = 0; idx < view.rowCount; idx++) + accTreeForTree.children.push(getTreeItemAccTree(aRole, columnCount)); + + testAccessibleTree(aTree, accTreeForTree); + } + + /** + * Event queue invoker object to test accessible tree for XUL tree element. + */ + function treeChecker(aID, aView, aRole) + { + this.DOMNode = getNode(aID); + + this.invoke = function invoke() + { + this.DOMNode.view = aView; + } + this.check = function check(aEvent) + { + testAccessibleTreeFor(this.DOMNode, aRole); + } + this.getID = function getID() + { + return "Tree testing of " + aID; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "debug"; + var gQueue = null; + + function doTest() + { + gQueue = new eventQueue(EVENT_REORDER); + + gQueue.push(new treeChecker("list", new nsTableTreeView(3), ROLE_LIST)); + gQueue.push(new treeChecker("tree", new nsTreeTreeView(), ROLE_OUTLINE)); + gQueue.push(new treeChecker("table", new nsTableTreeView(3), ROLE_TABLE)); + gQueue.push(new treeChecker("treetable", new nsTreeTreeView(), ROLE_TREE_TABLE)); + + gQueue.invoke(); // Will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727" + title="Reorganize implementation of XUL tree accessibility"> + Mozilla Bug 503727 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tree id="list" flex="1" hidecolumnpicker="true"> + <treecols> + <treecol id="col" flex="1" hideheader="true"/> + </treecols> + <treechildren/> + </tree> + + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + </treecols> + <treechildren/> + </tree> + + <tree id="table" flex="1"> + <treecols> + <treecol id="col1" flex="1" label="column"/> + <treecol id="col2" flex="1" label="column 2"/> + </treecols> + <treechildren/> + </tree> + + <tree id="treetable" flex="1"> + <treecols> + <treecol id="col1" flex="1" primary="true" label="column"/> + <treecol id="col2" flex="1" label="column 2"/> + </treecols> + <treechildren/> + </tree> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/tree/test_txtcntr.html b/accessible/tests/mochitest/tree/test_txtcntr.html new file mode 100644 index 0000000000..4c44701a41 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_txtcntr.html @@ -0,0 +1,234 @@ +<!DOCTYPE html> +<html> + +<head> + <title>HTML text containers tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + var accTree = { + role: ROLE_SECTION, + children: [ + { // text child + role: ROLE_TEXT_LEAF, + children: [], + }, + ], + }; + + testAccessibleTree("c1", accTree); + testAccessibleTree("c2", accTree); + + accTree = { + role: ROLE_SECTION, + children: [ + { + role: ROLE_TEXT_LEAF, + name: "Hello1", + }, + { + role: ROLE_WHITESPACE, + }, + { + role: ROLE_TEXT_LEAF, + name: "Hello2", + }, + { + role: ROLE_SEPARATOR, + }, + { + role: ROLE_TEXT_LEAF, + name: "Hello3 ", + }, + { + role: ROLE_PARAGRAPH, + children: [ + { + role: ROLE_TEXT_LEAF, + name: "Hello4 ", + }, + ], + }, + ], + }; + + testAccessibleTree("c3", accTree); + + // contentEditable div + accTree = { + role: ROLE_SECTION, + children: [ + { + role: ROLE_TEXT_LEAF, + name: "helllo ", + }, + { + role: ROLE_PARAGRAPH, + children: [ + { + role: ROLE_TEXT_LEAF, + name: "blabla", + }, + ], + }, + { + role: ROLE_TEXT_LEAF, + name: "hello ", + }, + ], + }; + + testAccessibleTree("c4", accTree); + + // blockquote + accTree = { + role: ROLE_SECTION, + children: [ + { // block quote + role: ROLE_BLOCKQUOTE, + children: [ + { // text child + role: ROLE_TEXT_LEAF, + name: "Hello", + children: [], + }, + ], + }, + ], + }; + + testAccessibleTree("c5", accTree); + + // abbreviation tag + accTree = { + role: ROLE_SECTION, + children: [ + { // text leaf + role: ROLE_TEXT_LEAF, + name: "This ", + children: [], + }, + { // abbr tag + role: ROLE_TEXT, + name: "accessibility", + children: [ + { // text leaf with actual text + role: ROLE_TEXT_LEAF, + name: "a11y", + children: [], + }, + ], + }, + { // text leaf + role: ROLE_TEXT_LEAF, + name: " test", + children: [], + }, + ], + }; + + testAccessibleTree("c6", accTree); + + // acronym tag + accTree = { + role: ROLE_SECTION, + children: [ + { // text leaf + role: ROLE_TEXT_LEAF, + name: "This ", + children: [], + }, + { // acronym tag + role: ROLE_TEXT, + name: "personal computer", + children: [ + { // text leaf with actual text + role: ROLE_TEXT_LEAF, + name: "PC", + children: [], + }, + ], + }, + { // text leaf + role: ROLE_TEXT_LEAF, + name: " is broken", + children: [], + }, + ], + }; + + testAccessibleTree("c7", accTree); + + // only whitespace between images should be exposed + accTree = { + SECTION: [ + { GRAPHIC: [] }, + { TEXT_LEAF: [] }, + { GRAPHIC: [] }, + ], + }; + testAccessibleTree("c8", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="overflowed content doesn't expose child text accessibles" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=489306"> + Mozilla Bug 489306</a> + <a target="_blank" + title="Create child accessibles for text controls from native anonymous content" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=542824"> + Mozilla Bug 542824</a> + <a target="_blank" + title="Update accessible tree on content insertion after layout" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=498015"> + Mozilla Bug 498015</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="c1" style="width: 100px; height: 100px; overflow: auto;"> + 1hellohello 2hellohello 3hellohello 4hellohello 5hellohello 6hellohello 7hellohello + </div> + <div id="c2"> + 1hellohello 2hellohello 3hellohello 4hellohello 5hellohello 6hellohello 7hellohello + </div> + <div id="c3"> + Hello1<br> + Hello2<hr> + Hello3 + <p> + Hello4 + </p> + </div> + <div id="c4" contentEditable="true"> + helllo <p>blabla</p> hello + </div> + <div id="c5"><blockquote>Hello</blockquote></div> + <div id="c6">This <abbr title="accessibility">a11y</abbr> test</div> + <div id="c7">This <acronym title="personal computer">PC</acronym> is broken</div> + + <!-- Whitespace between images should be exposed. Whitespace between the + div and img tags will be inconsistent depending on the image cache + state and what optimizations layout was able to apply. --> + <div id="c8"><img src="../moz.png"> <img src="../moz.png"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_txtctrl.html b/accessible/tests/mochitest/tree/test_txtctrl.html new file mode 100644 index 0000000000..ee66fbf801 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_txtctrl.html @@ -0,0 +1,171 @@ +<!DOCTYPE html> +<html> + +<head> + <title>HTML text controls tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script type="application/javascript"> + function doTest() { + // editable div + var accTree = { + role: ROLE_SECTION, + children: [ + { // text child + role: ROLE_TEXT_LEAF, + children: [], + }, + ], + }; + + testAccessibleTree("txc1", accTree); + + // input@type="text", value + accTree = { + role: ROLE_ENTRY, + children: [ + { // text child + role: ROLE_TEXT_LEAF, + children: [], + }, + ], + }; + + testAccessibleTree("txc2", accTree); + + // input@type="text", no value + accTree = + { ENTRY: [ ] }; + + testAccessibleTree("txc3", accTree); + + // textarea + accTree = { + role: ROLE_ENTRY, + children: [ + { + role: ROLE_TEXT_LEAF, // hello1\nhello2 text + }, + ], + }; + + testAccessibleTree("txc4", accTree); + + // input@type="password" + accTree = { + role: ROLE_PASSWORD_TEXT, + children: [ + { + role: ROLE_TEXT_LEAF, + children: [], + }, + ], + }; + + testAccessibleTree("txc5", accTree); + + // input@type="tel", value + accTree = { + role: ROLE_ENTRY, + children: [ + { // text child + role: ROLE_TEXT_LEAF, + children: [], + }, + ], + }; + + testAccessibleTree("txc6", accTree); + + // input@type="email", value + accTree = { + role: ROLE_EDITCOMBOBOX, // Because of list attribute + children: [ + { // text child + role: ROLE_TEXT_LEAF, + children: [], + }, + ], + }; + + testAccessibleTree("txc7", accTree); + + // input@type="search", value + accTree = { + role: ROLE_EDITCOMBOBOX, // Because of list attribute + children: [ + { // text child + role: ROLE_TEXT_LEAF, + children: [], + }, + ], + }; + + testAccessibleTree("txc8", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="overflowed content doesn't expose child text accessibles" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=489306"> + Mozilla Bug 489306 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=542824" + title="Create child accessibles for text controls from native anonymous content"> + Mozilla Bug 542824 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652" + title="Make sure accessible tree is correct when rendered text is changed"> + Mozilla Bug 625652 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="txc1" contentEditable="true"> + 1hellohello + </div> + <input id="txc2" value="hello"> + <input id="txc3"> + <textarea id="txc4"> + hello1 + hello2 + </textarea> + <input id="txc5" type="password" value="hello"> + <input id="txc6" type="tel" value="4167771234"> + + Email Address: + <input id="txc7" type="email" list="contacts" value="xyzzy"> + <datalist id="contacts"> + <option>xyzzy@plughs.com</option> + <option>nobody@mozilla.org</option> + </datalist> + + </br>Search for: + <input id="txc8" type="search" list="searchhisty" value="Gamma"> + <datalist id="searchhisty"> + <option>Gamma Rays</option> + <option>Gamma Ray Bursts</option> + </datalist> + +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_txtctrl.xhtml b/accessible/tests/mochitest/tree/test_txtctrl.xhtml new file mode 100644 index 0000000000..ff3d4977f0 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_txtctrl.xhtml @@ -0,0 +1,86 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL textbox and textarea hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //gA11yEventDumpToConsole = true; + //enableLogging("tree,verbose"); // debug stuff + + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // search textbox + let accTree = + { GROUPING: [ + { ENTRY: [ { TEXT_LEAF: [] } ] }, + ] }; + testAccessibleTree("txc_search", accTree); + + ////////////////////////////////////////////////////////////////////////// + // search textbox with search button + + if (MAC) { + accTree = + { GROUPING: [ + { ENTRY: [ { TEXT_LEAF: [] } ] }, + ] }; + } else { + accTree = + { GROUPING: [ + { ENTRY: [ { TEXT_LEAF: [] } ] }, + { PUSHBUTTON: [] }, + ] }; + } + + testAccessibleTree("txc_search_searchbutton", accTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=542824" + title="Create child accessibles for text controls from native anonymous content"> + Mozilla Bug 542824 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <box role="group" id="txc_search"> + <search-textbox value="hello"/> + </box> + <box role="group" id="txc_search_searchbutton"> + <search-textbox searchbutton="true" value="hello"/> + </box> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/tree/wnd.xhtml b/accessible/tests/mochitest/tree/wnd.xhtml new file mode 100644 index 0000000000..3b87cb5e0d --- /dev/null +++ b/accessible/tests/mochitest/tree/wnd.xhtml @@ -0,0 +1,8 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Empty Window"> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/a11y.ini b/accessible/tests/mochitest/treeupdate/a11y.ini new file mode 100644 index 0000000000..a40ef0ce05 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/a11y.ini @@ -0,0 +1,46 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + !/accessible/tests/mochitest/moz.png + +[test_ariadialog.html] +[test_ariahidden.html] +[test_ariaowns.html] +[test_bug852150.xhtml] +[test_bug883708.xhtml] +[test_bug884251.xhtml] +[test_bug895082.html] +[test_bug1040735.html] +[test_bug1175913.html] +[test_bug1189277.html] +[test_bug1276857.html] +support-files = test_bug1276857_subframe.html +[test_canvas.html] +[test_contextmenu.xhtml] +[test_cssoverflow.html] +[test_deck.xhtml] +[test_delayed_removal.html] +[test_doc.html] +[test_gencontent.html] +[test_general.html] +[test_hidden.html] +[test_imagemap.html] +[test_inert.html] +[test_inner_reorder.html] +[test_list.html] +[test_list_editabledoc.html] +[test_list_style.html] +[test_listbox.xhtml] +[test_menu.xhtml] +[test_menubutton.xhtml] +[test_optgroup.html] +[test_recreation.html] +[test_select.html] +[test_shadow_slots.html] +[test_shutdown.xhtml] +[test_table.html] +[test_textleaf.html] +[test_tooltip.xhtml] +[test_visibility.html] +[test_whitespace.html] diff --git a/accessible/tests/mochitest/treeupdate/test_ariadialog.html b/accessible/tests/mochitest/treeupdate/test_ariadialog.html new file mode 100644 index 0000000000..0b7f4bbb56 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_ariadialog.html @@ -0,0 +1,113 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Table creation in ARIA dialog test</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function showARIADialog(aID) { + this.node = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.node), + ]; + + this.invoke = function showARIADialog_invoke() { + getNode("dialog").style.display = "block"; + getNode("table").style.visibility = "visible"; + getNode("a").textContent = "link"; + getNode("input").value = "hello"; + getNode("input").focus(); + }; + + this.finalCheck = function showARIADialog_finalCheck() { + var tree = { + role: ROLE_DIALOG, + children: [ + { + role: ROLE_PUSHBUTTON, + children: [ { role: ROLE_TEXT_LEAF } ], + }, + { + role: ROLE_ENTRY, + }, + ], + }; + testAccessibleTree(aID, tree); + }; + + this.getID = function showARIADialog_getID() { + return "show ARIA dialog"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + // enableLogging("tree"); + gQueue = new eventQueue(); + + // make the accessible an inaccessible + gQueue.push(new showARIADialog("dialog")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="dialog" role="dialog" style="display: none;"> + <table id="table" role="presentation" + style="display: block; position: absolute; top: 88px; left: 312.5px; z-index: 10010; visibility: hidden;"> + <tbody> + <tr> + <td role="presentation"> + <div role="presentation"> + <a id="a" role="button">text</a> + </div> + <input id="input"> + </td> + </tr> + </tbody> + </table> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_ariahidden.html b/accessible/tests/mochitest/treeupdate/test_ariahidden.html new file mode 100644 index 0000000000..302465b59f --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_ariahidden.html @@ -0,0 +1,118 @@ +<html> + +<head> + <title>aria-hidden tree update tests</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function t1_setARIAHidden() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "t1"), + ]; + + this.invoke = function t1_setARIAHidden_invoke() { + getNode("t1_child").setAttribute("aria-hidden", "true"); + }; + + this.finalCheck = function t1_setARIAHidden_finalCheck() { + ok(!isAccessible("t1_child"), "No accessible for aria-hidden"); + }; + + this.getID = function t1_setARIAHidden_getID() { + return "aria-hidden set to true"; + }; + } + + function t1_removeARIAHidden() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "t1"), + ]; + + this.invoke = function t1_removeARIAHidden_invoke() { + getNode("t1_child").removeAttribute("aria-hidden"); + }; + + this.finalCheck = function t1_removeARIAHidden_finalCheck() { + ok(isAccessible("t1_child"), "No aria-hidden, has to be accessible"); + }; + + this.getID = function t1_removeARIAHidden_getID() { + return "remove aria-hidden"; + }; + } + + function t2_setARIAHidden() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "t2"), + ]; + + this.invoke = function t2_setARIAHidden_invoke() { + getNode("t2_child").setAttribute("aria-hidden", "true"); + }; + + this.finalCheck = function t2_setARIAHidden_finalCheck() { + testAccessibleTree("t2", { SECTION: []}); + }; + + this.getID = function t2_setARIAHidden_getID() { + return "t2: set aria-hidden"; + }; + } + + function t2_insertUnderARIAHidden() { + this.eventSeq = [ + new unexpectedInvokerChecker(EVENT_REORDER, "t2"), + ]; + + this.invoke = function t2_insertUnderARIAHidden_invoke() { + getNode("t2_child").innerHTML = "<input>"; + }; + + this.finalCheck = function t2_insertUnderARIAHidden_finalCheck() { + testAccessibleTree("t2", { SECTION: []}); + }; + + this.getID = function t2_insertUnderARIAHidden_getID() { + return "t2: insert under aria-hidden"; + }; + } + + // gA11yEventDumpToConsole = true; + function doTests() { + ok(!isAccessible("t1_child"), "No accessible for aria-hidden"); + + const gQueue = new eventQueue(); + gQueue.push(new t1_removeARIAHidden()); + gQueue.push(new t1_setARIAHidden()); + gQueue.push(new t2_setARIAHidden()); + gQueue.push(new t2_insertUnderARIAHidden()); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="t1"><div id="t1_child" aria-hidden="true">Hi</div><div>there</div></div> + <div id="t2"> + <span id="t2_child">hoho</span> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_ariaowns.html b/accessible/tests/mochitest/treeupdate/test_ariaowns.html new file mode 100644 index 0000000000..89d49efd5f --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html @@ -0,0 +1,851 @@ +<!DOCTYPE html> +<html> + +<head> + <title>@aria-owns attribute testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + // ////////////////////////////////////////////////////////////////////////// + + function changeARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_button")), + // no hide for t1_subdiv because it is contained by hidden t1_checkbox + new invokerChecker(EVENT_HIDE, getNode("t1_checkbox")), + new invokerChecker(EVENT_SHOW, getNode("t1_checkbox")), + new invokerChecker(EVENT_SHOW, getNode("t1_button")), + new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")), + new invokerChecker(EVENT_REORDER, getNode("t1_container")), + ]; + + this.invoke = function setARIAOwns_invoke() { + // children are swapped by ARIA owns + var tree = + { SECTION: [ + { CHECKBUTTON: [ + { SECTION: [] }, + ] }, + { PUSHBUTTON: [ ] }, + ] }; + testAccessibleTree("t1_container", tree); + + getNode("t1_container"). + setAttribute("aria-owns", "t1_button t1_subdiv"); + }; + + this.finalCheck = function setARIAOwns_finalCheck() { + // children are swapped again, button and subdiv are appended to + // the children. + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // checkbox, native order + { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own + { SECTION: [ ] }, // subdiv from the subtree, ARIA owned + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function setARIAOwns_getID() { + return "Change @aria-owns attribute"; + }; + } + + function removeARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_button")), + new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), + new orderChecker(), + new asyncInvokerChecker(EVENT_SHOW, getNode("t1_button")), + new asyncInvokerChecker(EVENT_SHOW, getNode("t1_subdiv")), + new orderChecker(), + new invokerChecker(EVENT_REORDER, getNode("t1_container")), + new unexpectedInvokerChecker(EVENT_REORDER, getNode("t1_checkbox")), + ]; + + this.invoke = function removeARIAOwns_invoke() { + getNode("t1_container").removeAttribute("aria-owns"); + }; + + this.finalCheck = function removeARIAOwns_finalCheck() { + // children follow the DOM order + var tree = + { SECTION: [ + { PUSHBUTTON: [ ] }, + { CHECKBUTTON: [ + { SECTION: [] }, + ] }, + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function removeARIAOwns_getID() { + return "Remove @aria-owns attribute"; + }; + } + + function setARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_button")), + new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), + new invokerChecker(EVENT_SHOW, getNode("t1_button")), + new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")), + new invokerChecker(EVENT_REORDER, getNode("t1_container")), + ]; + + this.invoke = function setARIAOwns_invoke() { + getNode("t1_container"). + setAttribute("aria-owns", "t1_button t1_subdiv"); + }; + + this.finalCheck = function setARIAOwns_finalCheck() { + // children are swapped again, button and subdiv are appended to + // the children. + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // checkbox + { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own + { SECTION: [ ] }, // subdiv from the subtree, ARIA owned + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function setARIAOwns_getID() { + return "Set @aria-owns attribute"; + }; + } + + function addIdToARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_group")), + new invokerChecker(EVENT_SHOW, getNode("t1_group")), + new invokerChecker(EVENT_REORDER, document), + ]; + + this.invoke = function addIdToARIAOwns_invoke() { + getNode("t1_container"). + setAttribute("aria-owns", "t1_button t1_subdiv t1_group"); + }; + + this.finalCheck = function addIdToARIAOwns_finalCheck() { + // children are swapped again, button and subdiv are appended to + // the children. + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // t1_checkbox + { PUSHBUTTON: [ ] }, // button, t1_button + { SECTION: [ ] }, // subdiv from the subtree, t1_subdiv + { GROUPING: [ ] }, // group from outside, t1_group + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function addIdToARIAOwns_getID() { + return "Add id to @aria-owns attribute value"; + }; + } + + function appendEl() { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getNode, "t1_child3"), + new invokerChecker(EVENT_REORDER, getNode("t1_container")), + ]; + + this.invoke = function appendEl_invoke() { + var div = document.createElement("div"); + div.setAttribute("id", "t1_child3"); + div.setAttribute("role", "radio"); + getNode("t1_container").appendChild(div); + }; + + this.finalCheck = function appendEl_finalCheck() { + // children are invalidated, they includes aria-owns swapped kids and + // newly inserted child. + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // existing explicit, t1_checkbox + { RADIOBUTTON: [ ] }, // new explicit, t1_child3 + { PUSHBUTTON: [ ] }, // ARIA owned, t1_button + { SECTION: [ ] }, // ARIA owned, t1_subdiv + { GROUPING: [ ] }, // ARIA owned, t1_group + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function appendEl_getID() { + return "Append child under @aria-owns element"; + }; + } + + function removeEl() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")), + new invokerChecker(EVENT_REORDER, getNode("t1_container")), + ]; + + this.invoke = function removeEl_invoke() { + // remove a container of t1_subdiv + getNode("t1_span").remove(); + }; + + this.finalCheck = function removeEl_finalCheck() { + // subdiv should go away + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // explicit, t1_checkbox + { RADIOBUTTON: [ ] }, // explicit, t1_child3 + { PUSHBUTTON: [ ] }, // ARIA owned, t1_button + { GROUPING: [ ] }, // ARIA owned, t1_group + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function removeEl_getID() { + return "Remove a container of ARIA owned element"; + }; + } + + function removeId() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_group")), + new invokerChecker(EVENT_SHOW, getNode("t1_group")), + new invokerChecker(EVENT_REORDER, document), + ]; + + this.invoke = function removeId_invoke() { + getNode("t1_group").removeAttribute("id"); + }; + + this.finalCheck = function removeId_finalCheck() { + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, + { RADIOBUTTON: [ ] }, + { PUSHBUTTON: [ ] }, // ARIA owned, t1_button + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function removeId_getID() { + return "Remove ID from ARIA owned element"; + }; + } + + function setId() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("t1_grouptmp")), + new invokerChecker(EVENT_SHOW, getNode("t1_grouptmp")), + new invokerChecker(EVENT_REORDER, document), + ]; + + this.invoke = function setId_invoke() { + getNode("t1_grouptmp").setAttribute("id", "t1_group"); + }; + + this.finalCheck = function setId_finalCheck() { + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, + { RADIOBUTTON: [ ] }, + { PUSHBUTTON: [ ] }, // ARIA owned, t1_button + { GROUPING: [ ] }, // ARIA owned, t1_group, previously t1_grouptmp + ] }; + testAccessibleTree("t1_container", tree); + }; + + this.getID = function setId_getID() { + return "Set ID that is referred by ARIA owns"; + }; + } + + /** + * Remove an accessible DOM element containing an element referred by + * ARIA owns. + */ + function removeA11eteiner() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode("t2_container1")), + ]; + + this.invoke = function removeA11eteiner_invoke() { + var tree = + { SECTION: [ + { CHECKBUTTON: [ ] }, // ARIA owned, 't2_owned' + ] }; + testAccessibleTree("t2_container1", tree); + + getNode("t2_container2").removeChild(getNode("t2_container3")); + }; + + this.finalCheck = function removeA11eteiner_finalCheck() { + var tree = + { SECTION: [ + ] }; + testAccessibleTree("t2_container1", tree); + }; + + this.getID = function removeA11eteiner_getID() { + return "Remove an accessible DOM element containing an element referred by ARIA owns"; + }; + } + + /** + * Attempt to steal an element from other ARIA owns element. This should + * not be possible. The only child that will get owned into this + * container is a previously not aria-owned one. + */ + function stealFromOtherARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode("t3_container3")), + ]; + + this.invoke = function stealFromOtherARIAOwns_invoke() { + getNode("t3_container3").setAttribute("aria-owns", "t3_child t3_child2"); + }; + + this.finalCheck = function stealFromOtherARIAOwns_finalCheck() { + var tree = + { SECTION: [ + { CHECKBUTTON: [ + ] }, + ] }; + testAccessibleTree("t3_container1", tree); + + tree = + { SECTION: [ + ] }; + testAccessibleTree("t3_container2", tree); + + tree = + { SECTION: [ + { CHECKBUTTON: [ + ] }, + ] }; + testAccessibleTree("t3_container3", tree); + }; + + this.getID = function stealFromOtherARIAOwns_getID() { + return "Steal an element from other ARIA owns element"; + }; + } + + function appendElToRecacheChildren() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode("t3_container3")), + ]; + + this.invoke = function appendElToRecacheChildren_invoke() { + var div = document.createElement("div"); + div.setAttribute("role", "radio"); + getNode("t3_container3").appendChild(div); + }; + + this.finalCheck = function appendElToRecacheChildren_finalCheck() { + var tree = + { SECTION: [ + ] }; + testAccessibleTree("t3_container2", tree); + + tree = + { SECTION: [ + { RADIOBUTTON: [ ] }, + { CHECKBUTTON: [ ] }, // ARIA owned + ] }; + testAccessibleTree("t3_container3", tree); + }; + + this.getID = function appendElToRecacheChildren_getID() { + return "Append a child under @aria-owns element to trigger children recache"; + }; + } + + function showHiddenElement() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode("t4_container1")), + ]; + + this.invoke = function showHiddenElement_invoke() { + var tree = + { SECTION: [ + { RADIOBUTTON: [] }, + ] }; + testAccessibleTree("t4_container1", tree); + + getNode("t4_child1").style.display = "block"; + }; + + this.finalCheck = function showHiddenElement_finalCheck() { + var tree = + { SECTION: [ + { CHECKBUTTON: [] }, + { RADIOBUTTON: [] }, + ] }; + testAccessibleTree("t4_container1", tree); + }; + + this.getID = function showHiddenElement_getID() { + return "Show hidden ARIA owns referred element"; + }; + } + + function rearrangeARIAOwns(aContainer, aAttr, aIdList, aRoleList) { + this.eventSeq = []; + for (let id of aIdList) { + this.eventSeq.push(new invokerChecker(EVENT_HIDE, getNode(id))); + } + + for (let id of aIdList) { + this.eventSeq.push(new invokerChecker(EVENT_SHOW, getNode(id))); + } + this.eventSeq.push(new invokerChecker(EVENT_REORDER, getNode(aContainer))); + + this.invoke = function rearrangeARIAOwns_invoke() { + getNode(aContainer).setAttribute("aria-owns", aAttr); + }; + + this.finalCheck = function rearrangeARIAOwns_finalCheck() { + var tree = { SECTION: [ ] }; + for (var role of aRoleList) { + var ch = {}; + ch[role] = []; + tree.SECTION.push(ch); + } + testAccessibleTree(aContainer, tree); + }; + + this.getID = function rearrangeARIAOwns_getID() { + return `Rearrange @aria-owns attribute to '${aAttr}'`; + }; + } + + function removeNotARIAOwnedEl(aContainer, aChild) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aContainer), + ]; + + this.invoke = function removeNotARIAOwnedEl_invoke() { + var tree = { + SECTION: [ + { TEXT_LEAF: [ ] }, + { GROUPING: [ ] }, + ], + }; + testAccessibleTree(aContainer, tree); + + getNode(aContainer).removeChild(getNode(aChild)); + }; + + this.finalCheck = function removeNotARIAOwnedEl_finalCheck() { + var tree = { + SECTION: [ + { GROUPING: [ ] }, + ], + }; + testAccessibleTree(aContainer, tree); + }; + + this.getID = function removeNotARIAOwnedEl_getID() { + return `remove not ARIA owned child`; + }; + } + + function setARIAOwnsOnElToRemove(aParent, aChild) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getAccessible(aParent)), + ]; + + this.invoke = function setARIAOwnsOnElToRemove_invoke() { + getNode(aChild).setAttribute("aria-owns", "no_id"); + getNode(aParent).removeChild(getNode(aChild)); + getNode(aParent).remove(); + }; + + this.getID = function setARIAOwnsOnElToRemove_getID() { + return `set ARIA owns on an element, and then remove it, and then remove its parent`; + }; + } + + /** + * Set ARIA owns on inaccessible span element that contains + * accessible children. This will move children from the container for + * the span. + */ + function test8() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "t8_container"), + ]; + + this.invoke = function test8_invoke() { + var tree = + { SECTION: [ + { PUSHBUTTON: [] }, + { ENTRY: [] }, + { ENTRY: [] }, + { ENTRY: [] }, + ] }; + testAccessibleTree("t8_container", tree); + + getNode(t8_container).setAttribute("aria-owns", "t8_span t8_button"); + }; + + this.finalCheck = function test8_finalCheck() { + var tree = + { SECTION: [ + { TEXT: [ + { ENTRY: [] }, + { ENTRY: [] }, + { ENTRY: [] }, + ] }, + { PUSHBUTTON: [] }, + ] }; + testAccessibleTree("t8_container", tree); + }; + + this.getID = function test8_getID() { + return `Set ARIA owns on inaccessible span element that contains accessible children`; + }; + } + + function test9_prepare() { + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, () => { + return getNode("t9_container").contentDocument; + }), + ]; + + this.invoke = () => { + // The \ before the final /script avoids the script from being terminated + // by the html parser. + getNode("t9_container").src = `data:text/html, + <html><body></body> + <script> + let el = document.createElement('div'); + el.id = 'container'; + el.innerHTML = "<input id='input'>"; + document.documentElement.appendChild(el); + <\/script></html>`; + }; + + this.finalCheck = () => { + var tree = + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { SECTION: [ + { ENTRY: [] }, + ] }, + ] }, + ] }; + testAccessibleTree("t9_container", tree); + }; + + this.getID = () => { + return `Set ARIA owns on a document (part1)`; + }; + } + + function test9_setARIAOwns() { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, () => { + let doc = getNode("t9_container").contentDocument; + return doc && doc.getElementById("input"); + }), + ]; + + this.invoke = () => { + let doc = getNode("t9_container").contentDocument; + doc.body.setAttribute("aria-owns", "input"); + }; + + this.finalCheck = () => { + var tree = + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { SECTION: [] }, + { ENTRY: [] }, + ] }, + ] }; + testAccessibleTree("t9_container", tree); + }; + + this.getID = () => { + return `Set ARIA owns on a document (part2)`; + }; + } + + function test9_finish() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, () => { + return getNode("t9_container").contentDocument; + }), + ]; + + this.invoke = () => { + // trigger a tree update. + let doc = getNode("t9_container").contentDocument; + doc.body.appendChild(doc.createElement("p")); + }; + + this.finalCheck = () => { + var tree = + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { PARAGRAPH: [] }, + { SECTION: [] }, + { ENTRY: [] }, + ] }, + ] }; + testAccessibleTree("t9_container", tree); + }; + + this.getID = () => { + return `Set ARIA owns on a document (part3)`; + }; + } + + /** + * Put ARIA owned child back when ARIA owner removed. + */ + function test10_removeARIAOwner() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getAccessible("t10_owner")), + ]; + + this.invoke = () => { + let tree = + { SECTION: [ // t10_container + { SECTION: [ // t10_owner + { ENTRY: [] }, // t10_child + ] }, + ] }; + testAccessibleTree("t10_container", tree); + + getNode("t10_owner").remove(); + }; + + this.getID = () => { + return "Put aria owned child back when aria owner removed"; + }; + } + + function test10_finishTest() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "t10_container"), + ]; + + this.invoke = () => { + // trigger a tree update. + getNode("t10_container").append(document.createElement("p")); + }; + + this.finalCheck = () => { + let tree = + { SECTION: [ // t10_container + // { ENTRY: [] }, // t10_child + { PARAGRAPH: [] }, + ] }; + testAccessibleTree("t10_container", tree); + todo(false, "Input accessible has be moved back in the tree"); + }; + + this.getID = () => { + return `Put aria owned child back when aria owner removed (finish test)`; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + // ////////////////////////////////////////////////////////////////////////// + + // gA11yEventDumpToConsole = true; + // enableLogging("tree,eventTree,verbose"); // debug stuff + + var gQueue = null; + + async function doTest() { + let PromEvents = {}; + Services.scriptloader.loadSubScript( + "chrome://mochitests/content/a11y/accessible/tests/mochitest/promisified-events.js", + PromEvents); + + gQueue = new eventQueue(); + let queueFinished = new Promise(resolve => { + gQueue.onFinish = function() { + resolve(); + return DO_NOT_FINISH_TEST; + }; + }); + + // test1 + gQueue.push(new changeARIAOwns()); + gQueue.push(new removeARIAOwns()); + gQueue.push(new setARIAOwns()); + gQueue.push(new addIdToARIAOwns()); + gQueue.push(new appendEl()); + gQueue.push(new removeEl()); + gQueue.push(new removeId()); + gQueue.push(new setId()); + + // test2 + gQueue.push(new removeA11eteiner()); + + // test3 + gQueue.push(new stealFromOtherARIAOwns()); + gQueue.push(new appendElToRecacheChildren()); + + // test4 + gQueue.push(new showHiddenElement()); + + // test5 + gQueue.push(new rearrangeARIAOwns( + "t5_container", "t5_checkbox t5_radio t5_button", + [ "t5_checkbox", "t5_radio", "t5_button" ], + [ "CHECKBUTTON", "RADIOBUTTON", "PUSHBUTTON" ])); + gQueue.push(new rearrangeARIAOwns( + "t5_container", "t5_radio t5_button t5_checkbox", + [ "t5_radio", "t5_button" ], + [ "RADIOBUTTON", "PUSHBUTTON", "CHECKBUTTON" ])); + + gQueue.push(new removeNotARIAOwnedEl("t6_container", "t6_span")); + + gQueue.push(new setARIAOwnsOnElToRemove("t7_parent", "t7_child")); + + gQueue.push(new test8()); + gQueue.push(new test9_prepare()); + gQueue.push(new test9_setARIAOwns()); + gQueue.push(new test9_finish()); + + gQueue.push(new test10_removeARIAOwner()); + gQueue.push(new test10_finishTest()); + + gQueue.invoke(); + await queueFinished; + + let owned = document.createElement('div'); + owned.id = 't11_child'; + owned.textContent = 'owned'; + let evtPromise = PromEvents.waitForEvent(EVENT_SHOW, "t11_child"); + getNode("t11_container").append(owned); + let evt = await evtPromise; + is(evt.accessible.parent.name, "t11_owner"); + + // Test owning an ancestor which isn't created yet. + testAccessibleTree("t12_container", { SECTION: [ // t12_container + { SECTION: [ // t12b + { SECTION: [] }, // t12c + ] }, + { SECTION: [] }, // t12d + ] }); + // Owning t12a would create a cycle, so we expect it to do nothing. + // We own t12d so we get an event when aria-owns relocation is complete. + evtPromise = PromEvents.waitForEvent(EVENT_SHOW, "t12d"); + getNode("t12c").setAttribute("aria-owns", "t12a t12d"); + await evtPromise; + testAccessibleTree("t12_container", { SECTION: [ // t12_container + { SECTION: [ // t12b + { SECTION: [ // t12c + { SECTION: [] }, // t12d + ] }, + ] }, + ] }); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + + </script> +</head> + +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="t1_container" aria-owns="t1_checkbox t1_button"> + <div role="button" id="t1_button"></div> + <div role="checkbox" id="t1_checkbox"> + <span id="t1_span"> + <div id="t1_subdiv"></div> + </span> + </div> + </div> + <div id="t1_group" role="group"></div> + <div id="t1_grouptmp" role="group"></div> + + <div id="t2_container1" aria-owns="t2_owned"></div> + <div id="t2_container2"> + <div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div> + </div> + + <div id="t3_container1" aria-owns="t3_child"></div> + <div id="t3_child" role="checkbox"></div> + <div id="t3_container2"> + <div id="t3_child2" role="checkbox"></div> + </div> + <div id="t3_container3"></div> + + <div id="t4_container1" aria-owns="t4_child1 t4_child2"></div> + <div id="t4_container2"> + <div id="t4_child1" style="display:none" role="checkbox"></div> + <div id="t4_child2" role="radio"></div> + </div> + + <div id="t5_container"> + <div role="button" id="t5_button"></div> + <div role="checkbox" id="t5_checkbox"></div> + <div role="radio" id="t5_radio"></div> + </div> + + <div id="t6_container" aria-owns="t6_fake"> + <span id="t6_span">hey</span> + </div> + <div id="t6_fake" role="group"></div> + + <div id="t7_container"> + <div id="t7_parent"> + <div id="t7_child"></div> + </div> + </div> + + <div id="t8_container"> + <input id="t8_button" type="button"><span id="t8_span"><input><input><input></span> + </div> + + <iframe id="t9_container"></iframe> + + <div id="t10_container"> + <div id="t10_owner" aria-owns="t10_child"></div> + <input id="t10_child"> + </div> + + <div id="t11_container" aria-label="t11_container"> + <div aria-owns="t11_child" aria-label="t11_owner"></div> + </div> + + <div id="t12_container"> + <span id="t12a"> + <div id="t12b" aria-owns="t12c"></div> + </span> + <div id="t12c"></div> + <div id="t12d"></div> + </div> +</body> + +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug1040735.html b/accessible/tests/mochitest/treeupdate/test_bug1040735.html new file mode 100644 index 0000000000..b7d0e472d0 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1040735.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<html> +<head> + <title>Adopt DOM node from anonymous subtree</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + + <script type="application/javascript"> + function doTest() { + document.body.appendChild(document.getElementById("mw_a")); + setTimeout(function() { ok(true, "no crash and assertions"); SimpleTest.finish(); }, 0); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1040735" + title="Bug 1040735 - DOM node reinsertion under anonymous content may trigger a11y child adoption"> + Bug 1040735</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <marquee> + <div id="mw_a" style="visibility: hidden;"> + <div style="visibility: visible;" id="mw_inside"></div> + </div> + </marquee> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug1175913.html b/accessible/tests/mochitest/treeupdate/test_bug1175913.html new file mode 100644 index 0000000000..1fe2720434 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1175913.html @@ -0,0 +1,95 @@ +<html> + +<head> + <title>Test hide/show events on event listener changes</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + function dummyListener() {} + + function testAddListener() { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getNode("parent")), + ]; + + this.invoke = function testAddListener_invoke() { + is(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that parent is not accessible."); + is(getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that child is not accessible."); + getNode("parent").addEventListener("click", dummyListener); + }; + + this.finalCheck = function testAddListener_finalCheck() { + var tree = { TEXT: [] }; + testAccessibleTree("parent", tree); + }; + + this.getID = function testAddListener_getID() { + return "Test that show event is sent when click listener is added"; + }; + } + + function testRemoveListener() { + this.eventSeq = [ + new unexpectedInvokerChecker(EVENT_HIDE, getNode("parent")), + ]; + + this.invoke = function testRemoveListener_invoke() { + getNode("parent").removeEventListener("click", dummyListener); + }; + + this.finalCheck = function testRemoveListener_finalCheck() { + ok(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC), + "Parent stays accessible after click event listener is removed"); + ok(!getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC), + "Child stays inaccessible"); + }; + + this.getID = function testRemoveListener_getID() { + return "Test that hide event is sent when click listener is removed"; + }; + } + + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + gQueue.push(new testAddListener()); + gQueue.push(new testRemoveListener()); + gQueue.invoke(); // SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175913" + title="Crash in mozilla::a11y::DocAccessibleParent::RemoveAccessible(ProxyAccessible* aAccessible)"> + Mozilla Bug 1175913 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <span id="parent"> + <span id="child"> + </span> + </span> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug1189277.html b/accessible/tests/mochitest/treeupdate/test_bug1189277.html new file mode 100644 index 0000000000..95efe5135a --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1189277.html @@ -0,0 +1,82 @@ +<html> + +<head> + <title>Test hide/show events for HTMLListBulletAccessibles on list restyle</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../name.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function runTest() { + this.containerNode = getNode("outerDiv"); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode("child")), + new invokerChecker(EVENT_HIDE, getNode("childDoc")), + new invokerChecker(EVENT_SHOW, "newChildDoc"), + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function runTest_invoke() { + this.containerNode.removeChild(getNode("child")); + + var docContainer = getNode("docContainer"); + var iframe = document.createElement("iframe"); + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + iframe.setAttribute("src", "http://example.com"); + iframe.setAttribute("id", "newChildDoc"); + + docContainer.removeChild(getNode("childDoc")); + docContainer.appendChild(iframe); + }; + + this.getID = function runTest_getID() { + return "check show events are not incorrectly coalesced"; + }; + } + + // enableLogging("tree"); + gA11yEventDumpToConsole = true; + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + gQueue.push(new runTest()); + gQueue.invoke(); // SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1189277" + title="content process crash caused by missing show event"> + Mozilla Bug 1189277 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="outerDiv"> + <div id="child">foo</div> + <div id="docContainer"> + <iframe id="childDoc" src="about:blank"> + </iframe> + </div> + </div> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug1276857.html b/accessible/tests/mochitest/treeupdate/test_bug1276857.html new file mode 100644 index 0000000000..a164247534 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1276857.html @@ -0,0 +1,131 @@ +<!DOCTYPE html> +<html> + +<head> + <title>DOM mutations test</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function runTest() { + let iframe = document.getElementById("iframe"); + + // children change will recreate the table + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, () => { + let doc = getNode("iframe").contentDocument; + return doc && doc.getElementById("c1"); + }), + ]; + + this.invoke = function runTest_invoke() { + var tree = { + SECTION: [ // c1 + { TEXT_LEAF: [] }, // Some text + { TEXT_CONTAINER: [ + { TEXT_LEAF: [] }, // something with .. + ] }, + { TEXT_LEAF: [] }, // More text + ], + }; + testAccessibleTree(iframe.contentDocument.getElementById("c1"), tree); + + iframe.contentDocument.getElementById("c1_t").querySelector("span").remove(); + }; + + this.finalCheck = function runTest_finalCheck() { + var tree = { + SECTION: [ // c1 + { TEXT_LEAF: [] }, // Some text + { TEXT_LEAF: [] }, // More text + ], + }; + testAccessibleTree(iframe.contentDocument.getElementById("c1"), tree); + }; + + this.getID = function runTest_getID() { + return "child DOM node is removed before the layout notifies the a11y about parent removal/show"; + }; + } + + function runShadowTest() { + // children change will recreate the table + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, () => { + let doc = getNode("iframe").contentDocument; + return doc && doc.getElementById("c2"); + }), + ]; + + this.invoke = function runShadowTest_invoke() { + var tree = { + SECTION: [ // c2 + { TEXT_LEAF: [] }, // Some text + { TEXT_CONTAINER: [ + { TEXT_LEAF: [] }, // something with .. + ] }, + { TEXT_LEAF: [] }, // More text + ], + }; + const iframe = document.getElementById("iframe"); + testAccessibleTree(iframe.contentDocument.getElementById("c2"), tree); + + var shadowRoot = iframe.contentDocument.getElementById("c2_c").shadowRoot; + shadowRoot.firstElementChild.querySelector("span").remove(); + // bug 1487312 + shadowRoot.firstElementChild.offsetTop; + shadowRoot.appendChild(document.createElement("button")); + }; + + this.finalCheck = function runShadowTest_finalCheck() { + var tree = { + SECTION: [ // c2 + { TEXT_LEAF: [] }, // Some text + { TEXT_LEAF: [] }, // More text + { PUSHBUTTON: [] }, // The button we appended. + ], + }; + const iframe = document.getElementById("iframe"); + testAccessibleTree(iframe.contentDocument.getElementById("c2"), tree); + }; + + this.getID = function runShadowTest_getID() { + return "child DOM node is removed before the layout notifies the a11y about parent removal/show in shadow DOM"; + }; + } + + // enableLogging("tree"); + // gA11yEventDumpToConsole = true; + + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + gQueue.push(new runTest()); + gQueue.push(new runShadowTest()); + gQueue.invoke(); // will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + + window.onload = () => { + let iframe = document.createElement("iframe"); + iframe.id = "iframe"; + iframe.src = "test_bug1276857_subframe.html"; + addA11yLoadEvent(doTest, iframe.contentWindow); + document.body.appendChild(iframe); + }; + </script> + +</head> +<body> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug1276857_subframe.html b/accessible/tests/mochitest/treeupdate/test_bug1276857_subframe.html new file mode 100644 index 0000000000..869c9ebe6c --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1276857_subframe.html @@ -0,0 +1,33 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>DOM mutations test</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="application/javascript" src="../role.js"></script> +</head> +<body> + <div id="c1"> + <div id="c1_t" style="display: table" role="presentation"> + Some text + <span style="display: table-cell">something with accessibles goes here</span> + More text + </div> + </div> + + <template id="tmpl"> + <div style="display: table" role="presentation"> + Some text + <span style="display: table-cell">something with accessibles goes here</span> + More text + </div> + </template> + + <div id="c2"><div id="c2_c" role="presentation"></div></div> + + <script> + var gShadowRoot = document.getElementById("c2_c").attachShadow({mode: "open"}); + var tmpl = document.getElementById("tmpl"); + gShadowRoot.appendChild(document.importNode(tmpl.content, true)); + </script> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml b/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml new file mode 100644 index 0000000000..51a3c39047 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml @@ -0,0 +1,57 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Canvas subdom mutation</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + + <script> + <![CDATA[ + function doTest() { + var the_displayNone = getNode("the_displaynone"); + var the_table = getNode("the_table"); + var the_row = getNode("the_row"); + ok(isAccessible(the_table), "table should be accessible"); + the_displayNone.appendChild(the_table); + ok(!isAccessible(the_table), "table in display none tree shouldn't be accessible"); + + setTimeout(function() { + document.body.removeChild(the_row); + // make sure no accessibles have stuck around. + ok(!isAccessible(the_row), "row shouldn't be accessible"); + ok(!isAccessible(the_table), "table shouldn't be accessible"); + ok(!isAccessible(the_displayNone), "display none things shouldn't be accessible"); + SimpleTest.finish(); + }, 0); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> +</head> +<body> + + <a target="_blank" + title="test accessible removal when reframe root isn't accessible" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=852150"> + Mozilla Bug 852150 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="the_displaynone" style="display: none;"></div> + <table id="the_table"></table> + <tr id="the_row"></tr> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml b/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml new file mode 100644 index 0000000000..5d9e813f3a --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml @@ -0,0 +1,31 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> + +function boom() { + var newSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "span"); + document.getElementById("c").insertBefore(newSpan, document.getElementById("d")); + document.getElementById("a").style.visibility = "visible"; + ok(true, "test didn't crash or assert"); + SimpleTest.finish(); +} + +</script> +</head> + +<body onload="boom();"> + <a target="_blank" + title="test reparenting accessible subtree when inaccessible element becomes accessible" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=883708"> + Mozilla Bug 883708 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + +<div style="visibility: collapse;" id="a"><div style="float: right; visibility: visible;"><div id="c"><td id="d"></td></div></div></div></body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml b/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml new file mode 100644 index 0000000000..7e3cf14fac --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml @@ -0,0 +1,19 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> + +function boom() { + document.getElementById("k").removeAttribute("href"); + ok(true, "changing iframe contents doesn't cause assertions"); + SimpleTest.finish(); +} + +</script> +</head> + +<body onload="boom();"> +<iframe src="data:text/html,1"><link id="k" href="data:text/html,2" /></iframe> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_bug895082.html b/accessible/tests/mochitest/treeupdate/test_bug895082.html new file mode 100644 index 0000000000..8332c5206e --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug895082.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> +<head> +<title>Replace body test</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> +function doTest() { + var y = document.getElementById("y"); + var oldBody = document.body; + var newBody = document.createElement("body"); + document.documentElement.insertBefore(newBody, oldBody); + setTimeout(function() { + document.documentElement.removeChild(oldBody); + newBody.appendChild(y); + ok(true, "we didn't assert"); + SimpleTest.finish(); + }, 0); +} + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=895082" + title="Bug 895082 - replacing body element asserts"> + Bug 895082</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + +<div><div id="y"></div></div> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_canvas.html b/accessible/tests/mochitest/treeupdate/test_canvas.html new file mode 100644 index 0000000000..229bf4f2e3 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_canvas.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Canvas subdom mutation</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function addSubtree(aID) { + this.node = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.node), + ]; + + this.invoke = function addSubtree_invoke() { + // ensure we start with no subtree + testAccessibleTree("canvas", { CANVAS: [] }); + getNode("dialog").style.display = "block"; + }; + + this.finalCheck = function addSubtree_finalCheck() { + testAccessibleTree("dialog", { DIALOG: [] }); + }; + + this.getID = function addSubtree_getID() { + return "show canvas subdom"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + gQueue = new eventQueue(); + + // make the subdom come alive! + gQueue.push(new addSubtree("dialog")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Expose content in Canvas element" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=495912"> + Mozilla Bug 495912 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <canvas id="canvas"> + <div id="dialog" role="dialog" style="display: none;"> + </div> + </canvas> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml b/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml new file mode 100644 index 0000000000..f81d77332d --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_contextmenu.xhtml @@ -0,0 +1,315 @@ +<?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="menu tree and events"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + <script type="application/javascript" + src="../role.js" /> + + <script type="application/javascript"> + <![CDATA[ + + function openMenu(aID, aTree) + { + this.eventSeq = [ + new invokerChecker(EVENT_MENUPOPUP_START, getNode(aID)) + ]; + + this.invoke = function openMenu_invoke() + { + var button = getNode("button"); + getNode(aID).openPopup(button, "after_start", 0, 0, true, false); + } + + this.finalCheck = function openMenu_finalCheck(aEvent) + { + testAccessibleTree(aID, aTree); + } + + this.getID = function openMenu_getID() + { + return "open menu " + prettyName(aID); + } + } + + function selectNextMenuItem(aID) + { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getNode(aID)) + ]; + + this.invoke = function selectMenuItem_invoke() + { + synthesizeKey("KEY_ArrowDown"); + } + + this.getID = function selectMenuItem_getID() + { + return "select menuitem " + prettyName(aID); + } + } + + function openSubMenu(aSubMenuID, aItemID, aMenuID, aTree) + { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getNode(aItemID)), + ]; + + this.invoke = function openSubMenu_invoke() + { + synthesizeKey("KEY_Enter"); + } + + this.finalCheck = function openSubMenu_finalCheck(aEvent) + { + testAccessibleTree(aMenuID, aTree); + } + + this.getID = function openSubMenu_getID() + { + return "open submenu " + prettyName(aSubMenuID) + " focusing item " + prettyName(aItemID); + } + } + + function closeSubMenu(aSubMenuID, aItemID) + { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getNode(aItemID)), + ]; + + this.invoke = function closeSubMenu_invoke() + { + synthesizeKey("KEY_Escape"); + } + + this.getID = function closeSubMenu_getID() + { + return "close submenu " + prettyName(aSubMenuID) + " focusing item " + prettyName(aItemID); + } + } + + function closeMenu(aID) + { + this.eventSeq = [ + new invokerChecker(EVENT_MENUPOPUP_END, getNode(aID)) + ]; + + this.invoke = function closeMenu_invoke() + { + synthesizeKey("KEY_Escape"); + } + + this.getID = function closeMenu_getID() + { + return "close menu " + prettyName(aID); + } + } + + //gA11yEventDumpToConsole = true; + //enableLogging("tree,verbose"); + + var gQueue = null; + var gContextTree = {}; + + // Linux and Windows menu trees discrepancy: bug 527646. + + /** + * Return the context menu tree before submenus were open. + */ + function getMenuTree1() + { + if (LINUX || SOLARIS) { + let tree = { + role: ROLE_MENUPOPUP, + children: [ + { + name: "item0", + role: ROLE_MENUITEM, + children: [] + }, + { + name: "item1", + role: ROLE_MENUITEM, + children: [] + }, + { + name: "item2", + role: ROLE_PARENT_MENUITEM, + children: [ ] + } + ] + }; + return tree; + } + + // Windows + let tree = { + role: ROLE_MENUPOPUP, + children: [ + { + name: "item0", + role: ROLE_MENUITEM, + children: [] + }, + { + name: "item1", + role: ROLE_MENUITEM, + children: [] + }, + { + name: "item2", + role: ROLE_PARENT_MENUITEM, + children: [ + { + name: "item2", + role: ROLE_MENUPOPUP, + children: [ ] + } + ] + } + ] + }; + return tree; + } + + /** + * Return context menu tree when submenu was open. + */ + function getMenuTree2() + { + var tree = getMenuTree1(); + if (LINUX || SOLARIS) { + let submenuTree = + { + name: "item2.0", + role: ROLE_PARENT_MENUITEM, + children: [ ] + }; + tree.children[2].children.push(submenuTree); + return tree; + } + + // Windows + let submenuTree = + { + name: "item2.0", + role: ROLE_PARENT_MENUITEM, + children: [ + { + name: "item2.0", + role: ROLE_MENUPOPUP, + children: [ ] + } + ] + }; + + tree.children[2].children[0].children.push(submenuTree); + return tree; + } + + /** + * Return context menu tree when subsub menu was open. + */ + function getMenuTree3() + { + var tree = getMenuTree2(); + var subsubmenuTree = + { + name: "item2.0.0", + role: ROLE_MENUITEM, + children: [] + }; + + if (LINUX || SOLARIS) + tree.children[2].children[0].children.push(subsubmenuTree); + else + tree.children[2].children[0].children[0].children[0].children.push(subsubmenuTree); + + return tree; + } + + + function doTests() + { + gQueue = new eventQueue(); + + // Check initial empty tree + testAccessibleTree("context", { MENUPOPUP: [] }); + + // Open context menu and check that menu item accesibles are created. + gQueue.push(new openMenu("context", getMenuTree1())); + + // Select items and check focus event on them. + gQueue.push(new selectNextMenuItem("item0")); + gQueue.push(new selectNextMenuItem("item1")); + gQueue.push(new selectNextMenuItem("item2")); + + // Open sub menu and check menu accessible tree and focus event. + gQueue.push(new openSubMenu("submenu2", "item2.0", + "context", getMenuTree2())); + gQueue.push(new openSubMenu("submenu2.0", "item2.0.0", + "context", getMenuTree3())); + + // Close submenus and check that focus goes to parent. + gQueue.push(new closeSubMenu("submenu2.0", "item2.0")); + gQueue.push(new closeSubMenu("submenu2", "item2")); + + gQueue.push(new closeMenu("context")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=630194" + title="Update accessible tree when opening the menu popup"> + Mozilla Bug 630194 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486" + title="Don't force accessible creation for popup children."> + Mozilla Bug 630486 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <menupopup id="context"> + <menuitem id="item0" label="item0"/> + <menuitem id="item1" label="item1"/> + <menu id="item2" label="item2"> + <menupopup id="submenu2"> + <menu id="item2.0" label="item2.0"> + <menupopup id="submenu2.0"> + <menuitem id="item2.0.0" label="item2.0.0"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menupopup> + + <button context="context" id="button">btn</button> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/treeupdate/test_cssoverflow.html b/accessible/tests/mochitest/treeupdate/test_cssoverflow.html new file mode 100644 index 0000000000..6b60fce975 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_cssoverflow.html @@ -0,0 +1,149 @@ +<html> + +<head> + <title>Testing HTML scrollable frames (css overflow style)</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + // ////////////////////////////////////////////////////////////////////////// + + /** + * Change scroll range to not empty size and inserts a child into container + * to trigger tree update of the container. Prior to bug 677154 not empty + * size resulted to accessible creation for scroll area, container tree + * update picked up that accessible unattaching scroll area accessible + * subtree. + */ + function changeScrollRange(aContainerID, aScrollAreaID) { + this.containerNode = getNode(aContainerID); + this.container = getAccessible(this.containerNode); + this.scrollAreaNode = getNode(aScrollAreaID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function changeScrollRange_invoke() { + this.scrollAreaNode.style.width = "20px"; + this.containerNode.appendChild(document.createElement("input")); + }; + + this.finalCheck = function changeScrollRange_finalCheck() { + var accTree = + { SECTION: [ // container + { SECTION: [ // scroll area + { ENTRY: [] }, // child content + ] }, + { ENTRY: [] }, // inserted input + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function changeScrollRange_getID() { + return "change scroll range for " + prettyName(aScrollAreaID); + }; + } + + /** + * Change scrollbar styles from visible to auto to make the scroll area focusable. + * That causes us to create an accessible for it. + * Make sure the tree stays intact. + * The scroll area has no ID on purpose to make it inaccessible initially. + */ + function makeFocusableByScrollbarStyles(aContainerID) { + this.container = getAccessible(aContainerID); + this.scrollAreaNode = getNode(aContainerID).firstChild; + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getAccessible, this.scrollAreaNode), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function makeFocusableByScrollbarStyles_invoke() { + var accTree = + { SECTION: [ // container + { PARAGRAPH: [ // paragraph + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree(this.container, accTree); + + this.scrollAreaNode.style.overflow = "auto"; + }; + + this.finalCheck = function makeFocusableByScrollbarStyles_finalCheck() { + var accTree = + { SECTION: [ // container + { role: ROLE_SECTION, // focusable scroll area + states: STATE_FOCUSABLE, + children: [ + { PARAGRAPH: [ // paragraph + { TEXT_LEAF: [] }, // text leaf + ] }, + ], + }, // focusable scroll area + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function makeFocusableByScrollbarStyles_getID() { + return "make div focusable through scrollbar styles " + + prettyName(aContainerID); + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + // ////////////////////////////////////////////////////////////////////////// + + var gQueue = null; + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new changeScrollRange("container", "scrollarea")); + gQueue.push(new makeFocusableByScrollbarStyles("container2")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=677154" + title="Detached document accessibility tree"> + Mozilla Bug 677154</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <div id="container"><div id="scrollarea" style="overflow:auto;"><input></div></div> + <div id="container2"><div style="height: 1px;"><p>foo</p></div></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_deck.xhtml b/accessible/tests/mochitest/treeupdate/test_deck.xhtml new file mode 100644 index 0000000000..979996a66c --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_deck.xhtml @@ -0,0 +1,154 @@ +<?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="Tree update on XUL deck panel switching"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + function switchDeckPanel(aContainerID, aDeckID) + { + this.panelIndex = 0; + + this.container = getAccessible(aContainerID); + this.deckNode = getNode(aDeckID); + this.prevPanel = getAccessible(this.deckNode.selectedPanel); + this.panelNode = this.deckNode.childNodes[this.panelIndex]; + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.prevPanel), + new invokerChecker(EVENT_SHOW, this.panelNode), + new invokerChecker(EVENT_REORDER, this.container) + ]; + + this.invoke = function switchDeckPanel_invoke() + { + var tree = + { GROUPING: [ // role="group" + { GROUPING: [ // groupbox, a selected panel #2 + { PUSHBUTTON: [ ] } // button + ] } + ] }; + testAccessibleTree(this.container, tree); + + this.deckNode.selectedIndex = this.panelIndex; + } + + this.finalCheck = function switchDeckPanel_finalCheck() + { + var tree = + { GROUPING: [ // role="group" + { LABEL: [ // description, a selected panel #1 + { TEXT_LEAF: [] } // text leaf, a description value + ] } + ] }; + testAccessibleTree(this.container, tree); + } + + this.getID = function switchDeckPanel_getID() + { + return "switch deck panel"; + } + } + + function showDeckPanel(aContainerID, aPanelID) + { + this.container = getAccessible(aContainerID); + this.deckNode = getNode(aPanelID); + var tree = + { GROUPING: [ // role="group" + { GROUPING: [ // grouping of panel 2 + { PUSHBUTTON: [] } // push button in panel 2 + ] } + ] }; + + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_REORDER, this.container) + ]; + + this.invoke = function showDeckPanel_invoke() + { + // This stops the refreh driver from doing its regular ticks, and leaves + // us in control. 100 is an arbitrary positive number to advance the clock + // it is not checked or used anywhere. + window.windowUtils.advanceTimeAndRefresh(100); + + testAccessibleTree(this.container, tree); + this.deckNode.style.display = "-moz-box"; + + // This flushes our DOM mutations and forces any pending mutation events. + window.windowUtils.advanceTimeAndRefresh(100); + } + + this.finalCheck = function showDeckPanel_finalCheck() + { + testAccessibleTree(this.container, tree); + + // Return to regular refresh driver ticks. + window.windowUtils.restoreNormalRefresh(); + } + + this.getID = function showDeckPanel_getID() + { + return "show deck panel"; + } + } + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + gQueue.push(new showDeckPanel("container", "hidden")); + gQueue.push(new switchDeckPanel("container", "deck")); + gQueue.invoke(); // will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=814836" + title=" xul:deck element messes up screen reader"> + Mozilla Bug 814836 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1" id="container" role="group"> + + <deck id="deck" selectedIndex="1"> + <description>This is the first page</description> + <groupbox> + <button label="This is the second page"/> + </groupbox> + <hbox id="hidden" style="display: none;"><label>This is the third page</label></hbox> + </deck> + + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_delayed_removal.html b/accessible/tests/mochitest/treeupdate/test_delayed_removal.html new file mode 100644 index 0000000000..3f421f0c5b --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_delayed_removal.html @@ -0,0 +1,500 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test accessible delayed removal</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style> + .gentext:before { + content: "START" + } + .gentext:after { + content: "END" + } + </style> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + + async function hideDivFromInsideSpan() { + let msg = "hideDivFromInsideSpan"; + info(msg); + let events = waitForOrderedEvents([ + [EVENT_HIDE, "div1"], [EVENT_TEXT_REMOVED, "span1"], + [EVENT_REORDER, "span1"] + ], msg); + document.body.offsetTop; // Flush layout. + getNode("div1").style.display = "none"; + await events; + + testAccessibleTree("c1", { SECTION: [ { REGION: [] }, ] }); + } + + async function showDivFromInsideSpan() { + let msg = "showDivFromInsideSpan"; + info(msg); + let events = waitForOrderedEvents( + [[EVENT_SHOW, "div2"], [EVENT_REORDER, "span2"]], msg); + document.body.offsetTop; // Flush layout. + getNode("div2").style.display = "block"; + await events; + + testAccessibleTree("c2", + { SECTION: [ { REGION: [{ SECTION: [ { TEXT_LEAF: [] } ] }] }, ] }); + } + + async function removeDivFromInsideSpan() { + let msg = "removeDivFromInsideSpan"; + info(msg); + let events = waitForOrderedEvents([ + [EVENT_HIDE, getNode("div3")], [EVENT_TEXT_REMOVED, "span3"], + [EVENT_REORDER, "span3"] + ], msg); + document.body.offsetTop; // Flush layout. + getNode("div3").remove(); + await events; + + testAccessibleTree("c3", { SECTION: [ { REGION: [] }, ] }); + } + + // Test to see that generated content is inserted + async function addCSSGeneratedContent() { + let msg = "addCSSGeneratedContent"; + let c4_child = getAccessible("c4_child"); + info(msg); + let events = waitForOrderedEvents([ + [EVENT_SHOW, evt => evt.accessible == c4_child.firstChild], + [EVENT_SHOW, evt => evt.accessible == c4_child.lastChild], + [EVENT_REORDER, c4_child]], msg); + document.body.offsetTop; // Flush layout. + getNode("c4_child").classList.add('gentext'); + await events; + + testAccessibleTree("c4", { SECTION: [ // container + { SECTION: [ // inserted node + { STATICTEXT: [] }, // :before + { TEXT_LEAF: [] }, // primary text + { STATICTEXT: [] }, // :after + ] }, + ] }); + } + + // Test to see that generated content gets removed + async function removeCSSGeneratedContent() { + let msg = "removeCSSGeneratedContent"; + let c5_child = getAccessible("c5_child"); + info(msg); + let events = waitForEvents([ + [EVENT_HIDE, c5_child.firstChild], + [EVENT_HIDE, c5_child.lastChild], + [EVENT_REORDER, c5_child]], msg); + document.body.offsetTop; // Flush layout. + getNode("c5_child").classList.remove('gentext'); + await events; + + testAccessibleTree("c5",{ SECTION: [ // container + { SECTION: [ // inserted node + { TEXT_LEAF: [] }, // primary text + ] }, + ] }); + } + + // Test to see that a non-accessible intermediate container gets its accessible + // descendants removed and inserted correctly. + async function intermediateNonAccessibleContainers() { + let msg = "intermediateNonAccessibleContainers"; + info(msg); + + testAccessibleTree("c6",{ SECTION: [ + { SECTION: [ + { role: ROLE_PUSHBUTTON, name: "Hello" }, + ] }, + ] }); + + let events = waitForOrderedEvents( + [[EVENT_HIDE, "b1"], [EVENT_SHOW, "b2"], [EVENT_REORDER, "scrollarea"]], msg); + document.body.offsetTop; // Flush layout. + getNode("scrollarea").style.overflow = "auto"; + document.querySelector("#scrollarea > div > div:first-child").style.display = "none"; + document.querySelector("#scrollarea > div > div:last-child").style.display = "block"; + await events; + + testAccessibleTree("c6",{ SECTION: [ + { SECTION: [ + { role: ROLE_PUSHBUTTON, name: "Goodbye" }, + ] }, + ] }); + } + + // Test to see that the button gets reparented into the new accessible container. + async function intermediateNonAccessibleContainerBecomesAccessible() { + let msg = "intermediateNonAccessibleContainerBecomesAccessible"; + info(msg); + + testAccessibleTree("c7",{ SECTION: [ + { role: ROLE_PUSHBUTTON, name: "Hello" }, + { TEXT_LEAF: [] } + ] }); + + let events = waitForOrderedEvents( + [[EVENT_HIDE, "b3"], + // b3 show event coalesced into its new container + [EVENT_SHOW, evt => evt.DOMNode.classList.contains('intermediate')], + [EVENT_REORDER, "c7"]], msg); + document.body.offsetTop; // Flush layout. + document.querySelector("#c7 > div").style.display = "block"; + await events; + + testAccessibleTree("c7",{ SECTION: [ + { SECTION: [ { role: ROLE_PUSHBUTTON, name: "Hello" } ] } + ] }); + } + + // Test to ensure that relocated accessibles are removed when a DOM + // ancestor is hidden. + async function removeRelocatedWhenDomAncestorHidden() { + info("removeRelocatedWhenDomAncestorHidden"); + + testAccessibleTree("c8",{ SECTION: [ + { EDITCOMBOBOX: [ // c8_owner + { COMBOBOX_LIST: [] }, // c8_owned + ]}, + { SECTION: [] }, // c8_owned_container + ] }); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, "c8_owned_container"], + [EVENT_HIDE, "c8_owned"], + [EVENT_REORDER, "c8"], + ], "removeRelocatedWhenDomAncestorHidden"); + document.body.offsetTop; // Flush layout. + getNode("c8_owned_container").hidden = true; + await events; + + testAccessibleTree("c8",{ SECTION: [ + { EDITCOMBOBOX: [] }, // c8_owner + ] }); + } + + // Bug 1572829 + async function removeShadowRootHost() { + info("removeShadowRootHost"); + document.body.offsetTop; // Flush layout. + + let event = waitForEvent(EVENT_REORDER, "c9", "removeShadowRootHost"); + getNode("c9").firstElementChild.attachShadow({mode: "open"}); + getNode("c9").firstElementChild.replaceWith(""); + + await event; + } + + function listItemReframe() { + testAccessibleTree("li",{ LISTITEM: [ + { LISTITEM_MARKER: [] }, + { TEXT_LEAF: [] }, + ] }); + + getNode("li").style.listStylePosition = "inside"; + document.body.offsetTop; // Flush layout. + window.windowUtils.advanceTimeAndRefresh(100); + + testAccessibleTree("li",{ LISTITEM: [ + { LISTITEM_MARKER: [] }, + { TEXT_LEAF: [] }, + ] }); + + window.windowUtils.restoreNormalRefresh(); + } + + // Check to see that a reframed body gets its children pruned correctly. + async function bodyReframe(argument) { + // Load sub-document in iframe. + let event = waitForEvent(EVENT_REORDER, "iframe", "bodyReframe"); + getNode("iframe").src = + `data:text/html,<div>Hello</div><div style="display: none">World</div>`; + await event; + + // Initial tree should have one section leaf. + testAccessibleTree("c10",{ SECTION: [ + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { SECTION: [ + { role: ROLE_TEXT_LEAF, name: "Hello" } + ] } + ]} + ] } + ] }); + + + let iframeDoc = getNode("iframe").contentWindow.document; + + // Trigger coalesced reframing. Both the body node and its children + // will need reframing. + event = waitForEvent(EVENT_REORDER, iframeDoc, "bodyReframe"); + iframeDoc.body.style.display = "inline-block"; + iframeDoc.querySelector("div:first-child").style.display = "none"; + iframeDoc.querySelector("div:last-child").style.display = "block"; + + await event; + + // Only the second section should be showing + testAccessibleTree("c10",{ SECTION: [ + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { SECTION: [ + { role: ROLE_TEXT_LEAF, name: "World" } + ] } + ]} + ] } + ] }); + } + + // Ensure that embed elements recreate their Accessible if they started + // without an src and then an src is set later. + async function embedBecomesOuterDoc() { + let msg = "embedBecomesOuterDoc"; + info(msg); + + testAccessibleTree("c12", { SECTION: [ + { TEXT: [] } + ] }); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, "embed"], + [EVENT_SHOW, "embed"], + [EVENT_REORDER, "c12"], + ], msg); + getNode("embed").src = "data:text/html,"; + await events; + + testAccessibleTree("c12", { SECTION: [ + { INTERNAL_FRAME: [ + { DOCUMENT: [] } + ] } + ] }); + } + + // Test that we get a text removed event when removing generated content from a button + async function testCSSGeneratedContentRemovedFromButton() { + let msg = "testCSSGeneratedContentRemovedFromButton"; + info(msg); + + testAccessibleTree("c13", { SECTION: [ + { role: ROLE_PUSHBUTTON, name: "beforego", + children: [{ STATICTEXT: [] }, { TEXT_LEAF: [] }] } + ] }); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, evt => evt.accessible.name == "before"], + [EVENT_TEXT_REMOVED, evt => evt.accessible.role == ROLE_PUSHBUTTON], + [EVENT_SHOW, evt => evt.DOMNode.tagName == "HR"], + [EVENT_REORDER, "c13"], + ], msg); + getNode("b13").click(); + await events; + + testAccessibleTree("c13", { SECTION: [ + { role: ROLE_PUSHBUTTON, name: "go", + children: [{ TEXT_LEAF: [] }] }, + { SEPARATOR: [] } + ] }); + } + + // Slack seems to often restyle containers and change children + // simultaneously, this results in an insertion queue filled with + // redundant insertions and unparented nodes. + // This test duplicates some of this. + async function testSlack() { + let msg = "testSlack"; + info(msg); + + window.windowUtils.advanceTimeAndRefresh(100); + let event = waitForEvent(EVENT_REORDER, "c14", "testSlack"); + + let keyContainer = document.querySelector("#c14 .intermediate"); + keyContainer.style.display = "inline-block"; + document.body.offsetTop; // Flush layout. + + let one = document.querySelector("#c14 [aria-label='one']"); + let three = document.querySelector("#c14 [aria-label='three']"); + one.remove(); + three.remove(); + // insert one first + keyContainer.firstChild.before(one.cloneNode()); + // insert three last + keyContainer.lastChild.after(three.cloneNode()); + + keyContainer.style.display = "flex"; + document.body.offsetTop; // Flush layout. + + window.windowUtils.restoreNormalRefresh(); + + await event; + + is(getAccessible("c14").name, "one two three", "subtree has correct order"); + } + + // Ensure that a node is removed when visibility: hidden is set but the + // layout frame is reconstructed; e.g. because of position: fixed. Also + // ensure that visible children aren't clobbered. + async function visibilityHiddenWithReframe() { + let msg = "visibilityHiddenWithReframe"; + info(msg); + + testAccessibleTree("c15", { SECTION: [ // c15 + { SECTION: [ // c15_inner + { TEXT_LEAF: [] }, // Text + { PARAGRAPH: [ + { TEXT_LEAF: [] } // Para + ] }, + { HEADING: [ // c15_visible + { TEXT_LEAF: [] } // Visible + ] }, // c15_visible + ] } // c15_inner + ] }); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, "c15_inner"], + [EVENT_SHOW, "c15_visible"], + [EVENT_REORDER, "c15"], + ], msg); + getNode("c15_inner").style = "visibility: hidden; position: fixed;"; + await events; + + testAccessibleTree("c15", { SECTION: [ // c15 + { HEADING: [ // c15_visible + { TEXT_LEAF: [] } // Visible + ] }, // c15_visible + ] }); + } + + async function doTest() { + await hideDivFromInsideSpan(); + + await showDivFromInsideSpan(); + + await removeDivFromInsideSpan(); + + await addCSSGeneratedContent(); + + await removeCSSGeneratedContent(); + + await intermediateNonAccessibleContainers(); + + await intermediateNonAccessibleContainerBecomesAccessible(); + + await removeRelocatedWhenDomAncestorHidden(); + + await removeShadowRootHost(); + + listItemReframe(); + + await bodyReframe(); + + await embedBecomesOuterDoc(); + + await testCSSGeneratedContentRemovedFromButton(); + + await testSlack(); + + await visibilityHiddenWithReframe(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="c1"> + <span role="region" id="span1" aria-label="region"><div id="div1">hello</div></span> + </div> + + <div id="c2"> + <span role="region" id="span2" aria-label="region"><div id="div2" style="display: none">hello</div></span> + </div> + + <div id="c3"> + <span role="region" id="span3" aria-label="region"><div id="div3">hello</div></span> + </div> + + <div id="c4"><div id="c4_child">text</div></div> + + <div id="c5"><div id="c5_child" class="gentext">text</div></div> + + <div id="c6"> + <div id="scrollarea" style="overflow:hidden;"> + <div><div role="none"><button id="b1">Hello</button></div><div role="none" style="display: none"><button id="b2">Goodbye</button></div></div> + </div> + </div> + + <div id="c7"> + <div style="display: inline;" class="intermediate"> + <button id="b3">Hello</button> + </div> + </div> + + <div id="c8"> + <div id="c8_owner" role="combobox" aria-owns="c8_owned"></div> + <div id="c8_owned_container"> + <div id="c8_owned" role="listbox"></div> + </div> + </div> + + <div id="c9"> + <div><dir>a</dir></div> + </div> + + <div id="c11"> + <ul> + <li id="li">Test</li> + </ul> + </div> + + <div id="c12"><embed id="embed"></embed></div> + + <div id="c10"> + <iframe id="iframe"></iframe> + </div> + + <div id="c13"> + <style> + .before::before { content: 'before' } + </style> + <button id="b13" class="before" onclick="this.className = ''; this.insertAdjacentElement('afterend', document.createElement('hr'))">go</button> + </div> + + <div role="heading" id="c14" data-qa="virtual-list-item"> + <div class="intermediate"> + <div role="img" aria-label="one"></div> two <div role="img" + aria-label="three"></div> + </div> + </div> + + <div id="c15"><div id="c15_inner"> + Text + <p>Para</p> + <h1 id="c15_visible" style="visibility: visible;">Visible</h1> + </div></div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_doc.html b/accessible/tests/mochitest/treeupdate/test_doc.html new file mode 100644 index 0000000000..6bb2863df4 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_doc.html @@ -0,0 +1,415 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test document root content mutations</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Helpers + + function getDocNode(aID) { + return getNode(aID).contentDocument; + } + function getDocChildNode(aID) { + return getDocNode(aID).body.firstChild; + } + + function rootContentReplaced(aID, aTextName, aRootContentRole) { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getDocChildNode, aID), + new invokerChecker(EVENT_REORDER, getDocNode, aID), + ]; + + this.finalCheck = function rootContentReplaced_finalCheck() { + var tree = { + role: aRootContentRole || ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: aTextName, + }, + ], + }; + testAccessibleTree(getDocNode(aID), tree); + }; + } + + function rootContentRemoved(aID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, null), + new invokerChecker(EVENT_REORDER, getDocNode, aID), + ]; + + this.preinvoke = function rootContentRemoved_preinvoke() { + // Set up target for hide event before we invoke. + var text = getAccessible(getAccessible(getDocNode(aID)).firstChild); + this.eventSeq[0].target = text; + }; + + this.finalCheck = function rootContentRemoved_finalCheck() { + var tree = { + role: ROLE_DOCUMENT, + children: [ ], + }; + testAccessibleTree(getDocNode(aID), tree); + }; + } + + function rootContentInserted(aID, aTextName) { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getDocChildNode, aID), + new invokerChecker(EVENT_REORDER, getDocNode, aID), + ]; + + this.finalCheck = function rootContentInserted_finalCheck() { + var tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: aTextName, + }, + ], + }; + testAccessibleTree(getDocNode(aID), tree); + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function writeIFrameDoc(aID) { + this.__proto__ = new rootContentReplaced(aID, "hello"); + + this.invoke = function writeIFrameDoc_invoke() { + var docNode = getDocNode(aID); + + // We can't use open/write/close outside of iframe document because of + // security error. + var script = docNode.createElement("script"); + script.textContent = "document.open(); document.write('hello'); document.close();"; + docNode.body.appendChild(script); + }; + + this.getID = function writeIFrameDoc_getID() { + return "write document"; + }; + } + + /** + * Replace HTML element. + */ + function replaceIFrameHTMLElm(aID) { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getDocChildNode, aID), + new invokerChecker(EVENT_REORDER, getDocNode, aID), + ]; + + this.invoke = function replaceIFrameHTMLElm_invoke() { + var docNode = getDocNode(aID); + var newHTMLNode = docNode.createElement("html"); + newHTMLNode.innerHTML = `<body><p>New Wave</p></body`; + docNode.replaceChild(newHTMLNode, docNode.documentElement); + }; + + this.finalCheck = function replaceIFrameHTMLElm_finalCheck() { + var tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_PARAGRAPH, + children: [ + { + role: ROLE_TEXT_LEAF, + name: "New Wave", + }, + ], + }, + ], + }; + testAccessibleTree(getDocNode(aID), tree); + }; + + this.getID = function replaceIFrameHTMLElm_getID() { + return "replace HTML element"; + }; + } + + /** + * Replace HTML body on new body having ARIA role. + */ + function replaceIFrameBody(aID) { + this.__proto__ = new rootContentReplaced(aID, "New Hello"); + + this.invoke = function replaceIFrameBody_invoke() { + var docNode = getDocNode(aID); + var newBodyNode = docNode.createElement("body"); + var newTextNode = docNode.createTextNode("New Hello"); + newBodyNode.appendChild(newTextNode); + docNode.documentElement.replaceChild(newBodyNode, docNode.body); + }; + + this.getID = function replaceIFrameBody_getID() { + return "replace body"; + }; + } + + /** + * Replace HTML body on new body having ARIA role. + */ + function replaceIFrameBodyOnARIARoleBody(aID) { + this.__proto__ = new rootContentReplaced(aID, "New Hello", + ROLE_APPLICATION); + + this.invoke = function replaceIFrameBodyOnARIARoleBody_invoke() { + var docNode = getDocNode(aID); + var newBodyNode = docNode.createElement("body"); + var newTextNode = docNode.createTextNode("New Hello"); + newBodyNode.appendChild(newTextNode); + newBodyNode.setAttribute("role", "application"); + docNode.documentElement.replaceChild(newBodyNode, docNode.body); + }; + + this.getID = function replaceIFrameBodyOnARIARoleBody_getID() { + return "replace body on body having ARIA role"; + }; + } + + /** + * Open/close document pair. + */ + function openIFrameDoc(aID) { + this.__proto__ = new rootContentRemoved(aID); + + this.invoke = function openIFrameDoc_invoke() { + this.preinvoke(); + + // Open document. + var docNode = getDocNode(aID); + var script = docNode.createElement("script"); + script.textContent = "function closeMe() { document.write('Works?'); document.close(); } window.closeMe = closeMe; document.open();"; + docNode.body.appendChild(script); + }; + + this.getID = function openIFrameDoc_getID() { + return "open document"; + }; + } + + function closeIFrameDoc(aID) { + this.__proto__ = new rootContentInserted(aID, "Works?"); + + this.invoke = function closeIFrameDoc_invoke() { + // Write and close document. + getDocNode(aID).write("Works?"); getDocNode(aID).close(); + }; + + this.getID = function closeIFrameDoc_getID() { + return "close document"; + }; + } + + /** + * Remove/insert HTML element pair. + */ + function removeHTMLFromIFrameDoc(aID) { + this.__proto__ = new rootContentRemoved(aID); + + this.invoke = function removeHTMLFromIFrameDoc_invoke() { + this.preinvoke(); + + // Remove HTML element. + var docNode = getDocNode(aID); + docNode.firstChild.remove(); + }; + + this.getID = function removeHTMLFromIFrameDoc_getID() { + return "remove HTML element"; + }; + } + + function insertHTMLToIFrameDoc(aID) { + this.__proto__ = new rootContentInserted(aID, "Haha"); + + this.invoke = function insertHTMLToIFrameDoc_invoke() { + // Insert HTML element. + var docNode = getDocNode(aID); + var html = docNode.createElement("html"); + var body = docNode.createElement("body"); + var text = docNode.createTextNode("Haha"); + body.appendChild(text); + html.appendChild(body); + docNode.appendChild(html); + }; + + this.getID = function insertHTMLToIFrameDoc_getID() { + return "insert HTML element document"; + }; + } + + /** + * Remove/insert HTML body pair. + */ + function removeBodyFromIFrameDoc(aID) { + this.__proto__ = new rootContentRemoved(aID); + + this.invoke = function removeBodyFromIFrameDoc_invoke() { + this.preinvoke(); + + // Remove body element. + var docNode = getDocNode(aID); + docNode.documentElement.removeChild(docNode.body); + }; + + this.getID = function removeBodyFromIFrameDoc_getID() { + return "remove body element"; + }; + } + + function insertElmUnderDocElmWhileBodyMissed(aID) { + this.docNode = null; + this.inputNode = null; + + function getInputNode() { return this.inputNode; } + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getInputNode.bind(this)), + new invokerChecker(EVENT_REORDER, getDocNode, aID), + ]; + + this.invoke = function invoke() { + this.docNode = getDocNode(aID); + this.inputNode = this.docNode.createElement("input"); + this.docNode.documentElement.appendChild(this.inputNode); + }; + + this.finalCheck = function finalCheck() { + var tree = + { DOCUMENT: [ + { ENTRY: [ ] }, + ] }; + testAccessibleTree(this.docNode, tree); + + // Remove aftermath of this test before next test starts. + this.docNode.documentElement.removeChild(this.inputNode); + }; + + this.getID = function getID() { + return "Insert element under document element while body is missed."; + }; + } + + function insertBodyToIFrameDoc(aID) { + this.__proto__ = new rootContentInserted(aID, "Yo ho ho i butylka roma!"); + + this.invoke = function insertBodyToIFrameDoc_invoke() { + // Insert body element. + var docNode = getDocNode(aID); + var body = docNode.createElement("body"); + var text = docNode.createTextNode("Yo ho ho i butylka roma!"); + body.appendChild(text); + docNode.documentElement.appendChild(body); + }; + + this.getID = function insertBodyToIFrameDoc_getID() { + return "insert body element"; + }; + } + + function changeSrc(aID) { + this.containerNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function changeSrc_invoke() { + this.containerNode.src = "data:text/html,<html><input></html>"; + }; + + this.finalCheck = function changeSrc_finalCheck() { + var tree = + { INTERNAL_FRAME: [ + { DOCUMENT: [ + { ENTRY: [ ] }, + ] }, + ] }; + testAccessibleTree(this.containerNode, tree); + }; + + this.getID = function changeSrc_getID() { + return "change src on iframe"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpToConsole = true; + // enableLogging('tree,verbose'); + + var gQueue = null; + + function doTest() { + gQueue = new eventQueue(); + + gQueue.push(new writeIFrameDoc("iframe")); + gQueue.push(new replaceIFrameHTMLElm("iframe")); + gQueue.push(new replaceIFrameBody("iframe")); + gQueue.push(new openIFrameDoc("iframe")); + gQueue.push(new closeIFrameDoc("iframe")); + gQueue.push(new removeHTMLFromIFrameDoc("iframe")); + gQueue.push(new insertHTMLToIFrameDoc("iframe")); + gQueue.push(new removeBodyFromIFrameDoc("iframe")); + gQueue.push(new insertElmUnderDocElmWhileBodyMissed("iframe")); + gQueue.push(new insertBodyToIFrameDoc("iframe")); + gQueue.push(new changeSrc("iframe")); + gQueue.push(new replaceIFrameBodyOnARIARoleBody("iframe")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Update accessible tree when root element is changed" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=606082">Mozilla Bug 606082</a> + <a target="_blank" + title="Elements inserted outside the body aren't accessible" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887">Mozilla Bug 608887</a> + <a target="_blank" + title="Reorder event for document must be fired after document initial tree creation" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=669263">Mozilla Bug 669263</a> + <a target="_blank" + title="Changing the HTML body doesn't pick up ARIA role" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=818407">Mozilla Bug 818407</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <iframe id="iframe"></iframe> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_gencontent.html b/accessible/tests/mochitest/treeupdate/test_gencontent.html new file mode 100644 index 0000000000..9a0c107133 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_gencontent.html @@ -0,0 +1,187 @@ +<html> + +<head> + <title>Elements with CSS generated content</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style> + .gentext:before { + content: "START" + } + .gentext:after { + content: "END" + } + </style> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + // ////////////////////////////////////////////////////////////////////////// + + /** + * Insert new node with CSS generated content style applied to container. + */ + function insertNodeHavingGenContent(aContainerID) { + this.containerNode = getNode(aContainerID); + this.container = getAccessible(this.containerNode); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getFirstChild, this.container), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function insertNodeHavingGenContent_invoke() { + var node = document.createElement("div"); + node.textContent = "text"; + node.setAttribute("class", "gentext"); + this.containerNode.appendChild(node); + }; + + this.finalCheck = function insertNodeHavingGenContent_finalCheck() { + var accTree = + { SECTION: [ // container + { SECTION: [ // inserted node + { STATICTEXT: [] }, // :before + { TEXT_LEAF: [] }, // primary text + { STATICTEXT: [] }, // :after + ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function insertNodeHavingGenContent_getID() { + return "insert node having generated content to " + prettyName(aContainerID); + }; + } + + /** + * Add CSS generated content to the given node contained by container node. + */ + function addGenContent(aContainerID, aNodeID) { + this.container = getAccessible(aContainerID); + this.nodeAcc = getAccessible(aNodeID); + this.node = getNode(aNodeID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getFirstChild, this.nodeAcc), + new invokerChecker(EVENT_SHOW, getLastChild, this.nodeAcc), + new invokerChecker(EVENT_REORDER, this.nodeAcc), + ]; + + this.invoke = function addGenContent_invoke() { + this.node.classList.add("gentext"); + }; + + this.finalCheck = function insertNodeHavingGenContent_finalCheck() { + var accTree = + { SECTION: [ // container + { SECTION: [ // inserted node + { STATICTEXT: [] }, // :before + { TEXT_LEAF: [] }, // primary text + { STATICTEXT: [] }, // :after + ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function addGenContent_getID() { + return "add generated content to" + prettyName(aNodeID); + }; + } + + /** + * Remove CSS generated content from the given node contained by container node. + */ + function removeGenContent(aContainerID, aNodeID) { + this.container = getAccessible(aContainerID); + this.nodeAcc = getAccessible(aNodeID); + this.node = getNode(aNodeID); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.nodeAcc.lastChild), + new invokerChecker(EVENT_HIDE, this.nodeAcc.firstChild), + new invokerChecker(EVENT_REORDER, this.nodeAcc), + ]; + + this.invoke = function removeGenContent_invoke() { + this.node.classList.remove("gentext"); + }; + + this.finalCheck = function removeGenContent_finalCheck() { + var accTree = + { SECTION: [ // container + { SECTION: [ // inserted node + { TEXT_LEAF: [] }, // primary text + ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function addGenContent_getID() { + return "remove generated content from" + prettyName(aNodeID); + }; + } + /** + * Target getters. + */ + function getFirstChild(aAcc) { + try { return aAcc.firstChild; } catch (e) { return null; } + } + + function getLastChild(aAcc) { + try { return aAcc.lastChild; } catch (e) { return null; } + } + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + // ////////////////////////////////////////////////////////////////////////// + + var gQueue = null; + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new insertNodeHavingGenContent("container1")); + gQueue.push(new addGenContent("container2", "container2_child")); + gQueue.push(new removeGenContent("container3", "container3_child")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=646350" + title="Add a test for dynamic chnages of CSS generated content"> + Mozilla Bug 646350</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <div id="container1"></div> + <div id="container2"><div id="container2_child">text</div></div> + <div id="container3"><div id="container3_child" class="gentext">text</div></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_general.html b/accessible/tests/mochitest/treeupdate/test_general.html new file mode 100644 index 0000000000..8129cae98a --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_general.html @@ -0,0 +1,174 @@ +<html> + +<head> + <title>Testing the tree updates</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + // ////////////////////////////////////////////////////////////////////////// + + function prependAppend(aContainer) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aContainer), + ]; + + this.invoke = function prependAppend_invoke() { + var checkbox = document.createElement("input"); + checkbox.setAttribute("type", "checkbox"); + getNode(aContainer).insertBefore(checkbox, getNode(aContainer).firstChild); + + var button = document.createElement("input"); + button.setAttribute("type", "button"); + getNode(aContainer).appendChild(button); + }; + + this.finalCheck = function prependAppend_finalCheck() { + var accTree = + { SECTION: [ // container + { CHECKBUTTON: [ ] }, + { ENTRY: [ ] }, + { PUSHBUTTON: [ ] }, + ] }; + testAccessibleTree(aContainer, accTree); + }; + + this.getID = function prependAppend_getID() { + return "prepends a child and appends a child"; + }; + } + + function removeRemove(aContainer) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aContainer), + ]; + + this.invoke = function removeRemove_invoke() { + getNode(aContainer).firstChild.remove(); + }; + + this.finalCheck = function removeRemove_finalCheck() { + var accTree = + { SECTION: [ // container + { PUSHBUTTON: [ ] }, + ] }; + testAccessibleTree(aContainer, accTree); + }; + + this.getID = function removeRemove_getID() { + return "remove first and second children"; + }; + } + + function insertInaccessibleAccessibleSiblings() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "c3"), + ]; + + this.invoke = function insertInaccessibleAccessibleSiblings_invoke() { + getNode("c3").appendChild(document.createElement("span")); + getNode("c3").appendChild(document.createElement("input")); + }; + + this.finalCheck = function insertInaccessibleAccessibleSiblings_finalCheck() { + var accTree = + { SECTION: [ // container + { PUSHBUTTON: [ + { TEXT_LEAF: [] }, + ] }, + { ENTRY: [ ] }, + ] }; + testAccessibleTree("c3", accTree); + }; + + this.getID = function insertInaccessibleAccessibleSiblings_getID() { + return "insert inaccessible and then accessible siblings"; + }; + } + + // Test for bug 1500416. + function displayContentsInsertion() { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, "c4"), + ]; + + this.invoke = function displayContentsInsertion_invoke() { + document.body.offsetTop; // Flush layout. + + let list = document.createElement("ul"); + list.style.display = "contents"; + list.appendChild(document.createElement("li")); + list.firstChild.appendChild(document.createTextNode("Text")); + getNode("c4").appendChild(list); + }; + + this.finalCheck = function displayContentsInsertion_finalCheck() { + var accTree = + { SECTION: [ // container + { LIST: [ + { LISTITEM: [ + { LISTITEM_MARKER: [] }, + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }; + testAccessibleTree("c4", accTree); + }; + + this.getID = function displayContentsInsertion_getID() { + return "insert accessible display: contents element."; + }; + } + + + // ////////////////////////////////////////////////////////////////////////// + // Do tests + // ////////////////////////////////////////////////////////////////////////// + + var gQueue = null; + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + function doTests() { + gQueue = new eventQueue(); + + gQueue.push(new prependAppend("c1")); + gQueue.push(new removeRemove("c2")); + gQueue.push(new insertInaccessibleAccessibleSiblings()); + gQueue.push(new displayContentsInsertion()); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="c1"><input></div> + <div id="c2"><span><input type="checkbox"><input></span><input type="button"></div> + + <div id="c3"><input type="button" value="button"></div> + <div id="c4"></div> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_hidden.html b/accessible/tests/mochitest/treeupdate/test_hidden.html new file mode 100644 index 0000000000..e687fc97c9 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_hidden.html @@ -0,0 +1,125 @@ +<!DOCTYPE html> +<html> + +<head> + <title>@hidden attribute testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + // ////////////////////////////////////////////////////////////////////////// + + /** + * Set @hidden attribute + */ + function setHiddenAttr(aContainerID, aChildID) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function setHiddenAttr_invoke() { + var tree = + { SECTION: [ + { ENTRY: [ + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aChildID).setAttribute("hidden", "true"); + }; + + this.finalCheck = function setHiddenAttr_finalCheck() { + var tree = + { SECTION: [ + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function setHiddenAttr_getID() { + return "Set @hidden attribute on input and test accessible tree for div"; + }; + } + + /** + * Remove @hidden attribute + */ + function removeHiddenAttr(aContainerID, aChildID) { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function removeHiddenAttr_invoke() { + var tree = + { SECTION: [ + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aChildID).removeAttribute("hidden"); + }; + + this.finalCheck = function removeHiddenAttr_finalCheck() { + var tree = + { SECTION: [ + { ENTRY: [ + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function removeHiddenAttr_getID() { + return "Remove @hidden attribute on input and test accessible tree for div"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + // ////////////////////////////////////////////////////////////////////////// + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + gQueue = new eventQueue(); + + gQueue.push(new setHiddenAttr("container", "child")); + gQueue.push(new removeHiddenAttr("container", "child")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + + </script> + +</head> + +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="container"><input id="child"></div> + + <div id="eventdump"></div> + +</body> + +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_imagemap.html b/accessible/tests/mochitest/treeupdate/test_imagemap.html new file mode 100644 index 0000000000..7befef6905 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_imagemap.html @@ -0,0 +1,402 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML img map accessible tree update tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function insertArea(aImageMapID, aMapID) { + this.imageMap = getAccessible(aImageMapID); + this.mapNode = getNode(aMapID); + + function getInsertedArea(aThisObj) { + return aThisObj.imageMap.firstChild; + } + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getInsertedArea, this), + new invokerChecker(EVENT_REORDER, this.imageMap), + ]; + + this.invoke = function insertArea_invoke() { + var areaElm = document.createElement("area"); + areaElm.setAttribute("href", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.bbc.co.uk/radio4/atoz/index.shtml#a"); + areaElm.setAttribute("coords", "0,0,13,14"); + areaElm.setAttribute("alt", "a"); + areaElm.setAttribute("shape", "rect"); + + this.mapNode.insertBefore(areaElm, this.mapNode.firstChild); + }; + + this.finalCheck = function insertArea_finalCheck() { + var accTree = + { IMAGE_MAP: [ + { + role: ROLE_LINK, + name: "a", + children: [ ], + }, + { + role: ROLE_LINK, + name: "b", + children: [ ], + }, + ] }; + testAccessibleTree(this.imageMap, accTree); + }; + + this.getID = function insertArea_getID() { + return "insert area element"; + }; + } + + function appendArea(aImageMapID, aMapID) { + this.imageMap = getAccessible(aImageMapID); + this.mapNode = getNode(aMapID); + + function getAppendedArea(aThisObj) { + return aThisObj.imageMap.lastChild; + } + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getAppendedArea, this), + new invokerChecker(EVENT_REORDER, this.imageMap), + ]; + + this.invoke = function appendArea_invoke() { + var areaElm = document.createElement("area"); + areaElm.setAttribute("href", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.bbc.co.uk/radio4/atoz/index.shtml#c"); + areaElm.setAttribute("coords", "34,0,47,14"); + areaElm.setAttribute("alt", "c"); + areaElm.setAttribute("shape", "rect"); + + this.mapNode.appendChild(areaElm); + }; + + this.finalCheck = function appendArea_finalCheck() { + var accTree = + { IMAGE_MAP: [ + { + role: ROLE_LINK, + name: "a", + children: [ ], + }, + { + role: ROLE_LINK, + name: "b", + children: [ ], + }, + { + role: ROLE_LINK, + name: "c", + children: [ ], + }, + ] }; + testAccessibleTree(this.imageMap, accTree); + }; + + this.getID = function appendArea_getID() { + return "append area element"; + }; + } + + function removeArea(aImageMapID, aMapID) { + this.imageMap = getAccessible(aImageMapID); + this.area = null; + this.mapNode = getNode(aMapID); + + function getRemovedArea(aThisObj) { + return aThisObj.area; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getRemovedArea, this), + new invokerChecker(EVENT_REORDER, this.imageMap), + ]; + + this.invoke = function removeArea_invoke() { + this.area = this.imageMap.firstChild; + this.mapNode.removeChild(this.mapNode.firstElementChild); + }; + + this.finalCheck = function removeArea_finalCheck() { + var accTree = + { IMAGE_MAP: [ + { + role: ROLE_LINK, + name: "b", + children: [ ], + }, + { + role: ROLE_LINK, + name: "c", + children: [ ], + }, + ] }; + testAccessibleTree(this.imageMap, accTree); + }; + + this.getID = function removeArea_getID() { + return "remove area element"; + }; + } + + function removeNameOnMap(aImageMapContainerID, aImageMapID, aMapID) { + this.container = getAccessible(aImageMapContainerID); + this.containerNode = this.container.DOMNode; + this.imageMap = getAccessible(aImageMapID); + this.imgNode = this.imageMap.DOMNode; + this.mapNode = getNode(aMapID); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.imageMap), + new invokerChecker(EVENT_SHOW, this.imgNode), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function removeNameOnMap_invoke() { + this.mapNode.removeAttribute("name"); + }; + + this.finalCheck = function removeNameOnMap_finalCheck() { + var accTree = + { SECTION: [ + { GRAPHIC: [ ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function removeNameOnMap_getID() { + return "remove @name on map element"; + }; + } + + function restoreNameOnMap(aImageMapContainerID, aImageMapID, aMapID) { + this.container = getAccessible(aImageMapContainerID); + this.containerNode = this.container.DOMNode; + this.imageMap = null; + this.imgNode = getNode(aImageMapID); + this.mapNode = getNode(aMapID); + + function getImageMap(aThisObj) { + return aThisObj.imageMap; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getImageMap, this), + new invokerChecker(EVENT_SHOW, this.imgNode), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function restoreNameOnMap_invoke() { + this.imageMap = getAccessible(aImageMapID); + this.mapNode.setAttribute("name", "atoz_map"); + + // XXXhack: force repainting of the image (see bug 745788 for details). + waveOverImageMap(aImageMapID); + }; + + this.finalCheck = function removeNameOnMap_finalCheck() { + var accTree = + { SECTION: [ + { IMAGE_MAP: [ + { LINK: [ ] }, + { LINK: [ ] }, + ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function removeNameOnMap_getID() { + return "restore @name on map element"; + }; + } + + function removeMap(aImageMapContainerID, aImageMapID, aMapID) { + this.container = getAccessible(aImageMapContainerID); + this.containerNode = this.container.DOMNode; + this.imageMap = null; + this.imgNode = getNode(aImageMapID); + this.mapNode = getNode(aMapID); + + function getImageMap(aThisObj) { + return aThisObj.imageMap; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getImageMap, this), + new invokerChecker(EVENT_SHOW, this.imgNode), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function removeMap_invoke() { + this.imageMap = getAccessible(aImageMapID); + this.mapNode.remove(); + }; + + this.finalCheck = function removeMap_finalCheck() { + var accTree = + { SECTION: [ + { GRAPHIC: [ ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function removeMap_getID() { + return "remove map element"; + }; + } + + function insertMap(aImageMapContainerID, aImageID) { + this.container = getAccessible(aImageMapContainerID); + this.containerNode = this.container.DOMNode; + this.image = null; + this.imgMapNode = getNode(aImageID); + + function getImage(aThisObj) { + return aThisObj.image; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getImage, this), + new invokerChecker(EVENT_SHOW, this.imgMapNode), + new invokerChecker(EVENT_REORDER, this.container), + ]; + + this.invoke = function insertMap_invoke() { + this.image = getAccessible(aImageID); + + var map = document.createElement("map"); + map.setAttribute("name", "atoz_map"); + map.setAttribute("id", "map"); + + var area = document.createElement("area"); + area.setAttribute("href", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "http://www.bbc.co.uk/radio4/atoz/index.shtml#b"); + area.setAttribute("coords", "17,0,30,14"); + area.setAttribute("alt", "b"); + area.setAttribute("shape", "rect"); + + map.appendChild(area); + + this.containerNode.appendChild(map); + }; + + this.finalCheck = function insertMap_finalCheck() { + var accTree = + { SECTION: [ + { IMAGE_MAP: [ + { LINK: [ ] }, + ] }, + ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function insertMap_getID() { + return "insert map element"; + }; + } + + function hideImageMap(aContainerID, aImageID) { + this.container = getAccessible(aContainerID); + this.imageMap = null; + this.imageMapNode = getNode(aImageID); + + function getImageMap(aThisObj) { + return aThisObj.imageMap; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getImageMap, this), + new invokerChecker(EVENT_REORDER, aContainerID), + ]; + + this.invoke = function hideImageMap_invoke() { + this.imageMap = getAccessible(this.imageMapNode); + this.imageMapNode.style.display = "none"; + }; + + this.finalCheck = function hideImageMap_finalCheck() { + var accTree = + { SECTION: [ ] }; + testAccessibleTree(this.container, accTree); + }; + + this.getID = function hideImageMap_getID() { + return "display:none image"; + }; + } + + // gA11yEventDumpToConsole = true; // debug stuff + function doPreTest() { + waitForImageMap("imgmap", doTest); + } + + var gQueue = null; + function doTest() { + gQueue = new eventQueue(); + + gQueue.push(new insertArea("imgmap", "map")); + gQueue.push(new appendArea("imgmap", "map")); + gQueue.push(new removeArea("imgmap", "map")); + gQueue.push(new removeNameOnMap("container", "imgmap", "map")); + gQueue.push(new restoreNameOnMap("container", "imgmap", "map")); + gQueue.push(new removeMap("container", "imgmap", "map")); + gQueue.push(new insertMap("container", "imgmap")); + gQueue.push(new hideImageMap("container", "imgmap")); + + // enableLogging("tree"); // debug stuff + // gQueue.onFinish = function() { disableLogging("tree"); } + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doPreTest); + </script> + +</head> +<body> + + <a target="_blank" + title="Image map accessible tree is not updated when image map is changed" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=732389"> + Mozilla Bug 732389 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <map name="atoz_map" id="map"> + <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b" + coords="17,0,30,14" alt="b" shape="rect"> + </map> + + <div id="container"> + <img id="imgmap" width="447" height="15" + usemap="#atoz_map" + src="../letters.gif"><!-- + Important: no whitespace between the <img> and the </div>, so we + don't end up with textframes there, because those would be reflected + in our accessible tree in some cases. + --></div> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_inert.html b/accessible/tests/mochitest/treeupdate/test_inert.html new file mode 100644 index 0000000000..0364f6612b --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_inert.html @@ -0,0 +1,138 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Inert tree update test</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script src="../common.js"></script> + <script src="../promisified-events.js"></script> + <script src="../role.js"></script> + + <script> + async function doTest() { + if (SpecialPowers.getBoolPref("html5.inert.enabled")) { + const inertContainer = getAccessible("inertContainer"); + const inertTree = { SECTION: [ // inertContainer + { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // before + { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // after + ]}; + testAccessibleTree(inertContainer, inertTree); + + info("Unsetting inert"); + let reordered = waitForEvent(EVENT_REORDER, inertContainer); + const inertNode = getNode("inert"); + inertNode.inert = false; + await reordered; + testAccessibleTree(inertContainer, { SECTION: [ // inertContainer + { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // before + { SECTION: [ // inert + { TEXT_LEAF: [] }, + { PUSHBUTTON: [] }, + ] }, + { PARAGRAPH: [{ TEXT_LEAF: [] }] }, // after + ]}); + + info("Setting inert"); + reordered = waitForEvent(EVENT_REORDER, inertContainer); + inertNode.inert = true; + await reordered; + testAccessibleTree(inertContainer, inertTree); + } else { + // It's too late to flip the pref now. We'd need to load a new document + // to pick up the change. + todo(false, "Inert not enabled, skipping inert tests"); + } + + const noDialogTree = { SECTION: [ // dialogContainer + { TEXT_LEAF: [] } + ] }; + testAccessibleTree("dialogContainer", noDialogTree); + + info("Showing modal dialog"); + let reordered = waitForEvent(EVENT_REORDER, document); + const dialogNode = getNode("dialog"); + dialogNode.showModal(); + await reordered; + // The dialog makes everything else in the document inert. + testAccessibleTree(document, { DOCUMENT: [ + { DIALOG: [ + { PUSHBUTTON: [] } + ] } + ] }); + + info("Closing dialog"); + reordered = waitForEvent(EVENT_REORDER, document); + dialogNode.close(); + await reordered; + testAccessibleTree("dialogContainer", noDialogTree); + + const fullscreenTree = { SECTION: [ // fullscreen + { PUSHBUTTON: [] } + ] }; + const notFullscreenTree = { SECTION: [ // fullscreenContainer + { SECTION: [ + { PUSHBUTTON: [] } // requestFullscreen + ] }, + fullscreenTree, + ] }; + testAccessibleTree("fullscreenContainer", notFullscreenTree); + + info("Requesting fullscreen"); + reordered = waitForEvent(EVENT_REORDER, document); + // Fullscreen must be requested by a user input event. + synthesizeMouseAtCenter(getNode("requestFullscreen"), {}); + await reordered; + // Fullscreen makes everything else in the document inert. + testAccessibleTree(document, { DOCUMENT: [ + fullscreenTree + ] }); + + info("Exiting fullscreen"); + reordered = waitForEvent(EVENT_REORDER, document); + document.exitFullscreen(); + await reordered; + testAccessibleTree("fullscreenContainer", notFullscreenTree); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="inertContainer"> + <p>before</p> + <div id="inert" inert>inert <button></button></div> + <p>after</p> + </div> + + <div id="dialogContainer"> + dialogContainer + <dialog id="dialog"><button></button></dialog> + </div> + + <div id="fullscreenContainer"> + <div> + <button id="requestFullscreen" + onclick="document.getElementById('fullscreen').requestFullscreen();"> + </button> + </div> + <div id="fullscreen"><button></button></div> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_inner_reorder.html b/accessible/tests/mochitest/treeupdate/test_inner_reorder.html new file mode 100644 index 0000000000..b4411833d7 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_inner_reorder.html @@ -0,0 +1,148 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test accessible delayed removal</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + + async function testInnerReorder() { + window.windowUtils.advanceTimeAndRefresh(100); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, "c1.1.1"], + [EVENT_SHOW, "c1.1.1"], + [EVENT_INNER_REORDER, "c1.1"], + [EVENT_REORDER, "c1"], + ], "events yay"); + + let child = getNode("c1.1.1"); + child.remove(); + getNode("c1").appendChild(child); + + window.windowUtils.restoreNormalRefresh(); + + await events; + } + + async function testInnerReorderEntry() { + window.windowUtils.advanceTimeAndRefresh(100); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, e => e.accessible.name == "hello"], + [EVENT_HIDE, "c2.2"], + [EVENT_INNER_REORDER, "c2.1"], + [EVENT_REORDER, "c2"], + [EVENT_TEXT_VALUE_CHANGE, "c2.1"], + ], "events yay"); + + getNode("c2.1.1").remove(); + getNode("c2.2").remove(); + + window.windowUtils.restoreNormalRefresh(); + + await events; + } + + async function testInnerReorderAriaOwns() { + let events = waitForOrderedEvents([ + [EVENT_HIDE, "c3.1.1"], + [EVENT_SHOW, "c3.1.1"], + [EVENT_INNER_REORDER, "c3.1"], + [EVENT_REORDER, "c3"], + ], "events yay"); + + getNode("c3").setAttribute("aria-owns", "c3.1.1"); + + await events; + + events = waitForOrderedEvents([ + [EVENT_HIDE, "c3.1.1"], + [EVENT_SHOW, "c3.1.1"], + [EVENT_INNER_REORDER, "c3.1"], + [EVENT_REORDER, "c3"], + ], "events yay"); + + getNode("c3").removeAttribute("aria-owns"); + + await events; + } + + async function testInnerContainerRemoved() { + window.windowUtils.advanceTimeAndRefresh(100); + + let events = waitForOrderedEvents([ + [EVENT_HIDE, "c4.1"], + [EVENT_SHOW, "c4.1.1"], + [EVENT_REORDER, "c4"], + ], "events yay"); + + let child = getNode("c4.1.1"); + child.remove(); + getNode("c1").appendChild(child); + getNode("c4.1").remove(); + + window.windowUtils.restoreNormalRefresh(); + + await events; + } + + + async function doTest() { + await testInnerReorder(); + + await testInnerReorderEntry(); + + await testInnerReorderAriaOwns(); + + await testInnerContainerRemoved(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="c1"> + <div id="c1.1"><div id="c1.1.1">hello</div></div> + </div> + + <div id="c2"> + <div role="textbox" contenteditable="true" id="c2.1"> + <span id="c2.1.1">hello</span> + </div> + <input type="submit" id="c2.2"> + </div> + + <div id="c3"> + <div id="c3.1"><div id="c3.1.1"></div></div> + </div> + + <div id="c4"> + <div id="c4.1"><div id="c4.1.1">hello</div></div> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_list.html b/accessible/tests/mochitest/treeupdate/test_list.html new file mode 100644 index 0000000000..06c308e422 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_list.html @@ -0,0 +1,139 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test HTML li and listitem bullet accessible cache</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Helpers + + function testLiAccessibleTree() { + // Test accessible tree. + var accTree = { + role: ROLE_LISTITEM, + children: [ + { + role: ROLE_LISTITEM_MARKER, + children: [], + }, + { + role: ROLE_TEXT_LEAF, + children: [], + }, + ], + }; + + testAccessibleTree("li", accTree); + } + + // ////////////////////////////////////////////////////////////////////////// + // Sequence item processors + + function hideProcessor() { + this.liNode = getNode("li"); + this.li = getAccessible(this.liNode); + this.bullet = this.li.firstChild; + + this.process = function hideProcessor_process() { + this.liNode.style.display = "none"; + }; + + this.onProcessed = function hideProcessor_onProcessed() { + window.setTimeout( + function(aLiAcc, aLiNode, aBulletAcc) { + testDefunctAccessible(aLiAcc, aLiNode); + testDefunctAccessible(aBulletAcc); + + gSequence.processNext(); + }, + 0, this.li, this.liNode, this.bullet + ); + }; + } + + function showProcessor() { + this.liNode = getNode("li"); + + this.process = function showProcessor_process() { + this.liNode.style.display = "list-item"; + }; + + this.onProcessed = function showProcessor_onProcessed() { + testLiAccessibleTree(); + gSequence.processNext(); + }; + } + + function textReplaceProcessor() { + this.liNode = getNode("li"); + + this.process = function textReplaceProcessor_process() { + this.liNode.textContent = "hey"; + }; + + this.onProcessed = function textReplaceProcessor_onProcessed() { + var tree = { + LISTITEM: [ + { LISTITEM_MARKER: [] }, + { TEXT_LEAF: [] }, + ], + }; + testAccessibleTree(this.liNode, tree); + SimpleTest.finish(); + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpToConsole = true; + + var gSequence = null; + function doTest() { + testLiAccessibleTree(); + + gSequence = new sequence(); + + gSequence.append(new hideProcessor(), EVENT_HIDE, getAccessible("li"), + "hide HTML li"); + gSequence.append(new showProcessor(), EVENT_SHOW, getNode("li"), + "show HTML li"); + gSequence.append(new textReplaceProcessor(), EVENT_REORDER, getNode("li"), + "change text of HTML li"); + + gSequence.processNext(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="setParent shouldn't be virtual" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=496783">Mozilla Bug 496783</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <ul> + <li id="li">item1</li> + </ul> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html b/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html new file mode 100644 index 0000000000..59b9dc9c53 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html @@ -0,0 +1,100 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test HTML li and listitem bullet accessible insertion into editable document</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function addLi(aID) { + this.listNode = getNode(aID); + this.liNode = document.createElement("li"); + this.liNode.textContent = "item"; + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getAccessible, this.liNode), + new invokerChecker(EVENT_REORDER, this.listNode), + ]; + + this.invoke = function addLi_invoke() { + this.listNode.appendChild(this.liNode); + }; + + this.finalCheck = function addLi_finalCheck() { + var tree = { + role: ROLE_LIST, + children: [ + { + role: ROLE_LISTITEM, + children: [ + { + role: ROLE_LISTITEM_MARKER, + name: "1. ", + children: [], + }, + { + role: ROLE_TEXT_LEAF, + children: [], + }, + ], + }, + ], + }; + testAccessibleTree(aID, tree); + }; + + this.getID = function addLi_getID() { + return "add li"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + + var gQueue = null; + + function doTest() { + gQueue = new eventQueue(); + + gQueue.push(new addLi("list")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body contentEditable="true"> + + <a target="_blank" + title="Wrong list bullet text of accessible for the first numbered HTML:li in CKEditor" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=557795">Mozilla Bug 557795</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <ol id="list"> + </ol> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_list_style.html b/accessible/tests/mochitest/treeupdate/test_list_style.html new file mode 100644 index 0000000000..1d9e1c00ca --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_list_style.html @@ -0,0 +1,181 @@ +<html> + +<head> + <title>Test hide/show events for HTMLListBulletAccessibles on list restyle</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../name.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + /** + * Change list style type to none. + */ + async function hideBullet() { + info("Hide bullet by setting style to none"); + + let liAcc = getAccessible("list_element"); + let bullet = liAcc.firstChild; + + let events = waitForEvents([ + [EVENT_HIDE, bullet], + [EVENT_REORDER, liAcc] + ]); + + getNode("list").style.setProperty("list-style-type", "none"); + + await events; + + is(liAcc.name, "list element", + "Check that first child of LI is not a bullet."); + + dumpTree("list"); + } + + /** + * Change list style type to circles. + */ + async function showBullet() { + info("Show bullet by setting style to circle"); + let liAcc = getAccessible("list_element"); + + let events = waitForEvents([ + [EVENT_SHOW, evt => evt.accessible.parent == liAcc], + [EVENT_REORDER, liAcc] + ]); + + getNode("list").style.setProperty("list-style-type", "circle"); + + await events; + + is(liAcc.name, "◦ list element", + "Check that first child of LI is a circle bullet."); + + dumpTree("list"); + } + + /** + * Change list style position. + */ + async function changeBulletPosition() { + info("Change list style position"); + let liAcc = getAccessible("list_element"); + + let events = waitForEvents([ + [EVENT_HIDE, evt => evt.accessible.role == ROLE_LISTITEM_MARKER], + [EVENT_SHOW, evt => evt.accessible.role == ROLE_LISTITEM_MARKER], + [EVENT_REORDER, liAcc] + ]); + + getNode("list").style.setProperty("list-style-position", "inside"); + + await events; + + is(liAcc.name, "◦ list element", + "Check that first child of LI is a circle bullet."); + } + + async function changeBulletPositionAndType() { + let events = waitForEvents([ + [EVENT_HIDE, evt => evt.accessible.role == ROLE_LISTITEM_MARKER], + [EVENT_REORDER, evt => evt.accessible.role == ROLE_LISTITEM] + ]); + + let list = getNode("inside-marker-list"); + + // Bug 1513447 - This changes the list type to "none" and the + // position implicitly to "outside". + list.style.setProperty("list-style", "none"); + list.offsetLeft + list.style.setProperty("list-style-type", "telugu"); + + await events; + } + + async function doTest() { + + testAccessibleTree("list", { LIST: [ // ol + { LISTITEM: [ // li + { LISTITEM_MARKER: [ ] }, + { TEXT_LEAF: [] }, + ] }, + ] } ); + + await hideBullet(); + + testAccessibleTree("list", { LIST: [ // ol + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + ] } ); + + await showBullet(); + + testAccessibleTree("list", { LIST: [ // ol + { LISTITEM: [ // li + { LISTITEM_MARKER: [ ] }, + { TEXT_LEAF: [] }, + ] }, + ] } ); + + await changeBulletPosition(); + + testAccessibleTree("list", { LIST: [ // ol + { LISTITEM: [ // li + { LISTITEM_MARKER: [ ] }, + { TEXT_LEAF: [] }, + ] }, + ] } ); + + testAccessibleTree("unmarked-list", { LIST: [ // ol + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + ] } ); + + await changeBulletPositionAndType(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1100602" + title="[e10s] crash in mozilla::a11y::ProxyAccessible::Shutdown()"> + Mozilla Bug 1100602 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <ol id="list" style="list-style-type: circle;"> + <li id="list_element">list element</li> + </ol> + + <ol id="unmarked-list" style="list-style: none;"> + <li>list element</li> + </ol> + + <ol id="inside-marker-list" style="list-style-position: inside;"> + <li>list element</li> + </ol> + +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_listbox.xhtml b/accessible/tests/mochitest/treeupdate/test_listbox.xhtml new file mode 100644 index 0000000000..629c4b0915 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_listbox.xhtml @@ -0,0 +1,181 @@ +<?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="Accessible XUL listbox hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function insertListitem(aListboxID) + { + this.listboxNode = getNode(aListboxID); + + this.listitemNode = document.createXULElement("richlistitem"); + var label = document.createXULElement("label"); + label.setAttribute("value", "item1"); + this.listitemNode.appendChild(label); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.listitemNode), + new invokerChecker(EVENT_REORDER, this.listboxNode) + ]; + + this.invoke = function insertListitem_invoke() + { + this.listboxNode.insertBefore(this.listitemNode, + this.listboxNode.firstChild); + } + + this.finalCheck = function insertListitem_finalCheck() + { + var tree = + { LISTBOX: [ + { + role: ROLE_RICH_OPTION, + name: "item1" + }, + { + role: ROLE_RICH_OPTION, + name: "item2" + }, + { + role: ROLE_RICH_OPTION, + name: "item3" + }, + { + role: ROLE_RICH_OPTION, + name: "item4" + } + ] }; + testAccessibleTree(this.listboxNode, tree); + } + + this.getID = function insertListitem_getID() + { + return "insert listitem "; + } + } + + function removeListitem(aListboxID) + { + this.listboxNode = getNode(aListboxID); + this.listitemNode = null; + this.listitem; + + function getListitem(aThisObj) + { + return aThisObj.listitem; + } + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getListitem, this), + new invokerChecker(EVENT_REORDER, this.listboxNode) + ]; + + this.invoke = function removeListitem_invoke() + { + this.listitemNode = this.listboxNode.firstChild; + this.listitem = getAccessible(this.listitemNode); + + this.listboxNode.removeChild(this.listitemNode); + } + + this.finalCheck = function removeListitem_finalCheck() + { + var tree = + { LISTBOX: [ + { + role: ROLE_RICH_OPTION, + name: "item2" + }, + { + role: ROLE_RICH_OPTION, + name: "item3" + }, + { + role: ROLE_RICH_OPTION, + name: "item4" + } + ] }; + testAccessibleTree(this.listboxNode, tree); + } + + this.getID = function removeListitem_getID() + { + return "remove listitem "; + } + } + + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTest() + { + var tree = + { LISTBOX: [ + { + role: ROLE_RICH_OPTION, + name: "item2" + }, + { + role: ROLE_RICH_OPTION, + name: "item3" + }, + { + role: ROLE_RICH_OPTION, + name: "item4" + } + ] }; + testAccessibleTree("listbox", tree); + + gQueue = new eventQueue(); + gQueue.push(new insertListitem("listbox")); + gQueue.push(new removeListitem("listbox")); + gQueue.invoke(); // Will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=656225" + title="XUL listbox accessible tree doesn't get updated"> + Mozilla Bug 656225 + </a> + <br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <richlistbox id="listbox"> + <richlistitem><label value="item2"/></richlistitem> + <richlistitem><label value="item3"/></richlistitem> + <richlistitem><label value="item4"/></richlistitem> + </richlistbox> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_menu.xhtml b/accessible/tests/mochitest/treeupdate/test_menu.xhtml new file mode 100644 index 0000000000..44042cc9e7 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_menu.xhtml @@ -0,0 +1,127 @@ +<?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="Accessible XUL menu hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function openMenu(aID) + { + this.menuNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, this.menuNode) + ]; + + this.invoke = function openMenu_invoke() + { + var tree; + if (LINUX || SOLARIS) { + tree = + { PARENT_MENUITEM: [ ] }; + + } else { + tree = + { PARENT_MENUITEM: [ + { MENUPOPUP: [ ] } + ] }; + } + testAccessibleTree(aID, tree); + + // Show menu. + this.menuNode.open = true; + } + + this.finalCheck = function openMenu_finalCheck() + { + var tree; + if (LINUX || SOLARIS) { + tree = + { PARENT_MENUITEM: [ + { MENUITEM: [ ] }, + { MENUITEM: [ ] } + ] }; + + } else { + tree = + { PARENT_MENUITEM: [ + { MENUPOPUP: [ + { MENUITEM: [ ] }, + { MENUITEM: [ ] } + ] } + ] }; + } + testAccessibleTree(aID, tree); + } + + this.getID = function openMenu_getID() + { + return "open menu " + prettyName(aID); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + gQueue.push(new openMenu("menu")); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292" + title="Ensure accessible children for toolbarbutton types 'menu'"> + Mozilla Bug 249292 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486" + title="Don't force accessible creation for popup children."> + Mozilla Bug 630486 + </a> + <br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <menubar> + <menu id="menu" label="menu"> + <menupopup> + <menuitem label="menuitem"/> + <menuitem label="menuitem"/> + </menupopup> + </menu> + </menubar> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_menubutton.xhtml b/accessible/tests/mochitest/treeupdate/test_menubutton.xhtml new file mode 100644 index 0000000000..d3eeb29f78 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_menubutton.xhtml @@ -0,0 +1,141 @@ +<?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="Accessible XUL button hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function openMenu(aButtonID, aIsTree) + { + var menuItemRole = aIsTree ? ROLE_CHECK_MENU_ITEM : ROLE_MENUITEM; + this.button = getAccessible(aButtonID); + this.menupopup = aIsTree ? this.button.nextSibling : this.button.firstChild; + + var checker = new invokerChecker(EVENT_REORDER, this.menupopup); + this.__proto__ = new synthClick(aButtonID, checker); + + let testButton = popup => { + var children = []; + if (!aIsTree) { + children.push(popup); + } + var tree = { PUSHBUTTON: children }; + testAccessibleTree(this.button, tree); + testAccessibleTree(this.menupop, popup); + } + + this.invoke = function openMenu_invoke() + { + testButton({ MENUPOPUP: [ ] }); + this.__proto__.invoke(); + } + + this.finalCheck = function openMenu_finalCheck() + { + testButton({ MENUPOPUP: [ + { role: menuItemRole, children: [ ] }, + { role: menuItemRole, children: [ ] } + ] }); + + synthesizeKey("KEY_Escape"); + } + + this.getID = function openMenu_getID() + { + return "open menu of the button " + prettyName(aButtonID); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do test + + gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new openMenu("button1")); + gQueue.push(new openMenu("button3")); + + var columnPickerBtn = getAccessible("tree").firstChild.lastChild.previousSibling; + gQueue.push(new openMenu(columnPickerBtn, true)); + gQueue.invoke(); // SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292" + title="Ensure accessible children for toolbarbutton types 'menu'"> + Bug 249292 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=630486" + title="Don't force accessible creation for popup children"> + Bug 630486 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=722265" + title="Column header selection popup no longer exposed to accessibility APIs"> + Bug 722265 + </a> + <br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <button id="button1" type="menu" label="button"> + <menupopup> + <menuitem label="menuitem"/> + <menuitem label="menuitem"/> + </menupopup> + </button> + + <toolbarbutton id="button3" type="menu" label="toolbarbutton"> + <menupopup> + <menuitem label="menuitem"/> + <menuitem label="menuitem"/> + </menupopup> + </toolbarbutton> + + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + <treecol id="col2" flex="1" label="another column"/> + </treecols> + <treechildren/> + </tree> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_optgroup.html b/accessible/tests/mochitest/treeupdate/test_optgroup.html new file mode 100644 index 0000000000..943b73be43 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_optgroup.html @@ -0,0 +1,122 @@ +<!DOCTYPE html> +<html> +<head> + <title>Add and remove optgroup test</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + function addOptGroup(aID) { + this.selectNode = getNode(aID); + this.select = getAccessible(this.selectNode); + this.selectList = this.select.firstChild; + + this.invoke = function addOptGroup_invoke() { + var optGroup = document.createElement("optgroup"); + for (let i = 0; i < 2; i++) { + var opt = document.createElement("option"); + opt.value = i; + opt.text = "Option: Value " + i; + + optGroup.appendChild(opt); + } + + this.selectNode.add(optGroup, null); + var option = document.createElement("option"); + this.selectNode.add(option, null); + }; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.selectList), + ]; + + this.finalCheck = function addOptGroup_finalCheck() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [ + { GROUPING: [ + { COMBOBOX_OPTION: [ ] }, + { COMBOBOX_OPTION: [ ] }, + ]}, + { COMBOBOX_OPTION: [] }, + ] }, + ] }; + testAccessibleTree(this.select, tree); + }; + + this.getID = function addOptGroup_getID() { + return "test optgroup's insertion into a select"; + }; + } + + function removeOptGroup(aID) { + this.selectNode = getNode(aID); + this.select = getAccessible(this.selectNode); + this.selectList = this.select.firstChild; + + this.invoke = function removeOptGroup_invoke() { + this.option1Node = this.selectNode.firstChild.firstChild; + this.selectNode.firstChild.remove(); + }; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.selectList), + ]; + + this.finalCheck = function removeOptGroup_finalCheck() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [ + { COMBOBOX_OPTION: [] }, + ] }, + ] }; + testAccessibleTree(this.select, tree); + is(isAccessible(this.option1Node), false, "removed option shouldn't be accessible anymore!"); + }; + + this.getID = function removeOptGroup_getID() { + return "test optgroup's removal from a select"; + }; + } + + // gA11yEventDumpToConsole = true; + + function doTest() { + const gQueue = new eventQueue(); + + gQueue.push(new addOptGroup("select")); + gQueue.push(new removeOptGroup("select")); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=616452" + title="Bug 616452 - Dynamically inserted select options aren't reflected in accessible tree"> + Bug 616452</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select id="select"></select> + + <div id="debug"/> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_recreation.html b/accessible/tests/mochitest/treeupdate/test_recreation.html new file mode 100644 index 0000000000..d403b0890e --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_recreation.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test accessible recreation</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + + async function doTest() { + let events, msg; + + msg = "Assign a 'button' role to a span"; + events = waitForOrderedEvents( + [[EVENT_HIDE], [EVENT_SHOW, "span"], [EVENT_REORDER, "container"]], msg); + document.getElementById("span").setAttribute("role", "button"); + await events; + + msg = "Remove the 'button' role from a span"; + events = waitForOrderedEvents( + [[EVENT_HIDE, "span"], [EVENT_SHOW], [EVENT_REORDER, "container"]], msg); + document.getElementById("span").removeAttribute("role"); + await events; + + msg = "Assign a 'button' role to a div"; + events = waitForOrderedEvents( + [[EVENT_HIDE, "div1"], [EVENT_SHOW, "div1"], [EVENT_REORDER, "container"]], msg); + document.getElementById("div1").setAttribute("role", "button"); + await events; + + msg = "Change a password field to a text field"; + events = waitForOrderedEvents( + [[EVENT_HIDE, "password"], [EVENT_SHOW, "password"], [EVENT_REORDER, "container"]], msg); + document.getElementById("password").setAttribute("type", "text"); + await events; + + msg = "Change a text field to a password field"; + events = waitForOrderedEvents( + [[EVENT_HIDE, "text"], [EVENT_SHOW, "text"], [EVENT_REORDER, "container"]], msg); + document.getElementById("text").setAttribute("type", "password"); + await events; + + msg = "Add aria-label to a span"; + ok(!isAccessible("span2"), "span2 has no accessible"); + events = waitForOrderedEvents( + [[EVENT_SHOW, "span2"], [EVENT_REORDER, "container"]], msg); + document.getElementById("span2").setAttribute("aria-label", "label"); + await events; + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="container"> + <span id="span">span</span> + <div id="div1">div</div> + <a id="anchor">anchor</a> + <div id="div3" role="listbox">list</div> + <input type="password" id="password"/> + <input type="text" id="text"/> + <span id="span2"></span> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_select.html b/accessible/tests/mochitest/treeupdate/test_select.html new file mode 100644 index 0000000000..eabb64f80f --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_select.html @@ -0,0 +1,191 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML select options test</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + function addOptions(aID) { + this.selectNode = getNode(aID); + this.select = getAccessible(this.selectNode); + this.selectList = this.select.firstChild; + + this.invoke = function addOptions_invoke() { + for (let i = 0; i < 2; i++) { + var opt = document.createElement("option"); + opt.value = i; + opt.text = "Option: Value " + i; + + this.selectNode.add(opt, null); + } + }; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.selectList), + ]; + + this.finalCheck = function addOptions_finalCheck() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [ + { COMBOBOX_OPTION: [ ] }, + { COMBOBOX_OPTION: [ ] }, + ] }, + ] }; + testAccessibleTree(this.select, tree); + }; + + this.getID = function addOptions_getID() { + return "test elements insertion into a select"; + }; + } + + function removeOptions(aID) { + this.selectNode = getNode(aID); + this.select = getAccessible(this.selectNode); + this.selectList = this.select.firstChild; + + this.invoke = function removeOptions_invoke() { + while (this.selectNode.length) + this.selectNode.remove(0); + }; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.selectList), + ]; + + this.finalCheck = function removeOptions_finalCheck() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [] }, + ] }; + testAccessibleTree(this.select, tree); + }; + + this.getID = function removeptions_getID() { + return "test elements removal from a select"; + }; + } + + /** + * Setting role=option on option makes the accessible recreate. + */ + function setRoleOnOption() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, "s2_o"), + new invokerChecker(EVENT_SHOW, "s2_o"), + ]; + + this.invoke = function setRoleOnOption_setRole() { + getNode("s2_o").setAttribute("role", "option"); + }; + + this.finalCheck = function() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [ + { COMBOBOX_OPTION: [ ] }, + ] }, + ] }; + testAccessibleTree("s2", tree); + }; + + this.getID = function removeptions_getID() { + return "setting role=option on select option"; + }; + } + + /** + * Setting multiple on select makes the accessible recreate. + */ + function setMultipleOnSelect() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, "s3"), + new invokerChecker(EVENT_SHOW, "s3"), + ]; + + this.invoke = function setRoleOnOption_setRole() { + getNode("s3").multiple = true; + }; + + this.finalCheck = function() { + var tree = + { LISTBOX: [ + { OPTION: [ ] }, + ] }; + testAccessibleTree("s3", tree); + }; + + this.getID = function removeptions_getID() { + return "setting multiple on select element"; + }; + } + + + /** + * Removing size on select makes the accessible recreate. + */ + function removeSizeFromSelect() { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, "s4"), + new invokerChecker(EVENT_SHOW, "s4"), + ]; + + this.invoke = function setRoleOnOption_setRole() { + getNode("s4").removeAttribute("size"); + }; + + this.finalCheck = function() { + var tree = + { COMBOBOX: [ + { COMBOBOX_LIST: [ + { COMBOBOX_OPTION: [ ] }, + ] }, + ] }; + testAccessibleTree("s4", tree); + }; + + this.getID = function removeptions_getID() { + return "removing size from select element"; + }; + } + + function doTest() { + const gQueue = new eventQueue(); + + gQueue.push(new addOptions("select")); + gQueue.push(new removeOptions("select")); + gQueue.push(new setRoleOnOption()); + gQueue.push(new setMultipleOnSelect()); + gQueue.push(new removeSizeFromSelect()); + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <select id="select"></select> + <select id="s2"><option id="s2_o"></option></select> + <select id="s3"><option></option></select> + <select id="s4" size="3"><option></option></select> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_shadow_slots.html b/accessible/tests/mochitest/treeupdate/test_shadow_slots.html new file mode 100644 index 0000000000..27baef0e4a --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_shadow_slots.html @@ -0,0 +1,554 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test shadow roots with slots</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../promisified-events.js"></script> + + <script type="application/javascript"> + async function _dynamicShadowTest(name, mutationFunc, expectedTree, reorder_targets) { + info(name); + + let container = getNode(name); + let host = container.querySelector('.host'); + + document.body.offsetTop; + let event = reorder_targets ? + waitForEvents(reorder_targets.map(target => [EVENT_REORDER, target, name])) : + waitForEvent(EVENT_REORDER, host, name); + + mutationFunc(container, host); + + await event; + + testAccessibleTree(container, expectedTree); + + return true; + } + + async function attachFlatShadow() { + await _dynamicShadowTest("attachFlatShadow", + (container, host) => { + host.attachShadow({ mode: "open" }) + .appendChild(container.querySelector('.shadowtree').content.cloneNode(true)); + }, { SECTION: [{ SECTION: [{ name: "red"} ] }] }); + } + + async function attachOneDeepShadow() { + await _dynamicShadowTest("attachOneDeepShadow", + (container, host) => { + host.attachShadow({ mode: "open" }) + .appendChild(container.querySelector('.shadowtree').content.cloneNode(true)); + }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "red"} ] }] }] }); + } + + async function changeSlotFlat() { + await _dynamicShadowTest("changeSlotFlat", + (container, host) => { + container.querySelector('.red').removeAttribute('slot'); + container.querySelector('.green').setAttribute('slot', 'myslot'); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + async function changeSlotOneDeep() { + await _dynamicShadowTest("changeSlotOneDeep", + (container, host) => { + container.querySelector('.red').removeAttribute('slot'); + container.querySelector('.green').setAttribute('slot', 'myslot'); + }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "green"} ] }] }] }, ["shadowdiv"]); + } + + // Nested roots and slots + async function changeSlotNested() { + await _dynamicShadowTest("changeSlotNested", + (container, host) => { + testAccessibleTree(getNode("changeSlotNested"), + { SECTION: [{ SECTION: [{ SECTION: [{ name: "red"} ] }] }] }); + container.querySelector('.red').removeAttribute('slot'); + container.querySelector('.green').setAttribute('slot', 'myslot'); + }, { SECTION: [{ SECTION: [{ SECTION: [{ name: "green"} ] }] }] }, ["shadowdiv"]); + } + + async function changeSlotSingleChild() { + await _dynamicShadowTest("changeSlotSingleChild", + (container, host) => { + container.querySelector('.red').setAttribute('slot', 'invalid'); + }, { SECTION: [{ SECTION: [] }] }); + } + + async function changeSlotNoShadow() { + await _dynamicShadowTest("changeSlotNoShadow", + (container, host) => { + // Make sure changing the slot attribute doesn't remove an element + // even when it remains in the flat tree. + container.querySelector('.red').setAttribute('slot', 'invalid'); + // We need a reorder to know when we're done here, so remove another + // child. + container.querySelector('.green').remove(); + }, { SECTION: [{ SECTION: [{ name: "red"} ] }] }); + } + + // Dynamic mutations to both shadow root and shadow host subtrees + // testing/web-platform/tests/css/css-scoping/shadow-assign-dynamic-001.html + async function assignSlotDynamic() { + await _dynamicShadowTest("assignSlotDynamic", + (container, host) => { + host.shadowRoot.appendChild(container.querySelector('.shadowtree').content.cloneNode(true)); + host.appendChild(container.querySelector('.lighttree').content.cloneNode(true)); + }, { SECTION: [{ SECTION: [{ name: "slot1"}, { name: "slot2" } ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-001.html + async function shadowFallbackDynamic_1() { + await _dynamicShadowTest("shadowFallbackDynamic_1", + (container, host) => { + host.firstElementChild.remove(); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-002.html + async function shadowFallbackDynamic_2() { + await _dynamicShadowTest("shadowFallbackDynamic_2", + (container, host) => { + host.firstElementChild.removeAttribute("slot"); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-003.html + async function shadowFallbackDynamic_3() { + await _dynamicShadowTest("shadowFallbackDynamic_3", + (container, host) => { + host.appendChild(container.querySelector(".lighttree").content.cloneNode(true)); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-004.html + async function shadowFallbackDynamic_4() { + await _dynamicShadowTest("shadowFallbackDynamic_4", + (container, host) => { + host.shadowRoot.insertBefore( + container.querySelector(".moreshadowtree"). + content.cloneNode(true), host.shadowRoot.firstChild); + }, { SECTION: [{ SECTION: [{ name: "slotparent2", children: [{ name: "green"} ] }, { name: "slotparent1" } ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-004.html + // This tests a case when the the slotted element would remain in the same accessible container + async function shadowFallbackDynamic_4_1() { + await _dynamicShadowTest("shadowFallbackDynamic_4_1", + (container, host) => { + host.shadowRoot.insertBefore( + container.querySelector(".moreshadowtree"). + content.cloneNode(true), host.shadowRoot.firstChild); + }, { SECTION: [{ SECTION: [ { name: "green"}, { SEPARATOR: [] } ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-005.html + async function shadowFallbackDynamic_5() { + await _dynamicShadowTest("shadowFallbackDynamic_5", + (container, host) => { + host.firstElementChild.setAttribute("slot", "myotherslot"); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-002.html + async function shadowReassignDynamic_2() { + await _dynamicShadowTest("shadowReassignDynamic_2", + (container, host) => { + host.shadowRoot.querySelector("slot").setAttribute("name", "myslot"); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-003.html + async function shadowReassignDynamic_3() { + await _dynamicShadowTest("shadowReassignDynamic_3", + (container, host) => { + testAccessibleTree(container, { SECTION: [{ SECTION: [{ name: "green"}, { name: "red", children: [ { PUSHBUTTON: [] }]} ] }] }); + host.shadowRoot.querySelector("slot[name]").removeAttribute("name"); + + }, { SECTION: [{ SECTION: [{ name: "green", children: [ { PUSHBUTTON: [] }]}, { name: "red"} ] }] }, + [evt => evt.accessible.name == "green", evt => evt.accessible.name == "red"]); + } + + // testing/web-platform/tests/css/css-scoping/shadow-reassign-dynamic-004.html + async function shadowReassignDynamic_4() { + await _dynamicShadowTest("shadowReassignDynamic_4", + (container, host) => { + host.shadowRoot.getElementById("slot").remove(); + }, { SECTION: [{ SECTION: [{ name: "green"} ] }] }); + } + + function shadowProcessInvalidation() { + testAccessibleTree("shadowProcessInvalidation", + { SECTION: [{ + SECTION: [{ + SECTION: [{ TEXT_LEAF: { name: "Hello "} }, + { TEXT: [{ TEXT_LEAF: { name: "World"} }] }, + { PUSHBUTTON: { name: "World"} }] + }] + }] + }); + } + + async function justAttachShadow() { + await _dynamicShadowTest("justAttachShadow", + (container, host) => { + host.attachShadow({ mode: "open" }); + }, { SECTION: [{ SECTION: [] }] }); + } + + async function doTest() { + await attachFlatShadow(); + + await attachOneDeepShadow(); + + await changeSlotFlat(); + + await changeSlotOneDeep(); + + await changeSlotNested(); + + await changeSlotSingleChild(); + + await changeSlotNoShadow(); + + await assignSlotDynamic(); + + await shadowFallbackDynamic_1(); + + await shadowFallbackDynamic_2(); + + await shadowFallbackDynamic_3(); + + await shadowFallbackDynamic_4(); + + await shadowFallbackDynamic_4_1(); + + await shadowFallbackDynamic_5(); + + await shadowReassignDynamic_2(); + + await shadowReassignDynamic_3(); + + await shadowReassignDynamic_4(); + + shadowProcessInvalidation(); + + await justAttachShadow(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="attachFlatShadow"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <slot name="myslot">FAIL</slot> + </template> + <section class="host"> + <div style="background: green" aria-label="green"></div> + <div style="background: red" aria-label="red" slot="myslot"></div> + </section> + </div> + + <div id="attachOneDeepShadow"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <div id="shadowdiv"> + <slot name="myslot">FAIL</slot> + </div> + </template> + <section class="host"> + <div style="background: green" aria-label="green"></div> + <div style="background: red" aria-label="red" slot="myslot"></div> + </section> + </div> + + <div id="changeSlotFlat"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <slot name="myslot">FAIL</slot> + </template> + <section class="host"> + <div class="green" style="background: green" aria-label="green"></div> + <div class="red" style="background: red" aria-label="red" slot="myslot"></div> + </section> + <script> + document.querySelector("#changeSlotFlat > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#changeSlotFlat > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="changeSlotOneDeep"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <div id="shadowdiv"> + <slot name="myslot">FAIL</slot> + </div> + </template> + <section class="host"> + <div class="green" style="background: green" aria-label="green"></div> + <div class="red" style="background: red" aria-label="red" slot="myslot"></div> + </section> + <script> + document.querySelector("#changeSlotOneDeep > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#changeSlotOneDeep > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="changeSlotNested"> + <template class="shadowtree outer"> + <div id="shadowdiv"> + <slot name="myslot">FAIL</slot> + </div> + </template> + <template class="shadowtree inner"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <slot>FAIL</slot> + </template> + <section class="host"> + <div class="green" style="background: green" aria-label="green"></div> + <div class="red" style="background: red" aria-label="red" slot="myslot"></div> + </section> + <script> + (function foo() { + let outerShadow = + document.querySelector("#changeSlotNested > .host"). + attachShadow({ mode: "open" }); + outerShadow.appendChild( + document.querySelector("#changeSlotNested > .shadowtree.outer"). + content.cloneNode(true)); + let innerShadow = + outerShadow.querySelector("#shadowdiv"). + attachShadow({ mode: "open" }); + innerShadow.appendChild( + document.querySelector("#changeSlotNested > .shadowtree.inner"). + content.cloneNode(true)); + })(); + </script> + </div> + + <div id="changeSlotSingleChild"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <slot></slot> + </template> + <section class="host"> + <div class="red" style="background: red" aria-label="red"></div> + </section> + <script> + document.querySelector("#changeSlotSingleChild > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#changeSlotSingleChild > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="changeSlotNoShadow"> + <section class="host"> + <div class="red" style="background: red; width: 100px; height: 100px;" aria-label="red"></div> + <div class="green" style="background: green; width: 100px; height: 100px;" aria-label="green"></div> + </section> + </div> + + <div id="assignSlotDynamic"> + <template class="shadowtree"> + <style>::slotted(div) { width: 50px; height: 100px }</style> + <slot name="slot1">FAIL</slot> + <slot name="slot2">FAIL</slot> + </template> + <template class="lighttree"> + <div aria-label="slot1" slot="slot1"></div> + <div aria-label="slot2" slot="slot2"></div> + </template> + <section class="host"></section> + <script> + document.querySelector("#assignSlotDynamic > .host").attachShadow({ mode: "open" }); + </script> + </div> + + <div id="shadowFallbackDynamic_1"> + <template class="shadowtree"> + <slot name="myslot"> + <div aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </slot> + </template> + <section class="host"><span slot="myslot">FAIL</span></section> + <script> + document.querySelector("#shadowFallbackDynamic_1 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_1 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowFallbackDynamic_2"> + <template class="shadowtree"> + <slot name="myslot"> + <div aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </slot> + </template> + <section class="host"><span slot="myslot">FAIL</span></section> + <script> + document.querySelector("#shadowFallbackDynamic_2 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_2 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowFallbackDynamic_3"> + <template class="shadowtree"> + <slot name="myslot">FAIL</slot> + </template> + <template class="lighttree"> + <div aria-label="green" slot="myslot" style="width: 100px; height: 100px; background: green"></div> + </template> + <section class="host"></section> + <script> + document.querySelector("#shadowFallbackDynamic_3 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_3 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowFallbackDynamic_4"> + <template class="shadowtree"> + <div aria-label="slotparent1"><slot name="myslot"></slot></div> + </template> + <template class="moreshadowtree"> + <div aria-label="slotparent2"><slot name="myslot">FAIL</slot></div> + </template> + <section class="host"> + <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </section> + <script> + document.querySelector("#shadowFallbackDynamic_4 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_4 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowFallbackDynamic_4_1"> + <template class="shadowtree"> + <hr> + <slot name="myslot"></slot> + </template> + <template class="moreshadowtree"> + <slot name="myslot">FAIL</slot> + </template> + <section class="host"> + <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </section> + <script> + document.querySelector("#shadowFallbackDynamic_4_1 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_4_1 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowFallbackDynamic_5"> + <template class="shadowtree"> + <slot name="myslot"></slot> + <slot name="myotherslot">FAIL</slot> + </template> + <section class="host"> + <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </section> + <script> + document.querySelector("#shadowFallbackDynamic_5 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowFallbackDynamic_5 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowReassignDynamic_2"> + <template class="shadowtree"> + <style>::slotted(div) { width: 100px; height: 100px }</style> + <slot>FAIL</slot> + </template> + <section class="host"> + <div slot="myslot" aria-label="green" style="width: 100px; height: 100px; background: green"></div> + </section> + <script> + document.querySelector("#shadowReassignDynamic_2 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowReassignDynamic_2 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowReassignDynamic_3"> + <template class="shadowtree"> + <div aria-label="green"><slot name="nomatch"></slot></div> + <div aria-label="red"><slot></slot></div> + </template> + <section class="host"> + <div role="button"></div> + </section> + <script> + document.querySelector("#shadowReassignDynamic_3 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowReassignDynamic_3 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowReassignDynamic_4"> + <template class="shadowtree"> + <style>::slotted(div),div { width: 100px; height: 100px }</style> + <slot id="slot"></slot> + <slot> + <div aria-label="red" style="background: red"></div> + </slot> + </template> + <section class="host"> + <div aria-label="green" style="background: green"></div> + </section> + <script> + document.querySelector("#shadowReassignDynamic_4 > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowReassignDynamic_4 > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="shadowProcessInvalidation"> + <template class="shadowtree"> + <div id="shadowdiv"> + <slot></slot> + </div> + </template> + <section class="host">Hello <span id="c">World</span><button aria-labelledby="c"></button></section> + <script> + document.querySelector("#shadowProcessInvalidation > .host") + .attachShadow({ mode: "open" }) + .appendChild(document.querySelector("#shadowProcessInvalidation > .shadowtree").content.cloneNode(true)); + </script> + </div> + + <div id="justAttachShadow"> + <section class="host"> + <button></button> + </section> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml b/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml new file mode 100644 index 0000000000..ad8aebf812 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_shutdown.xhtml @@ -0,0 +1,131 @@ +<?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="Accessible XUL tree hierarchy tests"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../treeview.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../states.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + function setXULTreeView(aTreeID, aTreeView) + { + this.treeNode = getNode(aTreeID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.treeNode) + ]; + + this.invoke = function loadXULTree_invoke() + { + this.treeNode.view = aTreeView; + }; + + this.getID = function loadXULTree_getID() + { + return "Load XUL tree " + prettyName(aTreeID); + }; + } + + function removeTree(aID) + { + this.tree = getAccessible(aID); + this.lastItem = null; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, document) + ]; + + this.invoke = function invoke() + { + this.lastItem = getAccessible(aID).lastChild; + this.lastCell = this.lastItem.lastChild; + getNode(aID).remove(); + }; + + this.check = function check(aEvent) + { + testIsDefunct(this.tree, aID); + testIsDefunct(this.lastItem, "last item of " + aID); + if (this.lastCell) { + testIsDefunct(this.lastCell, "last item cell of " + aID); + } + }; + + this.getID = function getID() + { + return "Remove tree from DOM"; + }; + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "debug"; + var gQueue = null; + + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new setXULTreeView("tree", new nsTreeTreeView())); + gQueue.push(new removeTree("tree")); + + gQueue.push(new setXULTreeView("treetable", new nsTreeTreeView())); + gQueue.push(new removeTree("treetable")); + + gQueue.invoke(); // Will call SimpleTest.finish() + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=503727" + title="Reorganize implementation of XUL tree accessibility"> + Bug 503727 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <tree id="tree" flex="1"> + <treecols> + <treecol id="col" flex="1" primary="true" label="column"/> + </treecols> + <treechildren/> + </tree> + + <tree id="treetable" flex="1"> + <treecols> + <treecol id="col1" flex="1" primary="true" label="column"/> + <treecol id="col2" flex="1" label="column 2"/> + </treecols> + <treechildren/> + </tree> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/treeupdate/test_table.html b/accessible/tests/mochitest/treeupdate/test_table.html new file mode 100644 index 0000000000..50fac91757 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_table.html @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<html> +<head> + <title>Table update tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + function appendCaption(aTableID) { + this.invoke = function appendCaption_invoke() { + // append a caption, it should appear as a first element in the + // accessible tree. + var caption = document.createElement("caption"); + caption.textContent = "table caption"; + getNode(aTableID).appendChild(caption); + }; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aTableID), + ]; + + this.finalCheck = function appendCaption_finalCheck() { + var tree = + { TABLE: [ + { CAPTION: [ + { TEXT_LEAF: [] }, + ] }, + { ROW: [ + { CELL: [ {TEXT_LEAF: [] }]}, + { CELL: [ {TEXT_LEAF: [] }]}, + ] }, + ] }; + testAccessibleTree(aTableID, tree); + }; + + this.getID = function appendCaption_getID() { + return "append caption"; + }; + } + + function doTest() { + const gQueue = new eventQueue(); + gQueue.push(new appendCaption("table")); + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <table id="table"> + <tr> + <td>cell1</td> + <td>cell2</td> + </tr> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_textleaf.html b/accessible/tests/mochitest/treeupdate/test_textleaf.html new file mode 100644 index 0000000000..f0181dd754 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_textleaf.html @@ -0,0 +1,167 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test accessible recreation</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + function textLeafUpdate(aID, aIsTextLeafLinkable) { + this.node = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.node.parentNode), + ]; + + this.finalCheck = function textLeafUpdate_finalCheck() { + var textLeaf = getAccessible(this.node).firstChild; + is(textLeaf.actionCount, (aIsTextLeafLinkable ? 1 : 0), + "Wrong action numbers!"); + }; + } + + function setOnClickAttr(aID) { + var node = getNode(aID); + node.setAttribute("onclick", "alert(3);"); + var textLeaf = getAccessible(node).firstChild; + is(textLeaf.actionCount, 1, "setOnClickAttr: wrong action numbers!"); + } + + function removeOnClickAttr(aID) { + var node = getNode(aID); + node.removeAttribute("onclick"); + var textLeaf = getAccessible(node).firstChild; + is(textLeaf.actionCount, 0, + "removeOnClickAttr: wrong action numbers!"); + } + + function setOnClickNRoleAttrs(aID) { + this.__proto__ = new textLeafUpdate(aID, true); + + this.invoke = function setOnClickAttr_invoke() { + this.node.setAttribute("role", "link"); + this.node.setAttribute("onclick", "alert(3);"); + }; + + this.getID = function setOnClickAttr_getID() { + return "make " + prettyName(aID) + " linkable"; + }; + } + + function removeTextData(aID, aRole) { + this.containerNode = getNode(aID); + this.textNode = this.containerNode.firstChild; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.invoke = function removeTextData_invoke() { + var tree = { + role: aRole, + children: [ + { + role: ROLE_TEXT_LEAF, + name: "text", + }, + ], + }; + testAccessibleTree(this.containerNode, tree); + + this.textNode.data = ""; + }; + + this.finalCheck = function removeTextData_finalCheck() { + var tree = { + role: aRole, + children: [], + }; + testAccessibleTree(this.containerNode, tree); + }; + + this.getID = function removeTextData_finalCheck() { + return "remove text data of text node inside '" + aID + "'."; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + // adds onclick on element, text leaf should inherit its action + setOnClickAttr("div"); + // remove onclick attribute, text leaf shouldn't have any action + removeOnClickAttr("div"); + + // Call rest of event tests. + gQueue = new eventQueue(); + + // set onclick attribute making span accessible, it's inserted into tree + // and adopts text leaf accessible, text leaf should have an action + gQueue.push(new setOnClickNRoleAttrs("span")); + + // text data removal of text node should remove its text accessible + gQueue.push(new removeTextData("p", ROLE_PARAGRAPH)); + gQueue.push(new removeTextData("pre", ROLE_TEXT_CONTAINER)); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Clean up the code of accessible initialization and binding to the tree" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=545465"> + Mozilla Bug 545465 + </a> + <a target="_blank" + title="Make sure accessible tree is correct when rendered text is changed" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652"> + Mozilla Bug 625652 + </a> + <a target="_blank" + title="Remove text accesible getting no text inside a preformatted area" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=706335"> + Mozilla Bug 706335 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="container"> + <div id="div">div</div> + <span id="span">span</span> + </div> + + <p id="p">text</p> + <pre id="pre">text</pre> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_tooltip.xhtml b/accessible/tests/mochitest/treeupdate/test_tooltip.xhtml new file mode 100644 index 0000000000..dba83b2267 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_tooltip.xhtml @@ -0,0 +1,75 @@ +<?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="Accessible XUL tooltip test"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" /> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../role.js" /> + <script type="application/javascript" + src="../promisified-events.js" /> + + <script type="application/javascript"> + <![CDATA[ + + async function doTest() { + let tooltip = document.getElementById("tooltip"); + + testAccessibleTree("tooltip-container", { GROUPING: [ + ] }); + + let shown = waitForEvent(EVENT_SHOW, tooltip); + tooltip.openPopup(); + await shown; + + testAccessibleTree("tooltip-container", + { GROUPING: [ + { TOOLTIP: [] }, + { STATICTEXT: [] }, + ] }); + + let hidden = waitForEvent(EVENT_HIDE, tooltip); + tooltip.hidePopup(); + await hidden; + + testAccessibleTree("tooltip-container", { GROUPING: [] }); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + ]]> + </script> + + <hbox flex="1" style="overflow: auto;"> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1652211" + title="Added anonymous tooltip to mochitest docs messes with text"> + Bug 1652211 + </a> + <br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1" role="group" id="tooltip-container"> + <tooltip id="tooltip"> + <description class="tooltip-label" value="hello world"/> + </tooltip> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_visibility.html b/accessible/tests/mochitest/treeupdate/test_visibility.html new file mode 100644 index 0000000000..4107832b3e --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_visibility.html @@ -0,0 +1,411 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Style visibility tree update test</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + /** + * Hide parent while child stays visible. + */ + function test1(aContainerID, aParentID, aChildID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode(aParentID)), + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ + { SECTION: [ + { SECTION: [ + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aParentID).style.visibility = "hidden"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ + { SECTION: [ + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "hide parent while child stays visible"; + }; + } + + /** + * Hide grand parent while its children stay visible. + */ + function test2(aContainerID, aGrandParentID, aChildID, aChild2ID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode(aGrandParentID)), + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_SHOW, getNode(aChild2ID)), + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ // container + { SECTION: [ // grand parent + { SECTION: [ + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + { SECTION: [ // child2 + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aGrandParentID).style.visibility = "hidden"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ // container + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + { SECTION: [ // child2 + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "hide grand parent while its children stay visible"; + }; + } + + /** + * Change container style, hide parents while their children stay visible. + */ + function test3(aContainerID, aParentID, aParent2ID, aChildID, aChild2ID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode(aParentID)), + new invokerChecker(EVENT_HIDE, getNode(aParent2ID)), + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_SHOW, getNode(aChild2ID)), + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ // container + { SECTION: [ // parent + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + ] }, + { SECTION: [ // parent2 + { SECTION: [ // child2 + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aContainerID).style.color = "red"; + getNode(aParentID).style.visibility = "hidden"; + getNode(aParent2ID).style.visibility = "hidden"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ // container + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + { SECTION: [ // child2 + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "change container style, hide parents while their children stay visible"; + }; + } + + /** + * Change container style and make visible child inside the table. + */ + function test4(aContainerID, aChildID) { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_REORDER, getNode(aChildID).parentNode), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ + { TABLE: [ + { ROW: [ + { CELL: [ ] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aContainerID).style.color = "red"; + getNode(aChildID).style.visibility = "visible"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ + { TABLE: [ + { ROW: [ + { CELL: [ + { SECTION: [ + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "change container style, make visible child insdie the table"; + }; + } + + /** + * Hide subcontainer while child inside the table stays visible. + */ + function test5(aContainerID, aSubContainerID, aChildID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode(aSubContainerID)), + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ // container + { SECTION: [ // subcontainer + { TABLE: [ + { ROW: [ + { CELL: [ + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + ] }, + ] }, + ] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + + getNode(aSubContainerID).style.visibility = "hidden"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ // container + { SECTION: [ // child + { TEXT_LEAF: [] }, + ] }, + ] }; + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "hide subcontainer while child inside the table stays visible"; + }; + } + + /** + * Hide subcontainer while its child and child inside the nested table stays visible. + */ + function test6(aContainerID, aSubContainerID, aChildID, aChild2ID) { + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, getNode(aSubContainerID)), + new invokerChecker(EVENT_SHOW, getNode(aChildID)), + new invokerChecker(EVENT_SHOW, getNode(aChild2ID)), + new invokerChecker(EVENT_REORDER, getNode(aContainerID)), + ]; + + this.invoke = function invoke() { + var tree = + { SECTION: [ // container + { SECTION: [ // subcontainer + { TABLE: [ + { ROW: [ + { CELL: [ + { TABLE: [ // nested table + { ROW: [ + { CELL: [ + { SECTION: [ // child + { TEXT_LEAF: [] } ]} ]} ]} ]} ]} ]} ]}, + { SECTION: [ // child2 + { TEXT_LEAF: [] } ]} ]} ]}; + + testAccessibleTree(aContainerID, tree); + + // invoke + getNode(aSubContainerID).style.visibility = "hidden"; + }; + + this.finalCheck = function finalCheck() { + var tree = + { SECTION: [ // container + { SECTION: [ // child + { TEXT_LEAF: [] } ]}, + { SECTION: [ // child2 + { TEXT_LEAF: [] } ]} ]}; + + testAccessibleTree(aContainerID, tree); + }; + + this.getID = function getID() { + return "hide subcontainer while its child and child inside the nested table stays visible"; + }; + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + gQueue = new eventQueue(); + + gQueue.push(new test1("t1_container", "t1_parent", "t1_child")); + gQueue.push(new test2("t2_container", "t2_grandparent", "t2_child", "t2_child2")); + gQueue.push(new test3("t3_container", "t3_parent", "t3_parent2", "t3_child", "t3_child2")); + gQueue.push(new test4("t4_container", "t4_child")); + gQueue.push(new test5("t5_container", "t5_subcontainer", "t5_child")); + gQueue.push(new test6("t6_container", "t6_subcontainer", "t6_child", "t6_child2")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Develop a way to handle visibility style" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=606125"> + Mozilla Bug 606125 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- hide parent while child stays visible --> + <div id="t1_container"> + <div id="t1_parent"> + <div id="t1_child" style="visibility: visible">text</div> + </div> + </div> + + <!-- hide grandparent while its children stay visible --> + <div id="t2_container"> + <div id="t2_grandparent"> + <div id="t2_parent"> + <div id="t2_child" style="visibility: visible">text</div> + <div id="t2_child2" style="visibility: visible">text</div> + </div> + </div> + </div> + + <!-- change container style, hide parents while their children stay visible --> + <div id="t3_container"> + <div id="t3_parent"> + <div id="t3_child" style="visibility: visible">text</div> + </div> + <div id="t3_parent2"> + <div id="t3_child2" style="visibility: visible">text</div> + </div> + </div> + + <!-- change container style, show child inside the table --> + <div id="t4_container"> + <table> + <tr> + <td> + <div id="t4_child" style="visibility: hidden;">text</div> + </td> + </tr> + </table> + </div> + + <!-- hide subcontainer while child inside the table stays visible --> + <div id="t5_container"> + <div id="t5_subcontainer"> + <table> + <tr> + <td> + <div id="t5_child" style="visibility: visible;">text</div> + </td> + </tr> + </table> + </div> + </div> + + <!-- hide subcontainer while its child and child inside the nested table stays visible --> + <div id="t6_container"> + <div id="t6_subcontainer"> + <table> + <tr> + <td> + <table> + <tr> + <td> + <div id="t6_child" style="visibility: visible;">text</div> + </td> + </tr> + </table> + </td> + </tr> + </table> + <div id="t6_child2" style="visibility: visible">text</div> + </div> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_whitespace.html b/accessible/tests/mochitest/treeupdate/test_whitespace.html new file mode 100644 index 0000000000..ebb199cfbe --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_whitespace.html @@ -0,0 +1,200 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Whitespace text accessible creation/destruction</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + // ////////////////////////////////////////////////////////////////////////// + // Invokers + + /** + * Middle image accessible removal results in text accessible removal. + * + * Before: + * DOM: whitespace img1 whitespace img2 whitespace img3 whitespace, + * a11y: img1 whitespace img2 whitespace img3 + * After: + * DOM: whitespace img1 whitespace whitespace img3 whitespace, + * a11y: img1 whitespace img3 + */ + function removeImg() { + this.containerNode = getNode("container1"); + this.imgNode = getNode("img1"); + this.img = getAccessible(this.imgNode); + this.text = this.img.nextSibling; + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.img), + new invokerChecker(EVENT_HIDE, this.text), + new invokerChecker(EVENT_REORDER, this.containerNode), + ]; + + this.finalCheck = function textLeafUpdate_finalCheck() { + var tree = + { SECTION: [ + { GRAPHIC: [] }, + { TEXT_LEAF: [] }, + { GRAPHIC: [] }, + ] }; + + testAccessibleTree(this.containerNode, tree); + }; + + this.invoke = function setOnClickAttr_invoke() { + var tree = + { SECTION: [ + { GRAPHIC: [] }, + { TEXT_LEAF: [] }, + { GRAPHIC: [] }, + { TEXT_LEAF: [] }, + { GRAPHIC: [] }, + ] }; + + testAccessibleTree(this.containerNode, tree); + + this.containerNode.removeChild(this.imgNode); + }; + + this.getID = function setOnClickAttr_getID() { + return "remove middle img"; + }; + } + + /** + * Append image making the whitespace visible and thus accessible. + * Note: images and whitespaces are on different leves of accessible trees, + * so that image container accessible update doesn't update the tree + * of whitespace container. + * + * Before: + * DOM: whitespace emptylink whitespace linkwithimg whitespace + * a11y: emptylink linkwithimg + * After: + * DOM: whitespace linkwithimg whitespace linkwithimg whitespace + * a11y: linkwithimg whitespace linkwithimg + */ + function insertImg() { + this.containerNode = getNode("container2"); + this.topNode = this.containerNode.parentNode; + this.textNode = this.containerNode.nextSibling; + this.imgNode = document.createElement("img"); + this.imgNode.setAttribute("src", "../moz.png"); + + this.eventSeq = [ + new asyncInvokerChecker(EVENT_SHOW, getAccessible, this.textNode), + new asyncInvokerChecker(EVENT_SHOW, getAccessible, this.imgNode), + new orderChecker(), + new invokerChecker(EVENT_REORDER, this.topNode), + ]; + + this.invoke = function insertImg_invoke() { + var tree = + { SECTION: [ + { LINK: [] }, + { LINK: [ + { GRAPHIC: [] }, + ] }, + ] }; + + testAccessibleTree(this.topNode, tree); + + this.containerNode.appendChild(this.imgNode); + }; + + this.finalCheck = function insertImg_finalCheck() { + var tree = + { SECTION: [ + { LINK: [ + { GRAPHIC: [ ] }, + ] }, + { TEXT_LEAF: [ ] }, + { LINK: [ + { GRAPHIC: [ ] }, + ] }, + ] }; + + testAccessibleTree(this.topNode, tree); + }; + + this.getID = function appendImg_getID() { + return "insert img into internal container"; + }; + } + + function dontCreateMapWhiteSpace() { + const tree = { SECTION: [ { role: ROLE_TEXT_LEAF, name: "x" } ] }; + testAccessibleTree("container3", tree); + + getNode("c3_inner").style.textAlign = "center"; + document.body.offsetTop; // Flush layout. + window.windowUtils.advanceTimeAndRefresh(100); + + testAccessibleTree("container3", tree); + window.windowUtils.restoreNormalRefresh(); + } + + // ////////////////////////////////////////////////////////////////////////// + // Test + + // gA11yEventDumpID = "eventdump"; // debug stuff + // gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() { + dontCreateMapWhiteSpace(); + + gQueue = new eventQueue(); + + gQueue.push(new removeImg()); + gQueue.push(new insertImg()); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Make sure accessible tree is correct when rendered text is changed" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=625652"> + Mozilla Bug 625652 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- Whitespace between the div and img tags will be inconsistent depending + on the image cache state and what optimizations layout was able to + apply. --> + <div id="container1"><img src="../moz.png"> <img id="img1" src="../moz.png"> <img src="../moz.png"></div> + <div><a id="container2"></a> <a><img src="../moz.png"></a></div> + + <div id="container3"> + <div id="c3_inner" role="presentation"> + x<map> </map> + </div> + </div> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeview.css b/accessible/tests/mochitest/treeview.css new file mode 100644 index 0000000000..462f560d6f --- /dev/null +++ b/accessible/tests/mochitest/treeview.css @@ -0,0 +1,15 @@ +treechildren::-moz-tree-checkbox(checked) { + list-style-image: url("chrome://global/skin/icons/check.svg"); +} + +treechildren::-moz-tree-image(cyclerState1) { + list-style-type: disc; +} + +treechildren::-moz-tree-image(cyclerState2) { + list-style-type: circle; +} + +treechildren::-moz-tree-image(cyclerState3) { + list-style-type: square; +} diff --git a/accessible/tests/mochitest/treeview.js b/accessible/tests/mochitest/treeview.js new file mode 100644 index 0000000000..5dc3b595d8 --- /dev/null +++ b/accessible/tests/mochitest/treeview.js @@ -0,0 +1,273 @@ +/* import-globals-from common.js */ +/* import-globals-from events.js */ + +/** + * Helper method to start a single XUL tree test. + */ +function loadXULTreeAndDoTest(aDoTestFunc, aTreeID, aTreeView) { + var doTestFunc = aDoTestFunc ? aDoTestFunc : gXULTreeLoadContext.doTestFunc; + var treeID = aTreeID ? aTreeID : gXULTreeLoadContext.treeID; + var treeView = aTreeView ? aTreeView : gXULTreeLoadContext.treeView; + + let treeNode = getNode(treeID); + + gXULTreeLoadContext.queue = new eventQueue(); + gXULTreeLoadContext.queue.push({ + treeNode, + + eventSeq: [new invokerChecker(EVENT_REORDER, treeNode)], + + invoke() { + this.treeNode.view = treeView; + }, + + getID() { + return "Load XUL tree " + prettyName(treeID); + }, + }); + gXULTreeLoadContext.queue.onFinish = function () { + SimpleTest.executeSoon(doTestFunc); + return DO_NOT_FINISH_TEST; + }; + gXULTreeLoadContext.queue.invoke(); +} + +/** + * Analogy of addA11yLoadEvent, nice helper to load XUL tree and start the test. + */ +function addA11yXULTreeLoadEvent(aDoTestFunc, aTreeID, aTreeView) { + gXULTreeLoadContext.doTestFunc = aDoTestFunc; + gXULTreeLoadContext.treeID = aTreeID; + gXULTreeLoadContext.treeView = aTreeView; + + addA11yLoadEvent(loadXULTreeAndDoTest); +} + +function nsTableTreeView(aRowCount) { + this.__proto__ = new nsTreeView(); + + for (var idx = 0; idx < aRowCount; idx++) { + this.mData.push(new treeItem("row" + String(idx) + "_")); + } +} + +function nsTreeTreeView() { + this.__proto__ = new nsTreeView(); + + this.mData = [ + new treeItem("row1"), + new treeItem("row2_", true, [ + new treeItem("row2.1_"), + new treeItem("row2.2_"), + ]), + new treeItem("row3_", false, [ + new treeItem("row3.1_"), + new treeItem("row3.2_"), + ]), + new treeItem("row4"), + ]; +} + +function nsTreeView() { + this.mTree = null; + this.mData = []; +} + +nsTreeView.prototype = { + // //////////////////////////////////////////////////////////////////////////// + // nsITreeView implementation + + get rowCount() { + return this.getRowCountIntl(this.mData); + }, + setTree: function setTree(aTree) { + this.mTree = aTree; + }, + getCellText: function getCellText(aRow, aCol) { + var data = this.getDataForIndex(aRow); + if (aCol.id in data.colsText) { + return data.colsText[aCol.id]; + } + + return data.text + aCol.id; + }, + getCellValue: function getCellValue(aRow, aCol) { + var data = this.getDataForIndex(aRow); + return data.value; + }, + getRowProperties: function getRowProperties(aIndex) { + return ""; + }, + getCellProperties: function getCellProperties(aIndex, aCol) { + if (!aCol.cycler) { + return ""; + } + + var data = this.getDataForIndex(aIndex); + return this.mCyclerStates[data.cyclerState]; + }, + getColumnProperties: function getColumnProperties(aCol) { + return ""; + }, + getParentIndex: function getParentIndex(aRowIndex) { + var info = this.getInfoByIndex(aRowIndex); + return info.parentIndex; + }, + hasNextSibling: function hasNextSibling(aRowIndex, aAfterIndex) {}, + getLevel: function getLevel(aIndex) { + var info = this.getInfoByIndex(aIndex); + return info.level; + }, + getImageSrc: function getImageSrc(aRow, aCol) {}, + isContainer: function isContainer(aIndex) { + var data = this.getDataForIndex(aIndex); + return data.open != undefined; + }, + isContainerOpen: function isContainerOpen(aIndex) { + var data = this.getDataForIndex(aIndex); + return data.open; + }, + isContainerEmpty: function isContainerEmpty(aIndex) { + var data = this.getDataForIndex(aIndex); + return data.open == undefined; + }, + isSeparator: function isSeparator(aIndex) {}, + isSorted: function isSorted() {}, + toggleOpenState: function toggleOpenState(aIndex) { + var data = this.getDataForIndex(aIndex); + + data.open = !data.open; + var rowCount = this.getRowCountIntl(data.children); + + if (data.open) { + this.mTree.rowCountChanged(aIndex + 1, rowCount); + } else { + this.mTree.rowCountChanged(aIndex + 1, -rowCount); + } + }, + selectionChanged: function selectionChanged() {}, + cycleHeader: function cycleHeader(aCol) {}, + cycleCell: function cycleCell(aRow, aCol) { + var data = this.getDataForIndex(aRow); + data.cyclerState = (data.cyclerState + 1) % 3; + + this.mTree.invalidateCell(aRow, aCol); + }, + isEditable: function isEditable(aRow, aCol) { + return true; + }, + setCellText: function setCellText(aRow, aCol, aValue) { + var data = this.getDataForIndex(aRow); + data.colsText[aCol.id] = aValue; + }, + setCellValue: function setCellValue(aRow, aCol, aValue) { + var data = this.getDataForIndex(aRow); + data.value = aValue; + + this.mTree.invalidateCell(aRow, aCol); + }, + + // //////////////////////////////////////////////////////////////////////////// + // public implementation + + appendItem: function appendItem(aText) { + this.mData.push(new treeItem(aText)); + }, + + // //////////////////////////////////////////////////////////////////////////// + // private implementation + + getDataForIndex: function getDataForIndex(aRowIndex) { + return this.getInfoByIndex(aRowIndex).data; + }, + + getInfoByIndex: function getInfoByIndex(aRowIndex) { + var info = { + data: null, + parentIndex: -1, + level: 0, + index: -1, + }; + + this.getInfoByIndexIntl(aRowIndex, info, this.mData, 0); + return info; + }, + + getRowCountIntl: function getRowCountIntl(aChildren) { + var rowCount = 0; + for (var childIdx = 0; childIdx < aChildren.length; childIdx++) { + rowCount++; + + var data = aChildren[childIdx]; + if (data.open) { + rowCount += this.getRowCountIntl(data.children); + } + } + + return rowCount; + }, + + getInfoByIndexIntl: function getInfoByIndexIntl( + aRowIdx, + aInfo, + aChildren, + aLevel + ) { + var rowIdx = aRowIdx; + for (var childIdx = 0; childIdx < aChildren.length; childIdx++) { + var data = aChildren[childIdx]; + + aInfo.index++; + + if (rowIdx == 0) { + aInfo.data = data; + aInfo.level = aLevel; + return -1; + } + + if (data.open) { + var parentIdx = aInfo.index; + rowIdx = this.getInfoByIndexIntl( + rowIdx - 1, + aInfo, + data.children, + aLevel + 1 + ); + + if (rowIdx == -1) { + if (aInfo.parentIndex == -1) { + aInfo.parentIndex = parentIdx; + } + return 0; + } + } else { + rowIdx--; + } + } + + return rowIdx; + }, + + mCyclerStates: ["cyclerState1", "cyclerState2", "cyclerState3"], +}; + +function treeItem(aText, aOpen, aChildren) { + this.text = aText; + this.colsText = {}; + this.open = aOpen; + this.value = "true"; + this.cyclerState = 0; + if (aChildren) { + this.children = aChildren; + } +} + +/** + * Used in conjunction with loadXULTreeAndDoTest and addA11yXULTreeLoadEvent. + */ +var gXULTreeLoadContext = { + doTestFunc: null, + treeID: null, + treeView: null, + queue: null, +}; diff --git a/accessible/tests/mochitest/value.js b/accessible/tests/mochitest/value.js new file mode 100644 index 0000000000..379403ecaf --- /dev/null +++ b/accessible/tests/mochitest/value.js @@ -0,0 +1,52 @@ +/* import-globals-from common.js */ + +// ////////////////////////////////////////////////////////////////////////////// +// Public methods + +/** + * Tests nsIAccessibleValue interface. + * + * @param aAccOrElmOrId [in] identifier of accessible + * @param aValue [in] accessible value (nsIAccessible::value) + * @param aCurrValue [in] current value (nsIAccessibleValue::currentValue) + * @param aMinValue [in] minimum value (nsIAccessibleValue::minimumValue) + * @param aMaxValue [in] maximumn value (nsIAccessibleValue::maximumValue) + * @param aMinIncr [in] minimum increment value + * (nsIAccessibleValue::minimumIncrement) + */ +function testValue( + aAccOrElmOrId, + aValue, + aCurrValue, + aMinValue, + aMaxValue, + aMinIncr +) { + var acc = getAccessible(aAccOrElmOrId, [nsIAccessibleValue]); + if (!acc) { + return; + } + + is(acc.value, aValue, "Wrong value of " + prettyName(aAccOrElmOrId)); + + is( + acc.currentValue, + aCurrValue, + "Wrong current value of " + prettyName(aAccOrElmOrId) + ); + is( + acc.minimumValue, + aMinValue, + "Wrong minimum value of " + prettyName(aAccOrElmOrId) + ); + is( + acc.maximumValue, + aMaxValue, + "Wrong maximum value of " + prettyName(aAccOrElmOrId) + ); + is( + acc.minimumIncrement, + aMinIncr, + "Wrong minimum increment value of " + prettyName(aAccOrElmOrId) + ); +} diff --git a/accessible/tests/mochitest/value/a11y.ini b/accessible/tests/mochitest/value/a11y.ini new file mode 100644 index 0000000000..8bd39474a8 --- /dev/null +++ b/accessible/tests/mochitest/value/a11y.ini @@ -0,0 +1,11 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_ariavalue.html] +[test_datetime.html] +[test_general.html] +[test_number.html] +[test_progress.html] +[test_range.html] +[test_meter.html] diff --git a/accessible/tests/mochitest/value/test_ariavalue.html b/accessible/tests/mochitest/value/test_ariavalue.html new file mode 100644 index 0000000000..bdb62a866d --- /dev/null +++ b/accessible/tests/mochitest/value/test_ariavalue.html @@ -0,0 +1,85 @@ +<html> + +<head> + <title>nsIAccessible value testing for implicit aria-value* attributes</title> + + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" src="../common.js"></script> + <script type="application/javascript" src="../value.js"></script> + + <script src="chrome://mochikit/content/chrome-harness.js"></script> + + <script type="application/javascript"> + function doTest() { + for (const role of ["slider", "scrollbar"]) { + testValue(`${role}_default`, "50", 50, 0, 100, 0); + testValue(`${role}_min1max50`, "25.5", 25.5, 1, 50, 0); + testValue(`${role}_max200`, "100", 100, 0, 200, 0); + testValue(`${role}_min10`, "55", 55, 10, 100, 0); + testValue(`${role}_vt`, "juice", 50, 0, 100, 0); + testValue(`${role}_vn`, "6", 6, 0, 100, 0); + testValue(`${role}_vtvn`, "juice", 6, 0, 100, 0); + } + + testValue("spinbutton_default", "", 0, 0, 0, 0); + testValue("spinbutton_min1max50", "", 0, 1, 50, 0); + testValue("spinbutton_max200", "", 0, 0, 200, 0); + testValue("spinbutton_min10", "", 0, 10, 0, 0); + testValue("spinbutton_vt", "juice", 0, 0, 0, 0); + testValue("spinbutton_vn", "6", 6, 0, 0, 0); + testValue("spinbutton_vtvn", "juice", 6, 0, 0, 0); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1357071" + title="Add support for implicit values for aria-value* attributes for scrollbar and slider roles"> + Bug 1357071 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + + <!-- ARIA sliders --> + <div id="slider_default" role="slider">vanilla slider</div> + <div id="slider_min1max50" role="slider" aria-valuemin="1" aria-valuemax="50">banana slider</div> + <div id="slider_max200" role="slider" aria-valuemax="200">cherry slider</div> + <div id="slider_min10" role="slider" aria-valuemin="10">strawberry slider</div> + <div id="slider_vt" role="slider" aria-valuetext="juice">orange slider</div> + <div id="slider_vn" role="slider" aria-valuenow="6">chocolate slider</div> + <div id="slider_vtvn" role="slider" aria-valuetext="juice" aria-valuenow="6">apple slider</div> + + <!-- ARIA scrollbars --> + <div id="scrollbar_default" role="scrollbar">vanilla scrollbar</div> + <div id="scrollbar_min1max50" role="scrollbar" aria-valuemin="1" aria-valuemax="50">banana scrollbar</div> + <div id="scrollbar_max200" role="scrollbar" aria-valuemax="200">cherry scrollbar</div> + <div id="scrollbar_min10" role="scrollbar" aria-valuemin="10">strawberry scrollbar</div> + <div id="scrollbar_vt" role="scrollbar" aria-valuetext="juice">orange scrollbar</div> + <div id="scrollbar_vn" role="scrollbar" aria-valuenow="6">chocolate scrollbar</div> + <div id="scrollbar_vtvn" role="scrollbar" aria-valuetext="juice" aria-valuenow="6">apple scrollbar</div> + + <!-- ARIA spinbuttons --> + <div id="spinbutton_default" role="spinbutton">vanilla spinbutton</div> + <div id="spinbutton_min1max50" role="spinbutton" aria-valuemin="1" aria-valuemax="50">banana spinbutton</div> + <div id="spinbutton_max200" role="spinbutton" aria-valuemax="200">cherry spinbutton</div> + <div id="spinbutton_min10" role="spinbutton" aria-valuemin="10">strawberry spinbutton</div> + <div id="spinbutton_vt" role="spinbutton" aria-valuetext="juice">orange spinbutton</div> + <div id="spinbutton_vn" role="spinbutton" aria-valuenow="6">chocolate spinbutton</div> + <div id="spinbutton_vtvn" role="spinbutton" aria-valuetext="juice" aria-valuenow="6">apple spinbutton</div> +</body> + +</html> diff --git a/accessible/tests/mochitest/value/test_datetime.html b/accessible/tests/mochitest/value/test_datetime.html new file mode 100644 index 0000000000..e03356ac1a --- /dev/null +++ b/accessible/tests/mochitest/value/test_datetime.html @@ -0,0 +1,76 @@ +<!doctype html> +<html> +<head> + <title>nsIAccessible value testing for datetime-local input element</title> + <link rel="stylesheet" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script src="../common.js"></script> + <script src="../promisified-events.js"></script> + + <script> + async function doTest() { + const dateTimeNode = getNode("datetime"); + const dateTime = getAccessible(dateTimeNode); + // We assume en-US for testing. However, the OS date format might + // override the en-US date format. + const monthFirst = dateTime.getChildAt(0).name == "Month"; + const month = dateTime.getChildAt(monthFirst ? 0 : 2); + const day = dateTime.getChildAt(monthFirst ? 2 : 0); + const year = dateTime.getChildAt(4); + const hour = dateTime.getChildAt(6); + const minute = dateTime.getChildAt(8); + const amPm = dateTime.getChildAt(10); + + // We don't use testValue() because it also checks numeric value, but + // we don't support numeric value here because it isn't useful. + function assertIsClear() { + is(year.value, ""); + is(month.value, ""); + is(day.value, ""); + is(hour.value, ""); + is(minute.value, ""); + // Unlike the numeric fields, amPm is a textbox. Since textboxes take + // their a11y value from their text content and "--" is set as the text + // content, the a11y value is "--". + is(amPm.value, "--"); + } + + info("Checking that input is initially clear"); + assertIsClear(); + + // The container doesn't notify of value changes, so we wait for a value + // change on one of the fields to know when it's updated. + info("Setting value"); + let changed = waitForEvent(EVENT_TEXT_VALUE_CHANGE, month); + dateTimeNode.value = "2000-01-02T03:04"; + await changed; + is(year.value, "2000"); + is(month.value, "01"); + is(day.value, "02"); + is(hour.value, "03"); + is(minute.value, "04"); + // Again, the OS date format might override, so we might get "am" instead + // of "AM". + is(amPm.value.toLowerCase(), "am"); + + info("Clearing value"); + changed = waitForEvent(EVENT_TEXT_VALUE_CHANGE, month); + dateTimeNode.value = ""; + await changed; + assertIsClear(); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> + +<body> + <input type="datetime-local" id="datetime"> +</body> +</html> diff --git a/accessible/tests/mochitest/value/test_general.html b/accessible/tests/mochitest/value/test_general.html new file mode 100644 index 0000000000..862c5254c0 --- /dev/null +++ b/accessible/tests/mochitest/value/test_general.html @@ -0,0 +1,159 @@ +<html> + +<head> + <title>nsIAccessible value testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style type="text/css"> + .offscreen { + position: absolute; + left: -5000px; + top: -5000px; + height: 100px; + width: 100px; + } + </style> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + + <script src="chrome://mochikit/content/chrome-harness.js"></script> + + <script type="application/javascript"> + function doTest() { + function testValue(aID, aValue) { + var acc = getAccessible(aID); + if (!acc) + return; + is(acc.value, aValue, "Wrong value for " + aID + "!"); + } + + var href = getRootDirectory(window.location.href) + "foo"; + + // roles that can't live as HTMLLinkAccessibles + testValue("aria_menuitem_link", ""); + testValue("aria_button_link", ""); + testValue("aria_checkbox_link", ""); + testValue("aria_application_link", ""); + testValue("aria_main_link", ""); + testValue("aria_navigation_link", ""); + + // roles that can live as HTMLLinkAccessibles + testValue("aria_link_link", href); + + // //////////////////////////////////////////////////////////////////////// + // ARIA textboxes + + testValue("aria_textbox1", "helo"); + // Textbox containing list. + testValue("aria_textbox2", "1. test"); + + // //////////////////////////////////////////////////////////////////////// + // ARIA comboboxes + + // aria-activedescendant defines a current item the value is computed from + testValue("aria_combobox1", kDiscBulletText + "Zoom"); + + // aria-selected defines a selected item the value is computed from, + // list control is pointed by aria-owns relation. + testValue("aria_combobox2", kDiscBulletText + "Zoom"); + + // aria-selected defines a selected item the value is computed from, + // list control is a child of combobox. + testValue("aria_combobox3", kDiscBulletText + "2"); + + // //////////////////////////////////////////////////////////////////////// + // HTML controls + testValue("combobox1", "item1"); + testValue("combobox2", "item2"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=494807" + title="Do not expose a11y info specific to hyperlinks when role is overridden using ARIA"> + Bug 494807 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=819273" + title="ARIA combobox should have accessible value"> + Bug 819273 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=887250" + title="ARIA textbox role doesn't expose value"> + Bug 887250 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + + <a id="aria_menuitem_link" role="menuitem" href="foo">menuitem</a> + <a id="aria_button_link" role="button" href="foo">button</a> + <a id="aria_checkbox_link" role="checkbox" href="foo">checkbox</a> + + <!-- landmark links --> + <a id="aria_application_link" role="application" href="foo">app</a> + <a id="aria_main_link" role="main" href="foo">main</a> + <a id="aria_navigation_link" role="navigation" href="foo">nav</a> + + <!-- strange edge case: please don't do this in the wild --> + <a id="aria_link_link" role="link" href="foo">link</a> + + <div id="aria_textbox1" role="textbox">helo</div> + <div id="aria_textbox2" contenteditable role="textbox"> + <ol><li>test</li></ol> + </div> + + <div id="aria_combobox1" role="combobox" + aria-owns="aria_combobox1_owned_listbox" + aria-activedescendant="aria_combobox1_selected_option"> + </div> + <ul role="listbox" id="aria_combobox1_owned_listbox"> + <li role="option">Zebra</li> + <li role="option" id="aria_combobox1_selected_option">Zoom</li> + </ul> + + <div id="aria_combobox2" role="combobox" + aria-owns="aria_combobox2_owned_listbox"> + </div> + <ul role="listbox" id="aria_combobox2_owned_listbox"> + <li role="option">Zebra</li> + <li role="option" aria-selected="true">Zoom</li> + </ul> + + <div id="aria_combobox3" role="combobox"> + <div role="textbox"></div> + <ul role="listbox"> + <li role="option">1</li> + <li role="option" aria-selected="true">2</li> + <li role="option">3</li> + </ul> + </div> + + <select id="combobox1"> + <option id="cb1_item1">item1</option> + <option id="cb1_item2">item2</option> + </select> + <select id="combobox2"> + <option id="cb2_item1">item1</option> + <option id="cb2_item2" selected="true">item2</option> + </select> + +</body> +</html> diff --git a/accessible/tests/mochitest/value/test_meter.html b/accessible/tests/mochitest/value/test_meter.html new file mode 100644 index 0000000000..7ae1ed3543 --- /dev/null +++ b/accessible/tests/mochitest/value/test_meter.html @@ -0,0 +1,82 @@ +<html> + +<head> + <title>nsIAccessible value testing for meter element</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../value.js"></script> + + <script src="chrome://mochikit/content/chrome-harness.js"></script> + + <script type="application/javascript"> + function doTest() { + // HTML5 meter element tests + testValue("nothing", "0", 0, 0, 1, 0); + testValue("minOnly", "20", 20, 20, 20, 0); + testValue("maxOnly", "0", 0, 0, 20, 0); + testValue("valOnly", "1", 1, 0, 1, 0); + testValue("regular", "15", 15, 10, 30, 0); + testValue("noMin", "10", 10, 0, 100, 0); + testValue("noMax", "5", 5, 5, 5, 0); + testValue("noVal", "10", 10, 10, 20, 0); + testValue("invalidValue", "20", 20, 10, 20, 0); + testValue("invalidMax", "10", 10, 10, 10, 0); + testValue("invalidValueMax", "20", 20, 20, 20, 0); + + testValue("plainText", "Hello world", 0, 0, 1, 0); + testValue("regularText", "You scored 15 out of 30", 15, 10, 30, 0); + testValue("invalidText", "Something isnt right here", 20, 20, 20, 0); + + testValue("valueText", "value", 0, 0, 1, 0); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1460378" + title="HTML <meter> not spoken by screen readers"> + Mozilla Bug 559773 + </a><br /> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + + <meter id="nothing"></meter> + + <meter id="minOnly" min="20"></meter> + <meter id="maxOnly" max="20"></meter> + <meter id="valOnly" value="20"></meter> + + <meter id="regular" min="10" value="15" max="30"></meter> + + <meter id="noMin" value="10" max="100"></meter> + <meter id="noMax" min="5" value="10"></meter> + <meter id="noVal" min="10" max="20"></meter> + + <meter id="invalidValue" min="10" value="30" max="20"></meter> + <meter id="invalidMax" min="10" value="15" max="2"></meter> + <meter id="invalidValueMax" min="20" value="17" max="10"></meter> + + <meter id="plainText">Hello world</meter> + <meter id="regularText" min="10" value="15" max="30">You scored 15 out of 30</meter> + <meter id="invalidText" min="20" value="17" max="10">Something isnt right here</meter> + + <meter id="valueText" aria-valuetext="value">valuetext should take precedence over internal text</meter> +</body> +</html> diff --git a/accessible/tests/mochitest/value/test_number.html b/accessible/tests/mochitest/value/test_number.html new file mode 100644 index 0000000000..59024c3ef3 --- /dev/null +++ b/accessible/tests/mochitest/value/test_number.html @@ -0,0 +1,56 @@ +<html> + +<head> + <title>nsIAccessible value testing for input@type=range element</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../value.js"></script> + + <script src="chrome://mochikit/content/chrome-harness.js"></script> + + <script type="application/javascript"> + function doTest() { + // HTML5 number element tests + testValue("number", "", 0, 0, 0, 1); + testValue("number_value", "1", 1, 0, 0, 1); + testValue("number_step", "", 0, 0, 0, 1); + testValue("number_min42", "", 0, 42, 0, 1); + testValue("number_max42", "", 0, 0, 42, 1); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=559761" + title="make HTML5 input@type=number element accessible"> + Bug 559761 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + + <!-- HTML5 input@type=number element --> + <input type="number" id="number"> + <input type="number" id="number_value" value="1"> + <input type="number" id="number_step" step="1"> + <input type="number" id="number_min42" min="42"> + <input type="number" id="number_max42" max="42"> +</body> +</html> diff --git a/accessible/tests/mochitest/value/test_progress.html b/accessible/tests/mochitest/value/test_progress.html new file mode 100644 index 0000000000..03ddb02196 --- /dev/null +++ b/accessible/tests/mochitest/value/test_progress.html @@ -0,0 +1,61 @@ +<html> + +<head> + <title>nsIAccessible value testing for progress element</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../value.js"></script> + + <script src="chrome://mochikit/content/chrome-harness.js"></script> + + <script type="application/javascript"> + function doTest() { + // HTML5 progress element tests + testValue("pr_indeterminate", "", 0, 0, 1, 0); + testValue("pr_zero", "0%", 0, 0, 1, 0); + testValue("pr_zeropointfive", "50%", 0.5, 0, 1, 0); + testValue("pr_one", "100%", 1, 0, 1, 0); + testValue("pr_42", "100%", 42, 0, 1, 0); + testValue("pr_21", "50%", 21, 0, 42, 0); + testValue("pr_valuetext", "value", 0, 0, 1, 0); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=559773" + title="make HTML5 progress element accessible"> + Mozilla Bug 559773 + </a><br /> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + + <!-- HTML5 progress element --> + <progress id="pr_indeterminate">this will be read by legacy browsers</progress> + <progress id="pr_zero" value="0">this will be read by legacy browsers</progress> + <progress id="pr_zeropointfive" value="0.5">this will be read by legacy browsers</progress> + <progress id="pr_one" value="1">this will be read by legacy browsers</progress> + <progress id="pr_42" value="42">this will be read by legacy browsers</progress> + <progress id="pr_21" value="21" max="42">this will be read by legacy browsers</progress> + <!-- aria-valuetext should work due to implicit progressbar role (bug 1475376) --> + <progress id="pr_valuetext" aria-valuetext="value">this will be read by legacy browsers</progress> +</body> +</html> diff --git a/accessible/tests/mochitest/value/test_range.html b/accessible/tests/mochitest/value/test_range.html new file mode 100644 index 0000000000..55cb0b1767 --- /dev/null +++ b/accessible/tests/mochitest/value/test_range.html @@ -0,0 +1,59 @@ +<html> + +<head> + <title>nsIAccessible value testing for input@type=range element</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../value.js"></script> + + <script src="chrome://mochikit/content/chrome-harness.js"></script> + + <script type="application/javascript"> + function doTest() { + // HTML5 progress element tests + testValue("range", "50", 50, 0, 100, 1); + testValue("range_value", "1", 1, 0, 100, 1); + testValue("range_step", "50", 50, 0, 100, 1); + testValue("range_min42", "71", 71, 42, 100, 1); + testValue("range_max42", "21", 21, 0, 42, 1); + testValue("range_valuetext", "value", 50, 0, 100, 1); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=559764" + title="make HTML5 input@type=range element accessible"> + Bug 559764 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + + <!-- HTML5 input@type=range element --> + <input type="range" id="range"> + <input type="range" id="range_value" value="1"> + <input type="range" id="range_step" step="1"> + <input type="range" id="range_min42" min="42"> + <input type="range" id="range_max42" max="42"> + <!-- aria-valuetext should work due to implicit slider role (bug 1475376) --> + <input type="range" id="range_valuetext" aria-valuetext="value"> +</body> +</html> |