diff --git a/.eslintignore b/.eslintignore index f3a5e69bf6d3..611e24c8da00 100644 --- a/.eslintignore +++ b/.eslintignore @@ -7,7 +7,6 @@ obj*/** # We ignore all these directories by default, until we get them enabled. # If you are enabling a directory, please add directory specific exclusions # below. -accessible/** addon-sdk/** build/** caps/** diff --git a/accessible/.eslintrc b/accessible/.eslintrc new file mode 100644 index 000000000000..102708114c32 --- /dev/null +++ b/accessible/.eslintrc @@ -0,0 +1,15 @@ +{ + "extends": [ + "../.eslintrc" + ], + "globals": { + "Cc": true, + "Ci": true, + "Components": true, + "console": true, + "Cu": true, + "dump": true, + "Services": true, + "XPCOMUtils": true + } +} diff --git a/accessible/jsat/OutputGenerator.jsm b/accessible/jsat/OutputGenerator.jsm index a69e21ad1143..2a936b572df2 100644 --- a/accessible/jsat/OutputGenerator.jsm +++ b/accessible/jsat/OutputGenerator.jsm @@ -276,10 +276,9 @@ var OutputGenerator = { _addMencloseNotations: function _addMencloseNotations(aOutput, aAccessible) { let notations = Utils.getAttributes(aAccessible).notation || 'longdiv'; aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift'].apply( - aOutput, [for (notation of notations.split(' ')) - {string: this._getOutputName('notation-' + notation)} - ] - ); + aOutput, notations.split(' ').map(notation => { + return { string: this._getOutputName('notation-' + notation) }; + })); }, /** diff --git a/accessible/moz.build b/accessible/moz.build index 3e8485aff6c5..91f44776ad4a 100644 --- a/accessible/moz.build +++ b/accessible/moz.build @@ -21,3 +21,5 @@ if CONFIG['MOZ_XUL']: DIRS += ['xul'] TEST_DIRS += ['tests/mochitest'] + +BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini'] diff --git a/accessible/tests/browser/.eslintrc b/accessible/tests/browser/.eslintrc new file mode 100644 index 000000000000..e25a5b2c8375 --- /dev/null +++ b/accessible/tests/browser/.eslintrc @@ -0,0 +1,202 @@ +{ + "extends": [ + "../../../testing/mochitest/browser.eslintrc" + ], + // All globals made available in the test environment. + "globals": { + // Content scripts have global 'content' object + "content": true, + + // Defined in accessible/tests/mochitest/ common.js, name.js, states.js + "prettyName": true, + "statesToString": true, + "eventTypeToString": true, + "testName": true, + "testStates": true, + "testAccessibleTree": true, + "isAccessible": true, + "getAccessibleDOMNodeID": true, + + // Defined for all accessibility browser tests. + "addAccessibleTask": true, + "BrowserTestUtils": true, + "ContentTask": true, + "gBrowser": true, + "isDefunct": true, + "loadScripts": true, + "Logger": true, + "MOCHITESTS_DIR": true, + "waitForEvent": true, + "waitForMultipleEvents": true, + "invokeSetAttribute": true, + "invokeSetStyle": true, + "invokeFocus": true, + "findAccessibleChildByID": true + }, + "rules": { + "mozilla/mark-test-function-used": 1, + "mozilla/no-aArgs": 1, + "mozilla/no-cpows-in-tests": 1, + "mozilla/reject-importGlobalProperties": 1, + "mozilla/var-only-at-top-level": 1, + + "block-scoped-var": 2, + "brace-style": [2, "1tbs"], + "camelcase": 2, + "comma-dangle": [2, "never"], + "comma-spacing": 2, + "comma-style": [2, "last"], + "complexity": [2, 35], + "consistent-this": 0, + "curly": [2, "multi-line"], + "default-case": 0, + "dot-location": [2, "property"], + "dot-notation": 2, + "eol-last": 2, + "eqeqeq": 0, + "func-names": 0, + "func-style": 0, + "generator-star": 0, + "global-strict": 0, + "handle-callback-err": [2, "er"], + "indent": [2, 2, {"SwitchCase": 1}], + "key-spacing": [2, {"beforeColon": false, "afterColon": true}], + "linebreak-style": 0, + "max-depth": 0, + "max-nested-callbacks": [2, 3], + "max-params": 0, + "max-statements": 0, + "new-cap": [2, {"capIsNew": false}], + "new-parens": 2, + "no-array-constructor": 2, + "no-bitwise": 0, + "no-caller": 2, + "no-catch-shadow": 2, + "no-comma-dangle": 0, + "no-cond-assign": 2, + "no-console": 0, + "no-constant-condition": 0, + "no-continue": 0, + "no-control-regex": 2, + "no-debugger": 2, + "no-delete-var": 2, + "no-div-regex": 0, + "no-dupe-args": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-else-return": 2, + "no-empty": 2, + "no-empty-character-class": 2, + "no-eval": 2, + "no-ex-assign": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-boolean-cast": 2, + "no-extra-parens": 0, + "no-extra-semi": 2, + "no-extra-strict": 0, + "no-fallthrough": 2, + "no-floating-decimal": 0, + "no-inline-comments": 0, + "no-lonely-if": 2, + "no-mixed-requires": 0, + "no-mixed-spaces-and-tabs": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [2, {"max": 1}], + "no-native-reassign": 2, + "no-nested-ternary": 2, + "no-new-require": 0, + "no-octal": 2, + "no-param-reassign": 0, + "no-path-concat": 0, + "no-plusplus": 0, + "no-process-env": 0, + "no-process-exit": 0, + "no-proto": 2, + "no-redeclare": 2, + "no-regex-spaces": 2, + "no-reserved-keys": 0, + "no-restricted-modules": 0, + "no-return-assign": 1, + "no-script-url": 0, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow": 1, + "no-shadow-restricted-names": 2, + "no-space-before-semi": 0, + "no-spaced-func": 2, + "no-sparse-arrays": 2, + "no-sync": 0, + "no-ternary": 0, + "no-throw-literal": 2, + "no-trailing-spaces": 2, + "no-undef": 2, + "no-underscore-dangle": 0, + "no-undefined": 0, + "no-unneeded-ternary": 2, + "no-unreachable": 2, + "no-unused-vars": [2, {"vars": "all", "args": "none"}], + "no-use-before-define": 0, + "no-var": 0, + "no-warning-comments": 0, + "no-with": 2, + "object-shorthand": 0, + "one-var": [2, "never"], + "padded-blocks": [2, "never"], + "quote-props": 0, + "radix": 2, + "semi": [2, "always"], + "semi-spacing": [2, {"before": false, "after": true}], + "sort-vars": 0, + "space-after-function-name": 0, + "keyword-spacing": 2, + "space-before-blocks": 2, + "space-before-function-parentheses": 0, + "space-before-function-paren": [2, "never"], + "space-in-brackets": 0, + "space-in-parens": [2, "never"], + "space-infix-ops": [2, {"int32Hint": true}], + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "space-unary-word-ops": 0, + "spaced-comment": [2, "always"], + "strict": [2, "global"], + "use-isnan": 2, + "valid-jsdoc": 0, + "valid-typeof": 2, + "vars-on-top": 0, + "wrap-iife": 0, + "wrap-regex": 0, + "yoda": 2, + + "guard-for-in": 0, + "newline-after-var": 0, + "no-alert": 0, + "no-eq-null": 0, + "no-func-assign": 0, + "no-implied-eval": 0, + "no-inner-declarations": 0, + "no-invalid-regexp": 0, + "no-irregular-whitespace": 0, + "no-iterator": 0, + "no-label-var": 0, + "no-labels": 2, + "no-lone-blocks": 0, + "no-loop-func": 0, + "no-negated-in-lhs": 0, + "no-new": 0, + "no-new-func": 0, + "no-new-object": 0, + "no-new-wrappers": 0, + "no-obj-calls": 0, + "no-octal-escape": 0, + "no-undef-init": 2, + "no-unexpected-multiline": 2, + "object-curly-spacing": 0, + "no-unused-expressions": 0, + "no-void": 0, + "no-wrap-func": 0, + "operator-assignment": 0, + "operator-linebreak": [2, "after"], + } +} diff --git a/accessible/tests/browser/browser.ini b/accessible/tests/browser/browser.ini new file mode 100644 index 000000000000..3157061bbca9 --- /dev/null +++ b/accessible/tests/browser/browser.ini @@ -0,0 +1,50 @@ +[DEFAULT] +support-files = + events.js + head.js + doc_treeupdate_ariadialog.html + doc_treeupdate_ariaowns.html + doc_treeupdate_imagemap.html + doc_treeupdate_removal.xhtml + doc_treeupdate_visibility.html + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + !/accessible/tests/mochitest/moz.png + +# Caching tests +[browser_caching_name.js] +skip-if = e10s + +# Events tests +[browser_events_caretmove.js] +[browser_events_hide.js] +skip-if = e10s +[browser_events_show.js] +skip-if = e10s +[browser_events_statechange.js] +[browser_events_textchange.js] +skip-if = e10s + +# Tree update tests +[browser_treeupdate_ariadialog.js] +skip-if = e10s +[browser_treeupdate_ariaowns.js] +skip-if = e10s +[browser_treeupdate_canvas.js] +skip-if = e10s +[browser_treeupdate_cssoverflow.js] +[browser_treeupdate_doc.js] +skip-if = e10s +[browser_treeupdate_gencontent.js] +[browser_treeupdate_hidden.js] +[browser_treeupdate_imagemap.js] +skip-if = e10s +[browser_treeupdate_list.js] +[browser_treeupdate_list_editabledoc.js] +[browser_treeupdate_listener.js] +[browser_treeupdate_optgroup.js] +[browser_treeupdate_removal.js] +[browser_treeupdate_table.js] +[browser_treeupdate_textleaf.js] +[browser_treeupdate_visibility.js] +[browser_treeupdate_whitespace.js] diff --git a/accessible/tests/browser/browser_caching_name.js b/accessible/tests/browser/browser_caching_name.js new file mode 100644 index 000000000000..08e635014408 --- /dev/null +++ b/accessible/tests/browser/browser_caching_name.js @@ -0,0 +1,434 @@ +/* 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'; + +/* global EVENT_REORDER, EVENT_TEXT_INSERTED */ + +loadScripts({ name: 'name.js', dir: MOCHITESTS_DIR }); + +/** + * Rules for name tests that are inspired by + * accessible/tests/mochitest/name/markuprules.xul + * + * Each element in the list of rules represents a name calculation rule for a + * particular test case. + * + * The rules have the following format: + * { attr } - calculated from attribute + * { elm } - calculated from another element + * { fromsubtree } - calculated from element's subtree + * + * + * Options include: + * * recreated - subrtee is recreated and the test should only continue + * after a reorder event + * * textchanged - text is inserted into a subtree and the test should only + * continue after a text inserted event + */ +const ARIARule = [{ attr: 'aria-labelledby' }, { attr: 'aria-label' }]; +const HTMLControlHeadRule = [...ARIARule, { elm: 'label', isSibling: true }]; +const rules = { + CSSContent: [{ elm: 'style', isSibling: true }, { fromsubtree: true }], + HTMLARIAGridCell: [...ARIARule, { fromsubtree: true }, { attr: 'title' }], + HTMLControl: [...HTMLControlHeadRule, { fromsubtree: true }, + { attr: 'title' }], + HTMLElm: [...ARIARule, { attr: 'title' }], + HTMLImg: [...ARIARule, { attr: 'alt', recreated: true }, { attr: 'title' }], + HTMLImgEmptyAlt: [...ARIARule, { attr: 'title' }, { attr: 'alt' }], + HTMLInputButton: [...HTMLControlHeadRule, { attr: 'value' }, + { attr: 'title' }], + HTMLInputImage: [...HTMLControlHeadRule, { attr: 'alt', recreated: true }, + { attr: 'value', recreated: true }, { attr: 'title' }], + HTMLInputImageNoValidSrc: [...HTMLControlHeadRule, + { attr: 'alt', recreated: true }, { attr: 'value', recreated: true }], + HTMLInputReset: [...HTMLControlHeadRule, + { attr: 'value', textchanged: true }], + HTMLInputSubmit: [...HTMLControlHeadRule, + { attr: 'value', textchanged: true }], + HTMLLink: [...ARIARule, { fromsubtree: true }, { attr: 'title' }], + HTMLLinkImage: [...ARIARule, { elm: 'img' }, { attr: 'title' }], + HTMLOption: [...ARIARule, { attr: 'label' }, { fromsubtree: true }, + { attr: 'title' }], + HTMLTable: [...ARIARule, { elm: 'caption' }, { attr: 'summary' }, + { attr: 'title' }] +}; + +const markupTests = [{ + id: 'btn', + ruleset: 'HTMLControl', + markup: ` + test2 + test3 + + `, + expected: ['test2 test3', 'test1', 'test4', 'press me', 'test5'] +}, { + id: 'btn', + ruleset: 'HTMLInputButton', + markup: ` + test2 + test3 + + `, + expected: ['test2 test3', 'test1', 'test4', 'name from value', + 'name from title'] +}, { + id: 'btn-submit', + ruleset: 'HTMLInputSubmit', + markup: ` + test2 + test3 + + `, + expected: ['test2 test3', 'test1', 'test4', 'name from value'] +}, { + id: 'btn-reset', + ruleset: 'HTMLInputReset', + markup: ` + test2 + test3 + + `, + expected: ['test2 test3', 'test1', 'test4', 'name from value'] +}, { + id: 'btn-image', + ruleset: 'HTMLInputImage', + markup: ` + test2 + test3 + + `, + expected: ['test2 test3', 'test1', 'test4', 'name from alt', + 'name from value', 'name from title'] +}, { + id: 'btn-image', + ruleset: 'HTMLInputImageNoValidSrc', + markup: ` + test2 + test3 + + `, + expected: ['test2 test3', 'test1', 'test4', 'name from alt', + 'name from value'] +}, { + id: 'opt', + ruleset: 'HTMLOption', + markup: ` + test2 + test3 + `, + expected: ['test2 test3', 'test1', 'test4', 'option1', 'test5'] +}, { + id: 'img', + ruleset: 'HTMLImg', + markup: ` + test2 + test3 + Mozilla logo`, + expected: ['test2 test3', 'Logo of Mozilla', 'Mozilla logo', 'This is a logo'] +}, { + id: 'imgemptyalt', + ruleset: 'HTMLImgEmptyAlt', + markup: ` + test2 + test3 + `, + expected: ['test2 test3', 'Logo of Mozilla', 'This is a logo', ''] +}, { + id: 'tc', + ruleset: 'HTMLElm', + markup: ` + test2 + test3 + + + + + +
+

This is a paragraph

+ This is a link +
    +
  • This is a list
  • +
+
`, + expected: ['test2 test3', 'test1', 'test5'] +}, { + id: 'gc', + ruleset: 'HTMLARIAGridCell', + markup: ` + test2 + test3 + + + + + +
+

This is a paragraph

+ This is a link +
    +
  • Listitem1
  • +
  • Listitem2
  • +
+
`, + expected: ['test2 test3', 'test1', 'This is a paragraph', + 'This is a paragraph This is a link This is a list'] +}, { + id: 't', + ruleset: 'HTMLTable', + markup: ` + lby_tst6_1 + lby_tst6_2 + + + + + + + +
caption_tst6
cell1cell2
`, + expected: ['lby_tst6_1 lby_tst6_2', 'arialabel_tst6', 'caption_tst6', + 'summary_tst6', 'title_tst6'] +}, { + id: 'btn', + ruleset: 'CSSContent', + markup: ` + + `, + expected: ['do not press me', 'press me'] +}, { + // TODO: uncomment when Bug-1256382 is resoved. + // id: 'li', + // ruleset: 'CSSContent', + // markup: ` + // + // `, + // expected: ['1. Listitem', `${String.fromCharCode(0x2022)} Listitem`] +// }, { + id: 'a', + ruleset: 'HTMLLink', + markup: ` + test2 + test3 + test5`, + expected: ['test2 test3', 'test1', 'test5', 'test4'] +}, { + id: 'a-img', + ruleset: 'HTMLLinkImage', + markup: ` + test2 + test3 + test5`, + expected: ['test2 test3', 'test1', 'test5', 'test4'] +}]; + +/** + * Wait for an accessible event to happen and, in case given accessible is + * defunct, update it to one that is attached to the accessible event. + * @param {Promise} onEvent accessible event promise + * @param {Object} target { acc, parent, id } structure that contains an + * accessible, its parent and its content element + * id. + */ +function* updateAccessibleIfNeeded(onEvent, target) { + let event = yield onEvent; + if (isDefunct(target.acc)) { + target.acc = findAccessibleChildByID(event.accessible, target.id); + } +} + +/** + * Test accessible name that is calculated from an attribute, remove the + * attribute before proceeding to the next name test. If attribute removal + * results in a reorder or text inserted event - wait for it. If accessible + * becomes defunct, update its reference using the one that is attached to one + * of the above events. + * @param {Object} browser current "tabbrowser" element + * @param {Object} target { acc, parent, id } structure that contains an + * accessible, its parent and its content element + * id. + * @param {Object} rule current attr rule for name calculation + * @param {[type]} expected expected name value + */ +function* testAttrRule(browser, target, rule, expected) { + testName(target.acc, expected); + let onEvent; + if (rule.recreated) { + onEvent = waitForEvent(EVENT_REORDER, target.parent); + } else if (rule.textchanged) { + onEvent = waitForEvent(EVENT_TEXT_INSERTED, target.id); + } + yield invokeSetAttribute(browser, target.id, rule.attr); + if (onEvent) { + yield updateAccessibleIfNeeded(onEvent, target); + } +} + +/** + * Test accessible name that is calculated from an element name, remove the + * element before proceeding to the next name test. If element removal results + * in a reorder event - wait for it. If accessible becomes defunct, update its + * reference using the one that is attached to a possible reorder event. + * @param {Object} browser current "tabbrowser" element + * @param {Object} target { acc, parent, id } structure that contains an + * accessible, its parent and its content element + * id. + * @param {Object} rule current elm rule for name calculation + * @param {[type]} expected expected name value + */ +function* testElmRule(browser, target, rule, expected) { + testName(target.acc, expected); + let onEvent = waitForEvent(EVENT_REORDER, rule.isSibling ? + target.parent : target.id); + yield ContentTask.spawn(browser, rule.elm, elm => + content.document.querySelector(`${elm}`).remove()); + yield updateAccessibleIfNeeded(onEvent, target); +} + +/** + * Test accessible name that is calculated from its subtree, remove the subtree + * and wait for a reorder event before proceeding to the next name test. If + * accessible becomes defunct, update its reference using the one that is + * attached to a reorder event. + * @param {Object} browser current "tabbrowser" element + * @param {Object} target { acc, parent, id } structure that contains an + * accessible, its parent and its content element + * id. + * @param {Object} rule current subtree rule for name calculation + * @param {[type]} expected expected name value + */ +function* testSubtreeRule(browser, target, rule, expected) { + testName(target.acc, expected); + let onEvent = waitForEvent(EVENT_REORDER, target.id); + yield ContentTask.spawn(browser, target.id, id => { + let elm = content.document.getElementById(id); + while (elm.firstChild) { + elm.removeChild(elm.firstChild); + } + }); + yield updateAccessibleIfNeeded(onEvent, target); +} + +/** + * Iterate over a list of rules and test accessible names for each one of the + * rules. + * @param {Object} browser current "tabbrowser" element + * @param {Object} target { acc, parent, id } structure that contains an + * accessible, its parent and its content element + * id. + * @param {Array} ruleset A list of rules to test a target with + * @param {Array} expected A list of expected name value for each rule + */ +function* testNameRule(browser, target, ruleset, expected) { + for (let i = 0; i < ruleset.length; ++i) { + let rule = ruleset[i]; + let testFn; + if (rule.attr) { + testFn = testAttrRule; + } else if (rule.elm) { + testFn = testElmRule; + } else if (rule.fromsubtree) { + testFn = testSubtreeRule; + } + yield testFn(browser, target, rule, expected[i]); + } +} + +markupTests.forEach(({ id, ruleset, markup, expected }) => + addAccessibleTask(markup, function*(browser, accDoc) { + // Find a target accessible from an accessible subtree. + let acc = findAccessibleChildByID(accDoc, id); + // Find target's parent accessible from an accessible subtree. + let parent = getAccessibleDOMNodeID(acc.parent); + let target = { id, parent, acc }; + yield testNameRule(browser, target, rules[ruleset], expected); + })); diff --git a/accessible/tests/browser/browser_events_caretmove.js b/accessible/tests/browser/browser_events_caretmove.js new file mode 100644 index 000000000000..506945f3036f --- /dev/null +++ b/accessible/tests/browser/browser_events_caretmove.js @@ -0,0 +1,21 @@ +/* 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/. */ + +/* global EVENT_TEXT_CARET_MOVED, nsIAccessibleCaretMoveEvent */ + +'use strict'; + +/** + * Test caret move event and its interface: + * - caretOffset + */ +addAccessibleTask('', function*(browser) { + let onCaretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, 'textbox'); + yield invokeFocus(browser, 'textbox'); + let event = yield onCaretMoved; + + let caretMovedEvent = event.QueryInterface(nsIAccessibleCaretMoveEvent); + is(caretMovedEvent.caretOffset, 5, + 'Correct caret offset.'); +}); diff --git a/accessible/tests/browser/browser_events_hide.js b/accessible/tests/browser/browser_events_hide.js new file mode 100644 index 000000000000..6e1d5b6917d2 --- /dev/null +++ b/accessible/tests/browser/browser_events_hide.js @@ -0,0 +1,32 @@ +/* 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/. */ + +/* global EVENT_HIDE */ + +'use strict'; + +/** + * Test hide event and its interface: + * - targetParent + * - targetNextSibling + * - targetPrevSibling + */ +addAccessibleTask(` +
+ +
+ +
`, function*(browser) { + let onHide = waitForEvent(EVENT_HIDE, 'div'); + yield invokeSetStyle(browser, 'div', 'visibility', 'hidden'); + let event = yield onHide; + let hideEvent = event.QueryInterface(Ci.nsIAccessibleHideEvent); + + is(getAccessibleDOMNodeID(hideEvent.targetParent), 'parent', + 'Correct target parent.'); + is(getAccessibleDOMNodeID(hideEvent.targetNextSibling), 'next', + 'Correct target next sibling.'); + is(getAccessibleDOMNodeID(hideEvent.targetPrevSibling), 'previous', + 'Correct target previous sibling.'); +}); diff --git a/accessible/tests/browser/browser_events_show.js b/accessible/tests/browser/browser_events_show.js new file mode 100644 index 000000000000..5003c14f7939 --- /dev/null +++ b/accessible/tests/browser/browser_events_show.js @@ -0,0 +1,17 @@ +/* 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/. */ + +/* global EVENT_SHOW */ + +'use strict'; + +/** + * Test show event + */ +addAccessibleTask('', + function*(browser) { + let onShow = waitForEvent(EVENT_SHOW, 'div'); + yield invokeSetStyle(browser, 'div', 'visibility', 'visible'); + yield onShow; + }); diff --git a/accessible/tests/browser/browser_events_statechange.js b/accessible/tests/browser/browser_events_statechange.js new file mode 100644 index 000000000000..cc290c4b3546 --- /dev/null +++ b/accessible/tests/browser/browser_events_statechange.js @@ -0,0 +1,60 @@ +/* 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/. */ + +/* global STATE_CHECKED, EXT_STATE_EDITABLE, nsIAccessibleStateChangeEvent, + EVENT_STATE_CHANGE */ + +'use strict'; + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }, + { name: 'states.js', dir: MOCHITESTS_DIR }); + +function checkStateChangeEvent(event, state, isExtraState, isEnabled) { + let scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent); + is(scEvent.state, state, 'Correct state of the statechange event.'); + is(scEvent.isExtraState, isExtraState, + 'Correct extra state bit of the statechange event.'); + is(scEvent.isEnabled, isEnabled, 'Correct state of statechange event state'); +} + +// Insert mock source into the iframe to be able to verify the right document +// body id. +let iframeSrc = `data:text/html, + + + + Inner Iframe + + + `; + +/** + * Test state change event and its interface: + * - state + * - isExtraState + * - isEnabled + */ +addAccessibleTask(` + + `, function*(browser) { + // Test state change + let onStateChange = waitForEvent(EVENT_STATE_CHANGE, 'checkbox'); + // Set checked for a checkbox. + yield ContentTask.spawn(browser, {}, () => + content.document.getElementById('checkbox').checked = true); + let event = yield onStateChange; + + checkStateChangeEvent(event, STATE_CHECKED, false, true); + testStates(event.accessible, STATE_CHECKED, 0); + + // Test extra state + onStateChange = waitForEvent(EVENT_STATE_CHANGE, 'iframe'); + // Set design mode on. + yield ContentTask.spawn(browser, {}, () => + content.document.getElementById('iframe').contentDocument.designMode = 'on'); + event = yield onStateChange; + + checkStateChangeEvent(event, EXT_STATE_EDITABLE, true, true); + testStates(event.accessible, 0, EXT_STATE_EDITABLE); +}); diff --git a/accessible/tests/browser/browser_events_textchange.js b/accessible/tests/browser/browser_events_textchange.js new file mode 100644 index 000000000000..f2bca47f2221 --- /dev/null +++ b/accessible/tests/browser/browser_events_textchange.js @@ -0,0 +1,72 @@ +/* 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/. */ + +/* global EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED, + nsIAccessibleTextChangeEvent */ + +'use strict'; + +function checkTextChangeEvent(event, id, text, start, end, isInserted, isFromUserInput) { + let tcEvent = event.QueryInterface(nsIAccessibleTextChangeEvent); + is(tcEvent.start, start, `Correct start offset for ${prettyName(id)}`); + is(tcEvent.length, end - start, `Correct length for ${prettyName(id)}`); + is(tcEvent.isInserted, isInserted, + `Correct isInserted flag for ${prettyName(id)}`); + is(tcEvent.modifiedText, text, `Correct text for ${prettyName(id)}`); + is(tcEvent.isFromUserInput, isFromUserInput, + `Correct value of isFromUserInput for ${prettyName(id)}`); +} + +function* changeText(browser, id, value, events) { + let onEvents = waitForMultipleEvents(events.map(({ isInserted }) => { + let eventType = isInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED; + return { id, eventType }; + })); + // Change text in the subtree. + yield ContentTask.spawn(browser, { id, value }, ({ id, value }) => + content.document.getElementById(id).firstChild.textContent = value); + let resolvedEvents = yield onEvents; + + events.forEach(({ isInserted, str, offset }, idx) => + checkTextChangeEvent(resolvedEvents[idx], + id, str, offset, offset + str.length, isInserted, false)); +} + +function* removeTextFromInput(browser, id, value, start, end) { + let onTextRemoved = waitForEvent(EVENT_TEXT_REMOVED, id); + // Select text and delete it. + yield ContentTask.spawn(browser, { id, start, end }, ({ id, start, end }) => { + let el = content.document.getElementById(id); + el.focus(); + el.setSelectionRange(start, end); + }); + yield BrowserTestUtils.sendChar('VK_DELETE', browser); + + let event = yield onTextRemoved; + checkTextChangeEvent(event, id, value, start, end, false, true); +} + +/** + * Test text change event and its interface: + * - start + * - length + * - isInserted + * - modifiedText + * - isFromUserInput + */ +addAccessibleTask(` +

abc

+ `, function*(browser) { + let events = [ + { isInserted: false, str: 'abc', offset: 0 }, + { isInserted: true, str: 'def', offset: 0 } + ]; + yield changeText(browser, 'p', 'def', events); + + events = [{ isInserted: true, str: 'DEF', offset: 2 }]; + yield changeText(browser, 'p', 'deDEFf', events); + + // Test isFromUserInput property. + yield removeTextFromInput(browser, 'input', 'n', 1, 2); +}); diff --git a/accessible/tests/browser/browser_treeupdate_ariadialog.js b/accessible/tests/browser/browser_treeupdate_ariadialog.js new file mode 100644 index 000000000000..588933f3e48e --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_ariadialog.js @@ -0,0 +1,42 @@ +/* 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'; + +/* global EVENT_SHOW, ROLE_DIALOG, ROLE_PUSHBUTTON, ROLE_TEXT_LEAF, ROLE_ENTRY, + ROLE_DOCUMENT */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +// Test ARIA Dialog +addAccessibleTask('doc_treeupdate_ariadialog.html', function*(browser, accDoc) { + testAccessibleTree(accDoc, { + role: ROLE_DOCUMENT, + children: [ ] + }); + + // Make dialog visible and update its inner content. + let onShow = waitForEvent(EVENT_SHOW, 'dialog'); + yield ContentTask.spawn(browser, {}, () => + content.document.getElementById('dialog').style.display = 'block'); + yield onShow; + + testAccessibleTree(accDoc, { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_DIALOG, + children: [ + { + role: ROLE_PUSHBUTTON, + children: [ { role: ROLE_TEXT_LEAF } ] + }, + { + role: ROLE_ENTRY + } + ] + } + ] + }); +}); diff --git a/accessible/tests/browser/browser_treeupdate_ariaowns.js b/accessible/tests/browser/browser_treeupdate_ariaowns.js new file mode 100644 index 000000000000..0b029c5521f6 --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_ariaowns.js @@ -0,0 +1,317 @@ +/* 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'; + +/* global EVENT_REORDER */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +function* testContainer1(browser, accDoc) { + const id = 't1_container'; + const docID = getAccessibleDOMNodeID(accDoc); + const acc = findAccessibleChildByID(accDoc, id); + + /* ================= Initial tree test ==================================== */ + // children are swapped by ARIA owns + let tree = { + SECTION: [ + { CHECKBUTTON: [ + { SECTION: [] } + ] }, + { PUSHBUTTON: [ ] } + ] + }; + testAccessibleTree(acc, tree); + + /* ================ Change ARIA owns ====================================== */ + let onReorder = waitForEvent(EVENT_REORDER, id); + yield invokeSetAttribute(browser, id, 'aria-owns', 't1_button t1_subdiv'); + yield onReorder; + + // children are swapped again, button and subdiv are appended to + // the children. + tree = { + SECTION: [ + { CHECKBUTTON: [ ] }, // checkbox, native order + { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own + { SECTION: [ ] } // subdiv from the subtree, ARIA owned + ] + }; + testAccessibleTree(acc, tree); + + /* ================ Remove ARIA owns ====================================== */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield invokeSetAttribute(browser, id, 'aria-owns'); + yield onReorder; + + // children follow the DOM order + tree = { + SECTION: [ + { PUSHBUTTON: [ ] }, + { CHECKBUTTON: [ + { SECTION: [] } + ] } + ] + }; + testAccessibleTree(acc, tree); + + /* ================ Set ARIA owns ========================================= */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield invokeSetAttribute(browser, id, 'aria-owns', 't1_button t1_subdiv'); + yield onReorder; + + // children are swapped again, button and subdiv are appended to + // the children. + tree = { + SECTION: [ + { CHECKBUTTON: [ ] }, // checkbox + { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own + { SECTION: [ ] } // subdiv from the subtree, ARIA owned + ] + }; + testAccessibleTree(acc, tree); + + /* ================ Add ID to ARIA owns =================================== */ + onReorder = waitForEvent(EVENT_REORDER, docID); + yield invokeSetAttribute(browser, id, 'aria-owns', + 't1_button t1_subdiv t1_group'); + yield onReorder; + + // children are swapped again, button and subdiv are appended to + // the children. + tree = { + SECTION: [ + { CHECKBUTTON: [ ] }, // t1_checkbox + { PUSHBUTTON: [ ] }, // button, t1_button + { SECTION: [ ] }, // subdiv from the subtree, t1_subdiv + { GROUPING: [ ] } // group from outside, t1_group + ] + }; + testAccessibleTree(acc, tree); + + /* ================ Append element ======================================== */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, id => { + let div = content.document.createElement('div'); + div.setAttribute('id', 't1_child3'); + div.setAttribute('role', 'radio'); + content.document.getElementById(id).appendChild(div); + }); + yield onReorder; + + // children are invalidated, they includes aria-owns swapped kids and + // newly inserted child. + 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(acc, tree); + + /* ================ Remove element ======================================== */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, {}, () => + content.document.getElementById('t1_span').parentNode.removeChild( + content.document.getElementById('t1_span'))); + yield onReorder; + + // subdiv should go away + tree = { + SECTION: [ + { CHECKBUTTON: [ ] }, // explicit, t1_checkbox + { RADIOBUTTON: [ ] }, // explicit, t1_child3 + { PUSHBUTTON: [ ] }, // ARIA owned, t1_button + { GROUPING: [ ] } // ARIA owned, t1_group + ] + }; + testAccessibleTree(acc, tree); + + /* ================ Remove ID ============================================= */ + onReorder = waitForEvent(EVENT_REORDER, docID); + yield invokeSetAttribute(browser, 't1_group', 'id'); + yield onReorder; + + tree = { + SECTION: [ + { CHECKBUTTON: [ ] }, + { RADIOBUTTON: [ ] }, + { PUSHBUTTON: [ ] } // ARIA owned, t1_button + ] + }; + testAccessibleTree(acc, tree); + + /* ================ Set ID ================================================ */ + onReorder = waitForEvent(EVENT_REORDER, docID); + yield invokeSetAttribute(browser, 't1_grouptmp', 'id', 't1_group'); + yield onReorder; + + tree = { + SECTION: [ + { CHECKBUTTON: [ ] }, + { RADIOBUTTON: [ ] }, + { PUSHBUTTON: [ ] }, // ARIA owned, t1_button + { GROUPING: [ ] } // ARIA owned, t1_group, previously t1_grouptmp + ] + }; + testAccessibleTree(acc, tree); +} + +function* removeContainer(browser, accDoc) { + const id = 't2_container1'; + const acc = findAccessibleChildByID(accDoc, id); + + let tree = { + SECTION: [ + { CHECKBUTTON: [ ] } // ARIA owned, 't2_owned' + ] + }; + testAccessibleTree(acc, tree); + + let onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, {}, () => + content.document.getElementById('t2_container2').removeChild( + content.document.getElementById('t2_container3'))); + yield onReorder; + + tree = { + SECTION: [ ] + }; + testAccessibleTree(acc, tree); +} + +function* stealAndRecacheChildren(browser, accDoc) { + const id1 = 't3_container1'; + const id2 = 't3_container2'; + const acc1 = findAccessibleChildByID(accDoc, id1); + const acc2 = findAccessibleChildByID(accDoc, id2); + + /* ================ Steal from other ARIA owns ============================ */ + let onReorder = waitForEvent(EVENT_REORDER, id2); + yield invokeSetAttribute(browser, id2, 'aria-owns', 't3_child'); + yield onReorder; + + let tree = { + SECTION: [ ] + }; + testAccessibleTree(acc1, tree); + + tree = { + SECTION: [ + { CHECKBUTTON: [ ] } + ] + }; + testAccessibleTree(acc2, tree); + + /* ================ Append element to recache children ==================== */ + onReorder = waitForEvent(EVENT_REORDER, id2); + yield ContentTask.spawn(browser, id2, id => { + let div = content.document.createElement('div'); + div.setAttribute('role', 'radio'); + content.document.getElementById(id).appendChild(div); + }); + yield onReorder; + + tree = { + SECTION: [ ] + }; + testAccessibleTree(acc1, tree); + + tree = { + SECTION: [ + { RADIOBUTTON: [ ] }, + { CHECKBUTTON: [ ] } // ARIA owned + ] + }; + testAccessibleTree(acc2, tree); +} + +function* showHiddenElement(browser, accDoc) { + const id = 't4_container1'; + const acc = findAccessibleChildByID(accDoc, id); + + let tree = { + SECTION: [ + { RADIOBUTTON: [] } + ] + }; + testAccessibleTree(acc, tree); + + let onReorder = waitForEvent(EVENT_REORDER, id); + yield invokeSetStyle(browser, 't4_child1', 'display', 'block'); + yield onReorder; + + tree = { + SECTION: [ + { CHECKBUTTON: [] }, + { RADIOBUTTON: [] } + ] + }; + testAccessibleTree(acc, tree); +} + +function* rearrangeARIAOwns(browser, accDoc) { + const id = 't5_container'; + const acc = findAccessibleChildByID(accDoc, id); + const tests = [{ + val: 't5_checkbox t5_radio t5_button', + roleList: [ 'CHECKBUTTON', 'RADIOBUTTON', 'PUSHBUTTON' ] + }, { + val: 't5_radio t5_button t5_checkbox', + roleList: [ 'RADIOBUTTON', 'PUSHBUTTON', 'CHECKBUTTON' ] + }]; + + for (let { val, roleList } of tests) { + let onReorder = waitForEvent(EVENT_REORDER, id); + yield invokeSetAttribute(browser, id, 'aria-owns', val); + yield onReorder; + + let tree = { SECTION: [ ] }; + for (let role of roleList) { + let ch = {}; + ch[role] = []; + tree.SECTION.push(ch); + } + testAccessibleTree(acc, tree); + } +} + +function* removeNotARIAOwnedEl(browser, accDoc) { + const id = 't6_container'; + const acc = findAccessibleChildByID(accDoc, id); + + let tree = { + SECTION: [ + { TEXT_LEAF: [ ] }, + { GROUPING: [ ] } + ] + }; + testAccessibleTree(acc, tree); + + let onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, id => + content.document.getElementById(id).removeChild( + content.document.getElementById('t6_span'))); + yield onReorder; + + tree = { + SECTION: [ + { GROUPING: [ ] } + ] + }; + testAccessibleTree(acc, tree); +} + +addAccessibleTask('doc_treeupdate_ariaowns.html', function*(browser, accDoc) { + yield testContainer1(browser, accDoc); + yield removeContainer(browser, accDoc); + yield stealAndRecacheChildren(browser, accDoc); + yield showHiddenElement(browser, accDoc); + yield rearrangeARIAOwns(browser, accDoc); + yield removeNotARIAOwnedEl(browser, accDoc); +}); diff --git a/accessible/tests/browser/browser_treeupdate_canvas.js b/accessible/tests/browser/browser_treeupdate_canvas.js new file mode 100644 index 000000000000..e4b3b70f76c6 --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_canvas.js @@ -0,0 +1,25 @@ +/* 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'; + +/* global EVENT_SHOW */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +addAccessibleTask(` + + + `, function*(browser, accDoc) { + let canvas = findAccessibleChildByID(accDoc, 'canvas'); + let dialog = findAccessibleChildByID(accDoc, 'dialog'); + + testAccessibleTree(canvas, { CANVAS: [] }); + + let onShow = waitForEvent(EVENT_SHOW, 'dialog'); + yield invokeSetStyle(browser, 'dialog', 'display', 'block'); + yield onShow; + + testAccessibleTree(dialog, { DIALOG: [] }); +}); diff --git a/accessible/tests/browser/browser_treeupdate_cssoverflow.js b/accessible/tests/browser/browser_treeupdate_cssoverflow.js new file mode 100644 index 000000000000..d8b217380550 --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_cssoverflow.js @@ -0,0 +1,64 @@ +/* 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'; + +/* global EVENT_REORDER */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +addAccessibleTask(` +
+
+
+
`, function*(browser, accDoc) { + const id1 = 'container'; + const id2 = 'container2'; + const container = findAccessibleChildByID(accDoc, id1); + const container2 = findAccessibleChildByID(accDoc, id2); + + /* ================= Change scroll range ================================== */ + let tree = { + SECTION: [ {// container + SECTION: [ {// scroll area + ENTRY: [ ] // child content + } ] + } ] + }; + testAccessibleTree(container, tree); + + let onReorder = waitForEvent(EVENT_REORDER, id1); + yield ContentTask.spawn(browser, id1, id => { + let doc = content.document; + doc.getElementById('scrollarea').style.width = '20px'; + doc.getElementById(id).appendChild(doc.createElement('input')); + }); + yield onReorder; + + tree = { + SECTION: [ {// container + SECTION: [ {// scroll area + ENTRY: [ ] // child content + } ] + }, { + ENTRY: [ ] // inserted input + } ] + }; + testAccessibleTree(container, tree); + + /* ================= Change scrollbar styles ============================== */ + tree = { SECTION: [ ] }; + testAccessibleTree(container2, tree); + + onReorder = waitForEvent(EVENT_REORDER, id2); + yield invokeSetStyle(browser, 'scrollarea2', 'overflow', 'auto'); + yield onReorder; + + tree = { + SECTION: [ // container + { SECTION: [] } // scroll area + ] + }; + testAccessibleTree(container2, tree); +}); diff --git a/accessible/tests/browser/browser_treeupdate_doc.js b/accessible/tests/browser/browser_treeupdate_doc.js new file mode 100644 index 000000000000..ccb0e0487037 --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_doc.js @@ -0,0 +1,303 @@ +/* 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'; + +/* global ROLE_PUSHBUTTON, ROLE_TEXT_LEAF, EVENT_REORDER, ROLE_DOCUMENT, + nsIAccessibleDocument */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +const iframeSrc = `data:text/html, + + + + Inner Iframe + + + `; + +addAccessibleTask(` + `, function*(browser, accDoc) { + // ID of the iframe that is being tested + const id = 'inner-iframe'; + + let iframe = findAccessibleChildByID(accDoc, id); + + /* ================= Initial tree check =================================== */ + let tree = { + role: ROLE_DOCUMENT, + children: [ ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Write iframe document ================================ */ + let onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, id => { + let docNode = content.document.getElementById('iframe').contentDocument; + let newHTMLNode = docNode.createElement('html'); + let newBodyNode = docNode.createElement('body'); + let newTextNode = docNode.createTextNode('New Wave'); + newBodyNode.id = id; + newBodyNode.appendChild(newTextNode); + newHTMLNode.appendChild(newBodyNode); + docNode.replaceChild(newHTMLNode, docNode.documentElement); + }); + yield onReorder; + + tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'New Wave' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Replace iframe HTML element ========================== */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, id => { + let docNode = content.document.getElementById('iframe').contentDocument; + // We can't use open/write/close outside of iframe document because of + // security error. + let script = docNode.createElement('script'); + script.textContent = ` + document.open(); + document.write('hello'); + document.close();`; + docNode.body.appendChild(script); + }); + yield onReorder; + + tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'hello' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Replace iframe body ================================== */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, id => { + let docNode = content.document.getElementById('iframe').contentDocument; + let newBodyNode = docNode.createElement('body'); + let newTextNode = docNode.createTextNode('New Hello'); + newBodyNode.id = id; + newBodyNode.appendChild(newTextNode); + newBodyNode.setAttribute('role', 'button'); + docNode.documentElement.replaceChild(newBodyNode, docNode.body); + }); + yield onReorder; + + tree = { + role: ROLE_PUSHBUTTON, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'New Hello' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Open iframe document ================================= */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, id => { + // Open document. + let docNode = content.document.getElementById('iframe').contentDocument; + let script = docNode.createElement('script'); + script.textContent = ` + function closeMe() { + document.write('Works?'); + document.close(); + } + window.closeMe = closeMe; + document.open(); + document.write('');`; + docNode.body.appendChild(script); + }); + yield onReorder; + + tree = { + role: ROLE_DOCUMENT, + children: [ ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Close iframe document ================================ */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, {}, () => { + // Write and close document. + let docNode = content.document.getElementById('iframe').contentDocument; + docNode.write('Works?'); + docNode.close(); + }); + yield onReorder; + + tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'Works?' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Remove HTML from iframe document ===================== */ + onReorder = waitForEvent(EVENT_REORDER); + yield ContentTask.spawn(browser, {}, () => { + // Remove HTML element. + let docNode = content.document.getElementById('iframe').contentDocument; + docNode.removeChild(docNode.firstChild); + }); + let event = yield onReorder; + + ok(event.accessible instanceof nsIAccessibleDocument, + 'Reorder should happen on the document'); + tree = { + role: ROLE_DOCUMENT, + children: [ ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Insert HTML to iframe document ======================= */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, id => { + // Insert HTML element. + let docNode = content.document.getElementById('iframe').contentDocument; + let html = docNode.createElement('html'); + let body = docNode.createElement('body'); + let text = docNode.createTextNode('Haha'); + body.appendChild(text); + body.id = id; + html.appendChild(body); + docNode.appendChild(html); + }); + yield onReorder; + + tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'Haha' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Remove body from iframe document ===================== */ + onReorder = waitForEvent(EVENT_REORDER); + yield ContentTask.spawn(browser, {}, () => { + // Remove body element. + let docNode = content.document.getElementById('iframe').contentDocument; + docNode.documentElement.removeChild(docNode.body); + }); + event = yield onReorder; + + ok(event.accessible instanceof nsIAccessibleDocument, + 'Reorder should happen on the document'); + tree = { + role: ROLE_DOCUMENT, + children: [ ] + }; + testAccessibleTree(iframe, tree); + + /* ================ Insert element under document element while body missed */ + onReorder = waitForEvent(EVENT_REORDER); + yield ContentTask.spawn(browser, {}, () => { + let docNode = content.document.getElementById('iframe').contentDocument; + let inputNode = content.window.inputNode = docNode.createElement('input'); + docNode.documentElement.appendChild(inputNode); + }); + event = yield onReorder; + + ok(event.accessible instanceof nsIAccessibleDocument, + 'Reorder should happen on the document'); + tree = { + DOCUMENT: [ + { ENTRY: [ ] } + ] + }; + testAccessibleTree(iframe, tree); + + yield ContentTask.spawn(browser, {}, () => { + let docNode = content.document.getElementById('iframe').contentDocument; + // Remove aftermath of this test before next test starts. + docNode.documentElement.removeChild(content.window.inputNode); + }); + + /* ================= Insert body to iframe document ======================= */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, id => { + // Write and close document. + let docNode = content.document.getElementById('iframe').contentDocument; + // Insert body element. + let body = docNode.createElement('body'); + let text = docNode.createTextNode('Yo ho ho i butylka roma!'); + body.appendChild(text); + body.id = id; + docNode.documentElement.appendChild(body); + }); + yield onReorder; + + tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'Yo ho ho i butylka roma!' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Change source ======================================== */ + onReorder = waitForEvent(EVENT_REORDER, 'iframe'); + yield invokeSetAttribute(browser, 'iframe', 'src', + `data:text/html,`); + event = yield onReorder; + + tree = { + INTERNAL_FRAME: [ + { DOCUMENT: [ + { ENTRY: [ ] } + ] } + ] + }; + testAccessibleTree(event.accessible, tree); + iframe = findAccessibleChildByID(event.accessible, id); + + /* ================= Replace iframe body on ARIA role body ================ */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, id => { + let docNode = content.document.getElementById('iframe').contentDocument; + let newBodyNode = docNode.createElement('body'); + let newTextNode = docNode.createTextNode('New Hello'); + newBodyNode.appendChild(newTextNode); + newBodyNode.setAttribute('role', 'button'); + newBodyNode.id = id; + docNode.documentElement.replaceChild(newBodyNode, docNode.body); + }); + yield onReorder; + + tree = { + role: ROLE_PUSHBUTTON, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'New Hello' + } + ] + }; + testAccessibleTree(iframe, tree); +}); diff --git a/accessible/tests/browser/browser_treeupdate_gencontent.js b/accessible/tests/browser/browser_treeupdate_gencontent.js new file mode 100644 index 000000000000..12641928880e --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_gencontent.js @@ -0,0 +1,78 @@ +/* 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'; + +/* global EVENT_REORDER */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +addAccessibleTask(` + +
+
text
`, + function*(browser, accDoc) { + const id1 = 'container1'; + const id2 = 'container2'; + let container1 = findAccessibleChildByID(accDoc, id1); + let container2 = findAccessibleChildByID(accDoc, id2); + + let tree = { + SECTION: [ ] // container + }; + testAccessibleTree(container1, tree); + + tree = { + SECTION: [ { // container2 + SECTION: [ { // container2 child + TEXT_LEAF: [ ] // primary text + } ] + } ] + }; + testAccessibleTree(container2, tree); + + let onReorder = waitForEvent(EVENT_REORDER, id1); + // Create and add an element with CSS generated content to container1 + yield ContentTask.spawn(browser, id1, id => { + let node = content.document.createElement('div'); + node.textContent = 'text'; + node.setAttribute('class', 'gentext'); + content.document.getElementById(id).appendChild(node); + }); + yield onReorder; + + tree = { + SECTION: [ // container + { SECTION: [ // inserted node + { STATICTEXT: [] }, // :before + { TEXT_LEAF: [] }, // primary text + { STATICTEXT: [] } // :after + ] } + ] + }; + testAccessibleTree(container1, tree); + + onReorder = waitForEvent(EVENT_REORDER, id2); + // Add CSS generated content to an element in container2's subtree + yield invokeSetAttribute(browser, 'container2_child', 'class', 'gentext'); + yield onReorder; + + tree = { + SECTION: [ // container2 + { SECTION: [ // container2 child + { STATICTEXT: [] }, // :before + { TEXT_LEAF: [] }, // primary text + { STATICTEXT: [] } // :after + ] } + ] + }; + testAccessibleTree(container2, tree); + }); diff --git a/accessible/tests/browser/browser_treeupdate_hidden.js b/accessible/tests/browser/browser_treeupdate_hidden.js new file mode 100644 index 000000000000..00369ec053e9 --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_hidden.js @@ -0,0 +1,30 @@ +/* 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'; + +/* global EVENT_REORDER */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +function* setHidden(browser, value) { + let onReorder = waitForEvent(EVENT_REORDER, 'container'); + yield invokeSetAttribute(browser, 'child', 'hidden', value); + yield onReorder; +} + +addAccessibleTask('
', + function*(browser, accDoc) { + let container = findAccessibleChildByID(accDoc, 'container'); + + testAccessibleTree(container, { SECTION: [ { ENTRY: [ ] } ] }); + + // Set @hidden attribute + yield setHidden(browser, 'true'); + testAccessibleTree(container, { SECTION: [ ] }); + + // Remove @hidden attribute + yield setHidden(browser); + testAccessibleTree(container, { SECTION: [ { ENTRY: [ ] } ] }); + }); diff --git a/accessible/tests/browser/browser_treeupdate_imagemap.js b/accessible/tests/browser/browser_treeupdate_imagemap.js new file mode 100644 index 000000000000..161577ec3519 --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_imagemap.js @@ -0,0 +1,176 @@ +/* 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'; + +/* global EVENT_REORDER, ROLE_LINK */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +function* testImageMap(browser, accDoc) { + const id = 'imgmap'; + const acc = findAccessibleChildByID(accDoc, id); + + /* ================= Initial tree test ==================================== */ + let tree = { + IMAGE_MAP: [ + { role: ROLE_LINK, name: 'b', children: [ ] } + ] + }; + testAccessibleTree(acc, tree); + + /* ================= Insert area ========================================== */ + let onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, {}, () => { + let areaElm = content.document.createElement('area'); + let mapNode = content.document.getElementById('map'); + areaElm.setAttribute('href', + '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'); + mapNode.insertBefore(areaElm, mapNode.firstChild); + }); + yield onReorder; + + tree = { + IMAGE_MAP: [ + { role: ROLE_LINK, name: 'a', children: [ ] }, + { role: ROLE_LINK, name: 'b', children: [ ] } + ] + }; + testAccessibleTree(acc, tree); + + /* ================= Append area ========================================== */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, {}, () => { + let areaElm = content.document.createElement('area'); + let mapNode = content.document.getElementById('map'); + areaElm.setAttribute('href', + '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'); + mapNode.appendChild(areaElm); + }); + yield onReorder; + + tree = { + IMAGE_MAP: [ + { role: ROLE_LINK, name: 'a', children: [ ] }, + { role: ROLE_LINK, name: 'b', children: [ ] }, + { role: ROLE_LINK, name: 'c', children: [ ] } + ] + }; + testAccessibleTree(acc, tree); + + /* ================= Remove area ========================================== */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, {}, () => { + let mapNode = content.document.getElementById('map'); + mapNode.removeChild(mapNode.firstElementChild); + }); + yield onReorder; + + tree = { + IMAGE_MAP: [ + { role: ROLE_LINK, name: 'b', children: [ ] }, + { role: ROLE_LINK, name: 'c', children: [ ] } + ] + }; + testAccessibleTree(acc, tree); +} + +function* testContainer(browser) { + const id = 'container'; + /* ================= Remove name on map =================================== */ + let onReorder = waitForEvent(EVENT_REORDER, id); + yield invokeSetAttribute(browser, 'map', 'name'); + let event = yield onReorder; + const acc = event.accessible; + + let tree = { + SECTION: [ + { GRAPHIC: [ ] } + ] + }; + testAccessibleTree(acc, tree); + + /* ================= Restore name on map ================================== */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield invokeSetAttribute(browser, 'map', 'name', 'atoz_map'); + // XXX: force repainting of the image (see bug 745788 for details). + yield BrowserTestUtils.synthesizeMouse('#imgmap', 10, 10, + { type: 'mousemove' }, browser); + yield onReorder; + + tree = { + SECTION: [ { + IMAGE_MAP: [ + { LINK: [ ] }, + { LINK: [ ] } + ] + } ] + }; + testAccessibleTree(acc, tree); + + /* ================= Remove map =========================================== */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, {}, () => { + let mapNode = content.document.getElementById('map'); + mapNode.parentNode.removeChild(mapNode); + }); + yield onReorder; + + tree = { + SECTION: [ + { GRAPHIC: [ ] } + ] + }; + testAccessibleTree(acc, tree); + + /* ================= Insert map =========================================== */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, id => { + let map = content.document.createElement('map'); + let area = content.document.createElement('area'); + + map.setAttribute('name', 'atoz_map'); + map.setAttribute('id', 'map'); + + area.setAttribute('href', + '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); + content.document.getElementById(id).appendChild(map); + }); + yield onReorder; + + tree = { + SECTION: [ { + IMAGE_MAP: [ + { LINK: [ ] } + ] + } ] + }; + testAccessibleTree(acc, tree); + + /* ================= Hide image map ======================================= */ + onReorder = waitForEvent(EVENT_REORDER, id); + yield invokeSetStyle(browser, 'imgmap', 'display', 'none'); + yield onReorder; + + tree = { + SECTION: [ ] + }; + testAccessibleTree(acc, tree); +} + +addAccessibleTask('doc_treeupdate_imagemap.html', function*(browser, accDoc) { + yield testImageMap(browser, accDoc); + yield testContainer(browser); +}); diff --git a/accessible/tests/browser/browser_treeupdate_list.js b/accessible/tests/browser/browser_treeupdate_list.js new file mode 100644 index 000000000000..023adf8e3f2d --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_list.js @@ -0,0 +1,43 @@ +/* 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'; + +/* global ROLE_TEXT_LEAF, EVENT_REORDER, ROLE_STATICTEXT, ROLE_LISTITEM */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +function* setDisplayAndWaitForReorder(browser, value) { + let onReorder = waitForEvent(EVENT_REORDER, 'ul'); + yield invokeSetStyle(browser, 'li', 'display', value); + return yield onReorder; +} + +addAccessibleTask(` + `, function*(browser, accDoc) { + let li = findAccessibleChildByID(accDoc, 'li'); + let bullet = li.firstChild; + let accTree = { + role: ROLE_LISTITEM, + children: [ { + role: ROLE_STATICTEXT, + children: [] + }, { + role: ROLE_TEXT_LEAF, + children: [] + } ] + }; + testAccessibleTree(li, accTree); + + yield setDisplayAndWaitForReorder(browser, 'none'); + + ok(isDefunct(li), 'Check that li is defunct.'); + ok(isDefunct(bullet), 'Check that bullet is defunct.'); + + let event = yield setDisplayAndWaitForReorder(browser, 'list-item'); + + testAccessibleTree(findAccessibleChildByID(event.accessible, 'li'), accTree); +}); diff --git a/accessible/tests/browser/browser_treeupdate_list_editabledoc.js b/accessible/tests/browser/browser_treeupdate_list_editabledoc.js new file mode 100644 index 000000000000..7b01af87abce --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_list_editabledoc.js @@ -0,0 +1,39 @@ +/* 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'; + +/* global ROLE_TEXT_LEAF, EVENT_REORDER, ROLE_LISTITEM, ROLE_LIST, + ROLE_STATICTEXT */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +addAccessibleTask('
    ', function*(browser, accDoc) { + let list = findAccessibleChildByID(accDoc, 'list'); + + testAccessibleTree(list, { + role: ROLE_LIST, + children: [ ] + }); + + yield invokeSetAttribute(browser, 'body', 'contentEditable', 'true'); + let onReorder = waitForEvent(EVENT_REORDER, 'list'); + yield ContentTask.spawn(browser, {}, () => { + let li = content.document.createElement('li'); + li.textContent = 'item'; + content.document.getElementById('list').appendChild(li); + }); + yield onReorder; + + testAccessibleTree(list, { + role: ROLE_LIST, + children: [ { + role: ROLE_LISTITEM, + children: [ + { role: ROLE_STATICTEXT, name: "1. ", children: [] }, + { role: ROLE_TEXT_LEAF, children: [] } + ] + } ] + }); +}); diff --git a/accessible/tests/browser/browser_treeupdate_listener.js b/accessible/tests/browser/browser_treeupdate_listener.js new file mode 100644 index 000000000000..6ca68e372b61 --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_listener.js @@ -0,0 +1,43 @@ +/* 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'; + +/* global EVENT_REORDER */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +addAccessibleTask('', + function*(browser, accDoc) { + is(findAccessibleChildByID(accDoc, 'parent'), null, + 'Check that parent is not accessible.'); + is(findAccessibleChildByID(accDoc, 'child'), null, + 'Check that child is not accessible.'); + + let onReorder = waitForEvent(EVENT_REORDER, 'body'); + // Add an event listener to parent. + yield ContentTask.spawn(browser, {}, () => { + content.window.dummyListener = () => {}; + content.document.getElementById('parent').addEventListener( + 'click', content.window.dummyListener); + }); + yield onReorder; + + let tree = { TEXT: [] }; + testAccessibleTree(findAccessibleChildByID(accDoc, 'parent'), tree); + + onReorder = waitForEvent(EVENT_REORDER, 'body'); + // Remove an event listener from parent. + yield ContentTask.spawn(browser, {}, () => { + content.document.getElementById('parent').removeEventListener( + 'click', content.window.dummyListener); + delete content.window.dummyListener; + }); + yield onReorder; + + is(findAccessibleChildByID(accDoc, 'parent'), null, + 'Check that parent is not accessible.'); + is(findAccessibleChildByID(accDoc, 'child'), null, + 'Check that child is not accessible.'); + }); diff --git a/accessible/tests/browser/browser_treeupdate_optgroup.js b/accessible/tests/browser/browser_treeupdate_optgroup.js new file mode 100644 index 000000000000..efd5533b3281 --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_optgroup.js @@ -0,0 +1,91 @@ +/* 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'; + +/* global EVENT_REORDER */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +addAccessibleTask('', function*(browser, accDoc) { + let select = findAccessibleChildByID(accDoc, 'select'); + + let onEvent = waitForEvent(EVENT_REORDER, 'select'); + // Create a combobox with grouping and 2 standalone options + yield ContentTask.spawn(browser, {}, () => { + let doc = content.document; + let select = doc.getElementById('select'); + let optGroup = doc.createElement('optgroup'); + + for (let i = 0; i < 2; i++) { + let opt = doc.createElement('option'); + opt.value = i; + opt.text = 'Option: Value ' + i; + optGroup.appendChild(opt); + } + select.add(optGroup, null); + + for (let i = 0; i < 2; i++) { + let opt = doc.createElement('option'); + select.add(opt, null); + } + select.firstChild.firstChild.id = 'option1Node'; + }); + let event = yield onEvent; + let option1Node = findAccessibleChildByID(event.accessible, 'option1Node'); + + let tree = { + COMBOBOX: [ { + COMBOBOX_LIST: [ { + GROUPING: [ + { COMBOBOX_OPTION: [ { TEXT_LEAF: [] } ] }, + { COMBOBOX_OPTION: [ { TEXT_LEAF: [] } ] } + ] + }, { + COMBOBOX_OPTION: [] + }, { + COMBOBOX_OPTION: [] + } ] + } ] + }; + testAccessibleTree(select, tree); + ok(!isDefunct(option1Node), 'option shouldn\'t be defunct'); + + onEvent = waitForEvent(EVENT_REORDER, 'select'); + // Remove grouping from combobox + yield ContentTask.spawn(browser, {}, () => { + let select = content.document.getElementById('select'); + select.removeChild(select.firstChild); + }); + yield onEvent; + + tree = { + COMBOBOX: [ { + COMBOBOX_LIST: [ + { COMBOBOX_OPTION: [] }, + { COMBOBOX_OPTION: [] } + ] + } ] + }; + testAccessibleTree(select, tree); + ok(isDefunct(option1Node), + 'removed option shouldn\'t be accessible anymore!'); + + onEvent = waitForEvent(EVENT_REORDER, 'select'); + // Remove all options from combobox + yield ContentTask.spawn(browser, {}, () => { + let select = content.document.getElementById('select'); + while (select.length) { + select.remove(0); + } + }); + yield onEvent; + + tree = { + COMBOBOX: [ { + COMBOBOX_LIST: [ ] + } ] + }; + testAccessibleTree(select, tree); +}); diff --git a/accessible/tests/browser/browser_treeupdate_removal.js b/accessible/tests/browser/browser_treeupdate_removal.js new file mode 100644 index 000000000000..9892bbcd68df --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_removal.js @@ -0,0 +1,39 @@ +/* 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'; + +/* global EVENT_REORDER */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +addAccessibleTask('doc_treeupdate_removal.xhtml', function*(browser, accDoc) { + ok(isAccessible(findAccessibleChildByID(accDoc, 'the_table')), + 'table should be accessible'); + + // Move the_table element into hidden subtree. + let onReorder = waitForEvent(EVENT_REORDER, 'body'); + yield ContentTask.spawn(browser, {}, () => content.document.getElementById( + 'the_displaynone').appendChild(content.document.getElementById( + 'the_table'))); + yield onReorder; + + ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_table')), + 'table in display none tree shouldn\'t be accessible'); + ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_row')), + 'row shouldn\'t be accessible'); + + // Remove the_row element (since it did not have accessible, no event needed). + yield ContentTask.spawn(browser, {}, () => + content.document.body.removeChild( + content.document.getElementById('the_row'))); + + // make sure no accessibles have stuck around. + ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_row')), + 'row shouldn\'t be accessible'); + ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_table')), + 'table shouldn\'t be accessible'); + ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_displayNone')), + 'display none things shouldn\'t be accessible'); +}); diff --git a/accessible/tests/browser/browser_treeupdate_table.js b/accessible/tests/browser/browser_treeupdate_table.js new file mode 100644 index 000000000000..9609f51acbdf --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_table.js @@ -0,0 +1,51 @@ +/* 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'; + +/* global EVENT_REORDER */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +addAccessibleTask(` + + + + + +
    cell1cell2
    `, function*(browser, accDoc) { + let table = findAccessibleChildByID(accDoc, 'table'); + + let tree = { + TABLE: [ + { ROW: [ + { CELL: [ {TEXT_LEAF: [] }]}, + { CELL: [ {TEXT_LEAF: [] }]} + ] } + ] + }; + testAccessibleTree(table, tree); + + let onReorder = waitForEvent(EVENT_REORDER, 'table'); + yield ContentTask.spawn(browser, {}, () => { + // append a caption, it should appear as a first element in the + // accessible tree. + let doc = content.document; + let caption = doc.createElement('caption'); + caption.textContent = 'table caption'; + doc.getElementById('table').appendChild(caption); + }); + yield onReorder; + + tree = { + TABLE: [ + { CAPTION: [ { TEXT_LEAF: [] } ] }, + { ROW: [ + { CELL: [ {TEXT_LEAF: [] }]}, + { CELL: [ {TEXT_LEAF: [] }]} + ] } + ] + }; + testAccessibleTree(table, tree); +}); diff --git a/accessible/tests/browser/browser_treeupdate_textleaf.js b/accessible/tests/browser/browser_treeupdate_textleaf.js new file mode 100644 index 000000000000..8c7b495d5a81 --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_textleaf.js @@ -0,0 +1,34 @@ +/* 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'; + +/* global EVENT_REORDER, ROLE_TEXT_CONTAINER ROLE_PARAGRAPH, ROLE_TEXT_LEAF */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +function* removeTextData(browser, accessible, id, role) { + let tree = { + role: role, + children: [ { role: ROLE_TEXT_LEAF, name: "text" } ] + }; + testAccessibleTree(accessible, tree); + + let onReorder = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, id => + content.document.getElementById(id).firstChild.textContent = ''); + yield onReorder; + + tree = { role: role, children: [] }; + testAccessibleTree(accessible, tree); +} + +addAccessibleTask(` +

    text

    +
    text
    `, function*(browser, accDoc) { + let p = findAccessibleChildByID(accDoc, 'p'); + let pre = findAccessibleChildByID(accDoc, 'pre'); + yield removeTextData(browser, p, 'p', ROLE_PARAGRAPH); + yield removeTextData(browser, pre, 'pre', ROLE_TEXT_CONTAINER); +}); diff --git a/accessible/tests/browser/browser_treeupdate_visibility.js b/accessible/tests/browser/browser_treeupdate_visibility.js new file mode 100644 index 000000000000..65a55c9149d5 --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_visibility.js @@ -0,0 +1,196 @@ +/* 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'; + +/* global EVENT_REORDER */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +function* testTreeOnHide(browser, accDoc, containerID, id, before, after) { + let acc = findAccessibleChildByID(accDoc, containerID); + testAccessibleTree(acc, before); + + let onReorder = waitForEvent(EVENT_REORDER, containerID); + yield invokeSetStyle(browser, id, 'visibility', 'hidden'); + yield onReorder; + + testAccessibleTree(acc, after); +} + +function* test3(browser, accessible) { + let tree = { + SECTION: [ // container + { SECTION: [ // parent + { SECTION: [ // child + { TEXT_LEAF: [] } + ] } + ] }, + { SECTION: [ // parent2 + { SECTION: [ // child2 + { TEXT_LEAF: [] } + ] } + ] } + ] }; + testAccessibleTree(accessible, tree); + + let onReorder = waitForEvent(EVENT_REORDER, 't3_container'); + yield ContentTask.spawn(browser, {}, () => { + let doc = content.document; + doc.getElementById('t3_container').style.color = 'red'; + doc.getElementById('t3_parent').style.visibility = 'hidden'; + doc.getElementById('t3_parent2').style.visibility = 'hidden'; + }); + yield onReorder; + + tree = { + SECTION: [ // container + { SECTION: [ // child + { TEXT_LEAF: [] } + ] }, + { SECTION: [ // child2 + { TEXT_LEAF: [] } + ] } + ] }; + testAccessibleTree(accessible, tree); +} + +function* test4(browser, accessible) { + let tree = { + SECTION: [ + { TABLE: [ + { ROW: [ + { CELL: [ ] } + ] } + ] } + ] }; + testAccessibleTree(accessible, tree); + + let onReorder = waitForEvent(EVENT_REORDER, 't4_parent'); + yield ContentTask.spawn(browser, {}, () => { + let doc = content.document; + doc.getElementById('t4_container').style.color = 'red'; + doc.getElementById('t4_child').style.visibility = 'visible'; + }); + yield onReorder; + + tree = { + SECTION: [{ + TABLE: [{ + ROW: [{ + CELL: [{ + SECTION: [{ + TEXT_LEAF: [] + }] + }] + }] + }] + }] + }; + testAccessibleTree(accessible, tree); +} + +addAccessibleTask('doc_treeupdate_visibility.html', function*(browser, accDoc) { + let t3Container = findAccessibleChildByID(accDoc, 't3_container'); + let t4Container = findAccessibleChildByID(accDoc, 't4_container'); + + yield testTreeOnHide(browser, accDoc, 't1_container', 't1_parent', { + SECTION: [{ + SECTION: [{ + SECTION: [ { TEXT_LEAF: [] } ] + }] + }] + }, { + SECTION: [ { + SECTION: [ { TEXT_LEAF: [] } ] + } ] + }); + + yield testTreeOnHide(browser, accDoc, 't2_container', 't2_grandparent', { + SECTION: [{ // container + SECTION: [{ // grand parent + SECTION: [{ + SECTION: [{ // child + TEXT_LEAF: [] + }] + }, { + SECTION: [{ // child2 + TEXT_LEAF: [] + }] + }] + }] + }] + }, { + SECTION: [{ // container + SECTION: [{ // child + TEXT_LEAF: [] + }] + }, { + SECTION: [{ // child2 + TEXT_LEAF: [] + }] + }] + }); + + yield test3(browser, t3Container); + yield test4(browser, t4Container); + + yield testTreeOnHide(browser, accDoc, 't5_container', 't5_subcontainer', { + SECTION: [{ // container + SECTION: [{ // subcontainer + TABLE: [{ + ROW: [{ + CELL: [{ + SECTION: [{ // child + TEXT_LEAF: [] + }] + }] + }] + }] + }] + }] + }, { + SECTION: [{ // container + SECTION: [{ // child + TEXT_LEAF: [] + }] + }] + }); + + yield testTreeOnHide(browser, accDoc, 't6_container', 't6_subcontainer', { + SECTION: [{ // container + SECTION: [{ // subcontainer + TABLE: [{ + ROW: [{ + CELL: [{ + TABLE: [{ // nested table + ROW: [{ + CELL: [{ + SECTION: [{ // child + TEXT_LEAF: [] + }] + }] + }] + }] + }] + }] + }] + }, { + SECTION: [{ // child2 + TEXT_LEAF: [] + }] + }] + }] + }, { + SECTION: [{ // container + SECTION: [{ // child + TEXT_LEAF: [] + }] + }, { + SECTION: [{ // child2 + TEXT_LEAF: [] + }] + }] + }); +}); diff --git a/accessible/tests/browser/browser_treeupdate_whitespace.js b/accessible/tests/browser/browser_treeupdate_whitespace.js new file mode 100644 index 000000000000..6c2c56fa11f4 --- /dev/null +++ b/accessible/tests/browser/browser_treeupdate_whitespace.js @@ -0,0 +1,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/. */ + +'use strict'; + +/* global EVENT_REORDER */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }); + +addAccessibleTask(` +
    + + + +
    +
    + + +
    `, function*(browser, accDoc) { + let container1 = findAccessibleChildByID(accDoc, 'container1'); + let container2Parent = findAccessibleChildByID(accDoc, 'container2-parent'); + + let tree = { + SECTION: [ + { GRAPHIC: [] }, + { TEXT_LEAF: [] }, + { GRAPHIC: [] }, + { TEXT_LEAF: [] }, + { GRAPHIC: [] } + ] + }; + testAccessibleTree(container1, tree); + + let onReorder = waitForEvent(EVENT_REORDER, 'container1'); + // Remove img1 from container1 + yield ContentTask.spawn(browser, {}, () => { + let doc = content.document; + doc.getElementById('container1').removeChild( + doc.getElementById('img1')); + }); + yield onReorder; + + tree = { + SECTION: [ + { GRAPHIC: [] }, + { TEXT_LEAF: [] }, + { GRAPHIC: [] } + ] + }; + testAccessibleTree(container1, tree); + + tree = { + SECTION: [ + { LINK: [] }, + { LINK: [ { GRAPHIC: [] } ] } + ] + }; + testAccessibleTree(container2Parent, tree); + + onReorder = waitForEvent(EVENT_REORDER, 'container2-parent'); + // Append an img with valid src to container2 + yield ContentTask.spawn(browser, {}, () => { + let doc = content.document; + let img = doc.createElement('img'); + img.setAttribute('src', + 'http://example.com/a11y/accessible/tests/mochitest/moz.png'); + doc.getElementById('container2').appendChild(img); + }); + yield onReorder; + + tree = { + SECTION: [ + { LINK: [ { GRAPHIC: [ ] } ] }, + { TEXT_LEAF: [ ] }, + { LINK: [ { GRAPHIC: [ ] } ] } + ] + }; + testAccessibleTree(container2Parent, tree); +}); diff --git a/accessible/tests/browser/doc_treeupdate_ariadialog.html b/accessible/tests/browser/doc_treeupdate_ariadialog.html new file mode 100644 index 000000000000..9d08854b9a2a --- /dev/null +++ b/accessible/tests/browser/doc_treeupdate_ariadialog.html @@ -0,0 +1,23 @@ + + + + Tree Update ARIA Dialog Test + + + + + diff --git a/accessible/tests/browser/doc_treeupdate_ariaowns.html b/accessible/tests/browser/doc_treeupdate_ariaowns.html new file mode 100644 index 000000000000..38b5c333a191 --- /dev/null +++ b/accessible/tests/browser/doc_treeupdate_ariaowns.html @@ -0,0 +1,44 @@ + + + + Tree Update ARIA Owns Test + + +
    +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    + +
    +
    + + +
    + +
    +
    + + +
    + +
    + hey +
    +
    + + diff --git a/accessible/tests/browser/doc_treeupdate_imagemap.html b/accessible/tests/browser/doc_treeupdate_imagemap.html new file mode 100644 index 000000000000..4dd230fc2855 --- /dev/null +++ b/accessible/tests/browser/doc_treeupdate_imagemap.html @@ -0,0 +1,21 @@ + + + + Tree Update Imagemap Test + + + + b + + +
    +
    + + diff --git a/accessible/tests/browser/doc_treeupdate_removal.xhtml b/accessible/tests/browser/doc_treeupdate_removal.xhtml new file mode 100644 index 000000000000..9c59fb9d1119 --- /dev/null +++ b/accessible/tests/browser/doc_treeupdate_removal.xhtml @@ -0,0 +1,11 @@ + + + + Tree Update Removal Test + + + +
    + + + diff --git a/accessible/tests/browser/doc_treeupdate_visibility.html b/accessible/tests/browser/doc_treeupdate_visibility.html new file mode 100644 index 000000000000..c33a2bc02f4c --- /dev/null +++ b/accessible/tests/browser/doc_treeupdate_visibility.html @@ -0,0 +1,78 @@ + + + + Tree Update Visibility Test + + + +
    +
    +
    text
    +
    +
    + + +
    +
    +
    +
    text
    +
    text
    +
    +
    +
    + + +
    +
    +
    text
    +
    +
    +
    text
    +
    +
    + + +
    + + + + +
    + +
    +
    + + +
    +
    + + + + +
    +
    text
    +
    +
    +
    + + +
    +
    + + + + +
    + + + + +
    +
    text
    +
    +
    +
    text
    +
    +
    + + diff --git a/accessible/tests/browser/events.js b/accessible/tests/browser/events.js new file mode 100644 index 000000000000..428a1cfec6e1 --- /dev/null +++ b/accessible/tests/browser/events.js @@ -0,0 +1,104 @@ +/* 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'; + +/* global nsIAccessibleEvent, nsIAccessibleDocument, + nsIAccessibleStateChangeEvent, nsIAccessibleTextChangeEvent */ + +/* exported EVENT_REORDER, EVENT_SHOW, EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED, + EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE, EVENT_TEXT_CARET_MOVED, + EVENT_STATE_CHANGE, waitForEvent, waitForMultipleEvents */ + +const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE; +const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE; +const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER; +const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW; +const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE; +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; + +/** + * 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; +} + +/** + * A helper function that waits for an accessible event of certain type that + * belongs to a certain DOMNode (defined by its id). + * @param {String} id expected content element id for the event + * @param {Number} eventType expected accessible event type + * @return {Promise} promise that resolves to an event + */ +function waitForEvent(eventType, id) { + return new Promise(resolve => { + let eventObserver = { + observe(subject, topic, data) { + if (topic !== 'accessible-event') { + return; + } + + let event = subject.QueryInterface(nsIAccessibleEvent); + if (Logger.enabled) { + Logger.log(eventToString(event)); + } + + let domID = getAccessibleDOMNodeID(event.accessible); + // If event's accessible does not match expected event type or DOMNode + // id, skip thie event. + if (domID === id && event.eventType === eventType) { + if (Logger.enabled) { + Logger.log(`Correct event DOMNode id: ${id}`); + Logger.log(`Correct event type: ${eventTypeToString(eventType)}`); + } + ok(event.accessibleDocument instanceof nsIAccessibleDocument, + 'Accessible document present.'); + + Services.obs.removeObserver(this, "accessible-event"); + resolve(event); + } + } + }; + Services.obs.addObserver(eventObserver, "accessible-event", false); + }); +} + +/** + * 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) + */ +function waitForMultipleEvents(events) { + // Next expected event index. + let currentIdx = 0; + + return Promise.all(events.map(({ eventType, id }, idx) => + // In addition to waiting for an event, attach an order checker for the + // event. + waitForEvent(eventType, id).then(resolvedEvent => { + // Verify that event happens in order and increment expected index. + is(idx, currentIdx++, + `Unexpected event order: ${eventToString(resolvedEvent)}`); + return resolvedEvent; + }) + )); +} diff --git a/accessible/tests/browser/head.js b/accessible/tests/browser/head.js new file mode 100644 index 000000000000..e035840dce92 --- /dev/null +++ b/accessible/tests/browser/head.js @@ -0,0 +1,266 @@ +/* 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'; + +/* global EVENT_DOCUMENT_LOAD_COMPLETE */ + +/* exported Logger, MOCHITESTS_DIR, isDefunct, addAccessibleTask, + invokeSetAttribute, invokeFocus, invokeSetStyle, + findAccessibleChildByID, getAccessibleDOMNodeID */ + +const { interfaces: Ci, utils: Cu } = Components; + +Cu.import('resource://gre/modules/Services.jsm'); + +/** + * Current browser test directory path used to load subscripts. + */ +const CURRENT_DIR = + 'chrome://mochitests/content/browser/accessible/tests/browser/'; +/** + * A11y mochitest directory where we find common files used in both browser and + * plain tests. + */ +const MOCHITESTS_DIR = + 'chrome://mochitests/content/a11y/accessible/tests/mochitest/'; +/** + * A base URL for test files used in content. + */ +const CURRENT_CONTENT_DIR = + 'http://example.com/browser/accessible/tests/browser/'; + +/** + * Used to dump debug information. + */ +let Logger = { + /** + * Set up this variable to dump log messages into console. + */ + dumpToConsole: false, + + /** + * Set up this variable to dump log messages into error console. + */ + dumpToAppConsole: false, + + /** + * Return true if dump is enabled. + */ + get enabled() { + return this.dumpToConsole || this.dumpToAppConsole; + }, + + /** + * Dump information into console if applicable. + */ + log(msg) { + this.logToConsole(msg); + this.logToAppConsole(msg); + }, + + /** + * Log message to console. + */ + logToConsole(msg) { + if (this.dumpToConsole) { + dump(`\n${msg}\n`); + } + }, + + /** + * Log message to error console. + */ + logToAppConsole(msg) { + if (this.dumpToAppConsole) { + Services.console.logStringMessage(`${msg}`); + } + } +}; + +/** + * Check if an accessible object has a defunct test. + * @param {nsIAccessible} accessible object to test defunct state for + * @return {Boolean} flag indicating defunct state + */ +function isDefunct(accessible) { + try { + let extState = {}; + accessible.getState({}, extState); + return extState.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT; + } catch (x) { + return true; + } +} + +/** + * Asynchronously set or remove content element's attribute (in content process + * if e10s is enabled). + * @param {Object} browser current "tabbrowser" element + * @param {String} id content element id + * @param {String} attr attribute name + * @param {String?} value optional attribute value, if not present, remove + * attribute + * @return {Promise} promise indicating that attribute is set/removed + */ +function invokeSetAttribute(browser, id, attr, value) { + return ContentTask.spawn(browser, { id, attr, value }, + ({ id, attr, value }) => { + let elm = content.document.getElementById(id); + if (value) { + elm.setAttribute(attr, value); + } else { + elm.removeAttribute(attr); + } + }); +} + +/** + * Asynchronously set or remove content element's style (in content process if + * e10s is enabled). + * @param {Object} browser current "tabbrowser" element + * @param {String} id content element id + * @param {String} aStyle style property name + * @param {String?} aValue optional style property value, if not present, + * remove style + * @return {Promise} promise indicating that style is set/removed + */ +function invokeSetStyle(browser, id, style, value) { + return ContentTask.spawn(browser, { id, style, value }, + ({ id, style, value }) => { + let elm = content.document.getElementById(id); + if (value) { + elm.style[style] = value; + } else { + delete elm.style[style]; + } + }); +} + +/** + * Asynchronously set focus on a content element (in content process if e10s is + * enabled). + * @param {Object} browser current "tabbrowser" element + * @param {String} id content element id + * @return {Promise} promise indicating that focus is set + */ +function invokeFocus(browser, id) { + return ContentTask.spawn(browser, id, id => { + let elm = content.document.getElementById(id); + if (elm instanceof Ci.nsIDOMNSEditableElement && elm.editor || + elm instanceof Ci.nsIDOMXULTextBoxElement) { + elm.selectionStart = elm.selectionEnd = elm.value.length; + } + elm.focus(); + }); +} + +/** + * Traverses the accessible tree starting from a given accessible as a root and + * looks for an accessible that matches based on its DOMNode id. + * @param {nsIAccessible} accessible root accessible + * @param {String} id id to look up accessible for + * @return {nsIAccessible?} found accessible if any + */ +function findAccessibleChildByID(accessible, id) { + if (getAccessibleDOMNodeID(accessible) === id) { + return accessible; + } + for (let i = 0; i < accessible.children.length; ++i) { + let found = findAccessibleChildByID(accessible.getChildAt(i), id); + if (found) { + return found; + } + } +} + +/** + * Load a list of scripts into the test + * @param {Array} scripts a list of scripts to load + */ +function loadScripts(...scripts) { + for (let script of scripts) { + let path = typeof script === 'string' ? `${CURRENT_DIR}${script}` : + `${script.dir}${script.name}`; + Services.scriptloader.loadSubScript(path, this); + } +} + +/** + * Load a list of frame scripts into test's content. + * @param {Object} browser browser element that content belongs to + * @param {Array} scripts a list of scripts to load into content + */ +function loadFrameScripts(browser, ...scripts) { + let mm = browser.messageManager; + for (let script of scripts) { + let frameScript; + if (typeof script === 'string') { + if (script.includes('.js')) { + // If script string includes a .js extention, assume it is a script + // path. + frameScript = `${CURRENT_DIR}${script}`; + } else { + // Otherwise it is a serealized script. + frameScript = `data:,${script}`; + } + } else { + // Script is a object that has { dir, name } format. + frameScript = `${script.dir}${script.name}`; + } + mm.loadFrameScript(frameScript, false, true); + } +} + +/** + * A wrapper around browser test add_task that triggers an accessible test task + * as a new browser test task with given document, data URL or markup snippet. + * @param {String} doc URL (relative to current directory) or + * data URL or markup snippet that is used + * to test content with + * @param {Function|Function*} task a generator or a function with tests to + * run + */ +function addAccessibleTask(doc, task) { + add_task(function*() { + let url; + if (doc.includes('doc_')) { + url = `${CURRENT_CONTENT_DIR}${doc}`; + } else { + // Assume it's a markup snippet. + url = `data:text/html, + + + + Accessibility Test + + ${doc} + `; + } + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: url + }, function*(browser) { + yield SimpleTest.promiseFocus(browser); + + loadFrameScripts(browser, + 'let { document, window, navigator } = content;', + { name: 'common.js', dir: MOCHITESTS_DIR }); + + info(`e10s enabled: ${Services.appinfo.browserTabsRemoteAutostart}`); + info(`Actually remote browser: ${browser.isRemoteBrowser}`); + + let onDocLoad = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, 'body'); + browser.reload(); + let event = yield onDocLoad; + + yield task(browser, event.accessible); + }); + }); +} + +// Loading and common.js from accessible/tests/mochitest/ for all tests, as well +// as events.js. +loadScripts({ name: 'common.js', dir: MOCHITESTS_DIR }, 'events.js'); diff --git a/accessible/tests/mochitest/common.js b/accessible/tests/mochitest/common.js index c64eb8d302f4..a835b2dbc8a1 100644 --- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -88,6 +88,11 @@ const kSquareBulletText = String.fromCharCode(0x25fe) + " "; const MAX_TRIM_LENGTH = 100; +/** + * Services to determine if e10s is enabled. + */ +Components.utils.import('resource://gre/modules/Services.jsm'); + /** * nsIAccessibleRetrieval service. */ @@ -737,6 +742,31 @@ function getTextFromClipboard() 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 pretty name for identifier, it may be ID, DOM node or accessible. */ @@ -755,10 +785,17 @@ function prettyName(aIdentifier) if (aIdentifier instanceof nsIAccessible) { var acc = getAccessible(aIdentifier); + var domID = getAccessibleDOMNodeID(acc); var msg = "["; try { - msg += getNodePrettyName(acc.DOMNode); - msg += ", role: " + roleToString(acc.role); + 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) {