diff options
Diffstat (limited to 'accessible/tests')
431 files changed, 66338 insertions, 0 deletions
diff --git a/accessible/tests/browser/.eslintrc.js b/accessible/tests/browser/.eslintrc.js new file mode 100644 index 000000000..53425abc4 --- /dev/null +++ b/accessible/tests/browser/.eslintrc.js @@ -0,0 +1,218 @@ +"use strict"; + +module.exports = { // eslint-disable-line no-undef + "extends": [ + "../../../testing/mochitest/browser.eslintrc.js" + ], + // All globals made available in the test environment. + "globals": { + // Content scripts have global 'content' object + "content": true, + + "add_task": true, + + // Defined in accessible/tests/mochitest/ common.js, name.js, states.js + "prettyName": true, + "statesToString": true, + "eventTypeToString": true, + "testAttrs": true, + "testAbsentAttrs": true, + "testName": true, + "testDescr": true, + "testStates": true, + "testRelation": true, + "testValue": true, + "testAccessibleTree": true, + "isAccessible": true, + "getAccessibleDOMNodeID": true, + + // Defined for all top level accessibility browser tests. + "setE10sPrefs": true, + "unsetE10sPrefs": true, + "initPromise": true, + "shutdownPromise": true, + "forceGC": true, + + // Defined for all e10s accessibility browser tests. + "addAccessibleTask": true, + "BrowserTestUtils": true, + "ContentTask": true, + "gBrowser": true, + "isDefunct": true, + "loadScripts": true, + "loadFrameScripts": true, + "Logger": true, + "MOCHITESTS_DIR": true, + "waitForEvent": true, + "waitForMultipleEvents": true, + "invokeSetAttribute": true, + "invokeSetStyle": true, + "invokeFocus": true, + "findAccessibleChildByID": true + }, + "rules": { + "mozilla/no-aArgs": "warn", + "mozilla/no-cpows-in-tests": "warn", + "mozilla/reject-importGlobalProperties": "warn", + "mozilla/var-only-at-top-level": "warn", + + "block-scoped-var": "error", + "brace-style": ["error", "1tbs"], + "camelcase": "error", + "comma-dangle": ["error", "never"], + "comma-spacing": "error", + "comma-style": ["error", "last"], + "complexity": ["error", 35], + "consistent-this": "off", + "curly": ["error", "multi-line"], + "default-case": "off", + "dot-location": ["error", "property"], + "dot-notation": "error", + "eol-last": "error", + "eqeqeq": "off", + "func-names": "off", + "func-style": "off", + "generator-star": "off", + "global-strict": "off", + "handle-callback-err": ["error", "er"], + "indent": ["error", 2, {"SwitchCase": 1}], + "key-spacing": ["error", {"beforeColon": false, "afterColon": true}], + "linebreak-style": "off", + "max-depth": "off", + "max-nested-callbacks": ["error", 4], + "max-params": "off", + "max-statements": "off", + "new-cap": ["error", {"capIsNew": false}], + "new-parens": "error", + "no-array-constructor": "error", + "no-bitwise": "off", + "no-caller": "error", + "no-catch-shadow": "error", + "no-comma-dangle": "off", + "no-cond-assign": "error", + "no-console": "off", + "no-constant-condition": "off", + "no-continue": "off", + "no-control-regex": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-div-regex": "off", + "no-dupe-args": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-else-return": "error", + "no-empty": "error", + "no-empty-character-class": "error", + "no-eval": "error", + "no-ex-assign": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-extra-boolean-cast": "error", + "no-extra-parens": "off", + "no-extra-semi": "error", + "no-extra-strict": "off", + "no-fallthrough": "error", + "no-floating-decimal": "off", + "no-inline-comments": "off", + "no-lonely-if": "error", + "no-mixed-requires": "off", + "no-mixed-spaces-and-tabs": "error", + "no-multi-spaces": "error", + "no-multi-str": "error", + "no-multiple-empty-lines": ["error", {"max": 1}], + "no-native-reassign": "error", + "no-nested-ternary": "error", + "no-new-require": "off", + "no-octal": "error", + "no-param-reassign": "off", + "no-path-concat": "off", + "no-plusplus": "off", + "no-process-env": "off", + "no-process-exit": "off", + "no-proto": "error", + "no-redeclare": "error", + "no-regex-spaces": "error", + "no-reserved-keys": "off", + "no-restricted-modules": "off", + "no-return-assign": "error", + "no-script-url": "off", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "error", + "no-shadow-restricted-names": "error", + "no-space-before-semi": "off", + "no-spaced-func": "error", + "no-sparse-arrays": "error", + "no-sync": "off", + "no-ternary": "off", + "no-throw-literal": "error", + "no-trailing-spaces": "error", + "no-undef": "error", + "no-underscore-dangle": "off", + "no-undefined": "off", + "no-unneeded-ternary": "error", + "no-unreachable": "error", + "no-unused-vars": ["error", {"vars": "all", "args": "none"}], + "no-use-before-define": "off", + "no-var": "off", + "no-warning-comments": "off", + "no-with": "error", + "object-shorthand": "off", + "one-var": ["error", "never"], + "padded-blocks": ["error", "never"], + "quote-props": "off", + "radix": "error", + "semi": ["error", "always"], + "semi-spacing": ["error", {"before": false, "after": true}], + "sort-vars": "off", + "space-after-function-name": "off", + "keyword-spacing": "error", + "space-before-blocks": "error", + "space-before-function-parentheses": "off", + "space-before-function-paren": ["error", "never"], + "space-in-brackets": "off", + "space-in-parens": ["error", "never"], + "space-infix-ops": ["error", {"int32Hint": true}], + "space-unary-ops": ["error", { "words": true, "nonwords": false }], + "space-unary-word-ops": "off", + "spaced-comment": ["error", "always"], + "strict": ["error", "global"], + "use-isnan": "error", + "valid-jsdoc": "off", + "valid-typeof": "error", + "vars-on-top": "off", + "wrap-iife": "off", + "wrap-regex": "off", + "yoda": "error", + + "guard-for-in": "off", + "newline-after-var": "off", + "no-alert": "off", + "no-eq-null": "off", + "no-func-assign": "off", + "no-implied-eval": "off", + "no-inner-declarations": "off", + "no-invalid-regexp": "off", + "no-irregular-whitespace": "off", + "no-iterator": "off", + "no-label-var": "off", + "no-labels": "error", + "no-lone-blocks": "off", + "no-loop-func": "off", + "no-negated-in-lhs": "off", + "no-new": "off", + "no-new-func": "off", + "no-new-object": "off", + "no-new-wrappers": "off", + "no-obj-calls": "off", + "no-octal-escape": "off", + "no-undef-init": "error", + "no-unexpected-multiline": "error", + "object-curly-spacing": "off", + "no-unused-expressions": "off", + "no-void": "off", + "no-wrap-func": "off", + "operator-assignment": "off", + "operator-linebreak": ["error", "after"] + } +}; diff --git a/accessible/tests/browser/browser.ini b/accessible/tests/browser/browser.ini new file mode 100644 index 000000000..402deda24 --- /dev/null +++ b/accessible/tests/browser/browser.ini @@ -0,0 +1,17 @@ +[DEFAULT] + +support-files = + head.js + shared-head.js + +[browser_shutdown_multi_reference.js] +[browser_shutdown_parent_own_reference.js] +skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content. +[browser_shutdown_remote_no_reference.js] +skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content. +[browser_shutdown_remote_only.js] +skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content. +[browser_shutdown_remote_own_reference.js] +skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content. +[browser_shutdown_scope_lifecycle.js] +[browser_shutdown_start_restart.js] diff --git a/accessible/tests/browser/browser_shutdown_multi_reference.js b/accessible/tests/browser/browser_shutdown_multi_reference.js new file mode 100644 index 000000000..e0ba3ce6b --- /dev/null +++ b/accessible/tests/browser/browser_shutdown_multi_reference.js @@ -0,0 +1,48 @@ +/* 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'; + +add_task(function* () { + info('Creating a service'); + // Create a11y service. + let a11yInit = initPromise(); + let accService1 = Cc['@mozilla.org/accessibilityService;1'].getService( + Ci.nsIAccessibilityService); + yield a11yInit; + ok(accService1, 'Service initialized'); + + // Add another reference to a11y service. This will not trigger + // 'a11y-init-or-shutdown' event + let accService2 = Cc['@mozilla.org/accessibilityService;1'].getService( + Ci.nsIAccessibilityService); + ok(accService2, 'Service initialized'); + + info('Removing all service references'); + let canShutdown = false; + // This promise will resolve only if canShutdonw flag is set to true. If + // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut + // down, the promise will reject. + let a11yShutdown = new Promise((resolve, reject) => + shutdownPromise().then(flag => canShutdown ? + resolve() : reject('Accessible service was shut down incorrectly'))); + // Remove first a11y service reference. + accService1 = null; + ok(!accService1, 'Service is removed'); + // Force garbage collection that should not trigger shutdown because there is + // another reference. + forceGC(); + + // Have some breathing room when removing a11y service references. + yield new Promise(resolve => executeSoon(resolve)); + + // Now allow a11y service to shutdown. + canShutdown = true; + // Remove last a11y service reference. + accService2 = null; + ok(!accService2, 'Service is removed'); + // Force garbage collection that should trigger shutdown. + forceGC(); + yield a11yShutdown; +}); diff --git a/accessible/tests/browser/browser_shutdown_parent_own_reference.js b/accessible/tests/browser/browser_shutdown_parent_own_reference.js new file mode 100644 index 000000000..9895bd2c7 --- /dev/null +++ b/accessible/tests/browser/browser_shutdown_parent_own_reference.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/. */ + +'use strict'; + +add_task(function* () { + // Making sure that the e10s is enabled on Windows for testing. + yield setE10sPrefs(); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: `data:text/html, + <html> + <head> + <meta charset="utf-8"/> + <title>Accessibility Test</title> + </head> + <body></body> + </html>` + }, function*(browser) { + info('Creating a service in parent and waiting for service to be created ' + + 'in content'); + // Create a11y service in the main process. This will trigger creating of + // the a11y service in parent as well. + let parentA11yInit = initPromise(); + let contentA11yInit = initPromise(browser); + let accService = Cc['@mozilla.org/accessibilityService;1'].getService( + Ci.nsIAccessibilityService); + ok(accService, 'Service initialized in parent'); + yield Promise.all([parentA11yInit, contentA11yInit]); + + info('Adding additional reference to accessibility service in content ' + + 'process'); + // Add a new reference to the a11y service inside the content process. + loadFrameScripts(browser, `let accService = Components.classes[ + '@mozilla.org/accessibilityService;1'].getService( + Components.interfaces.nsIAccessibilityService);`); + + info('Trying to shut down a service in content and making sure it stays ' + + 'alive as it was started by parent'); + let contentCanShutdown = false; + // This promise will resolve only if contentCanShutdown flag is set to true. + // If 'a11y-init-or-shutdown' event with '0' flag (in content) comes before + // it can be shut down, the promise will reject. + let contentA11yShutdown = new Promise((resolve, reject) => + shutdownPromise(browser).then(flag => contentCanShutdown ? + resolve() : reject('Accessible service was shut down incorrectly'))); + // Remove a11y service reference in content and force garbage collection. + // This should not trigger shutdown since a11y was originally initialized by + // the main process. + loadFrameScripts(browser, `accService = null; Components.utils.forceGC();`); + + // Have some breathing room between a11y service shutdowns. + yield new Promise(resolve => executeSoon(resolve)); + + info('Removing a service in parent'); + // Now allow a11y service to shutdown in content. + contentCanShutdown = true; + // Remove the a11y service reference in the main process. + let parentA11yShutdown = shutdownPromise(); + accService = null; + ok(!accService, 'Service is removed in parent'); + // Force garbage collection that should trigger shutdown in both parent and + // content. + forceGC(); + yield Promise.all([parentA11yShutdown, contentA11yShutdown]); + + // Unsetting e10s related preferences. + yield unsetE10sPrefs(); + }); +}); diff --git a/accessible/tests/browser/browser_shutdown_remote_no_reference.js b/accessible/tests/browser/browser_shutdown_remote_no_reference.js new file mode 100644 index 000000000..b066c2592 --- /dev/null +++ b/accessible/tests/browser/browser_shutdown_remote_no_reference.js @@ -0,0 +1,48 @@ +/* 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'; + +add_task(function* () { + // Making sure that the e10s is enabled on Windows for testing. + yield setE10sPrefs(); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: `data:text/html, + <html> + <head> + <meta charset="utf-8"/> + <title>Accessibility Test</title> + </head> + <body></body> + </html>` + }, function*(browser) { + info('Creating a service in parent and waiting for service to be created ' + + 'in content'); + // Create a11y service in the main process. This will trigger creating of + // the a11y service in parent as well. + let parentA11yInit = initPromise(); + let contentA11yInit = initPromise(browser); + let accService = Cc['@mozilla.org/accessibilityService;1'].getService( + Ci.nsIAccessibilityService); + ok(accService, 'Service initialized in parent'); + yield Promise.all([parentA11yInit, contentA11yInit]); + + info('Removing a service in parent and waiting for service to be shut ' + + 'down in content'); + // Remove a11y service reference in the main process. + let parentA11yShutdown = shutdownPromise(); + let contentA11yShutdown = shutdownPromise(browser); + accService = null; + ok(!accService, 'Service is removed in parent'); + // Force garbage collection that should trigger shutdown in both main and + // content process. + forceGC(); + yield Promise.all([parentA11yShutdown, contentA11yShutdown]); + }); + + // Unsetting e10s related preferences. + yield unsetE10sPrefs(); +}); diff --git a/accessible/tests/browser/browser_shutdown_remote_only.js b/accessible/tests/browser/browser_shutdown_remote_only.js new file mode 100644 index 000000000..aab497678 --- /dev/null +++ b/accessible/tests/browser/browser_shutdown_remote_only.js @@ -0,0 +1,40 @@ +/* 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'; + +add_task(function* () { + // Making sure that the e10s is enabled on Windows for testing. + yield setE10sPrefs(); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: `data:text/html, + <html> + <head> + <meta charset="utf-8"/> + <title>Accessibility Test</title> + </head> + <body></body> + </html>` + }, function*(browser) { + info('Creating a service in content'); + // Create a11y service in the content process. + let a11yInit = initPromise(browser); + loadFrameScripts(browser, `let accService = Components.classes[ + '@mozilla.org/accessibilityService;1'].getService( + Components.interfaces.nsIAccessibilityService);`); + yield a11yInit; + + info('Removing a service in content'); + // Remove a11y service reference from the content process. + let a11yShutdown = shutdownPromise(browser); + // Force garbage collection that should trigger shutdown. + loadFrameScripts(browser, `accService = null; Components.utils.forceGC();`); + yield a11yShutdown; + + // Unsetting e10s related preferences. + yield unsetE10sPrefs(); + }); +}); diff --git a/accessible/tests/browser/browser_shutdown_remote_own_reference.js b/accessible/tests/browser/browser_shutdown_remote_own_reference.js new file mode 100644 index 000000000..429737a81 --- /dev/null +++ b/accessible/tests/browser/browser_shutdown_remote_own_reference.js @@ -0,0 +1,75 @@ +/* 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'; + +add_task(function* () { + // Making sure that the e10s is enabled on Windows for testing. + yield setE10sPrefs(); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: `data:text/html, + <html> + <head> + <meta charset="utf-8"/> + <title>Accessibility Test</title> + </head> + <body></body> + </html>` + }, function*(browser) { + info('Creating a service in parent and waiting for service to be created ' + + 'in content'); + // Create a11y service in the main process. This will trigger creating of + // the a11y service in parent as well. + let parentA11yInit = initPromise(); + let contentA11yInit = initPromise(browser); + let accService = Cc['@mozilla.org/accessibilityService;1'].getService( + Ci.nsIAccessibilityService); + ok(accService, 'Service initialized in parent'); + yield Promise.all([parentA11yInit, contentA11yInit]); + + info('Adding additional reference to accessibility service in content ' + + 'process'); + // Add a new reference to the a11y service inside the content process. + loadFrameScripts(browser, `let accService = Components.classes[ + '@mozilla.org/accessibilityService;1'].getService( + Components.interfaces.nsIAccessibilityService);`); + + info('Shutting down a service in parent and making sure the one in ' + + 'content stays alive'); + let contentCanShutdown = false; + let parentA11yShutdown = shutdownPromise(); + // This promise will resolve only if contentCanShutdown flag is set to true. + // If 'a11y-init-or-shutdown' event with '0' flag (in content) comes before + // it can be shut down, the promise will reject. + let contentA11yShutdown = new Promise((resolve, reject) => + shutdownPromise(browser).then(flag => contentCanShutdown ? + resolve() : reject('Accessible service was shut down incorrectly'))); + // Remove a11y service reference in the main process and force garbage + // collection. This should not trigger shutdown in content since a11y + // service is used by XPCOM. + accService = null; + ok(!accService, 'Service is removed in parent'); + // Force garbage collection that should not trigger shutdown because there + // is a reference in a content process. + forceGC(); + loadFrameScripts(browser, `Components.utils.forceGC();`); + yield parentA11yShutdown; + + // Have some breathing room between a11y service shutdowns. + yield new Promise(resolve => executeSoon(resolve)); + + info('Removing a service in content'); + // Now allow a11y service to shutdown in content. + contentCanShutdown = true; + // Remove last reference to a11y service in content and force garbage + // collection that should trigger shutdown. + loadFrameScripts(browser, `accService = null; Components.utils.forceGC();`); + yield contentA11yShutdown; + + // Unsetting e10s related preferences. + yield unsetE10sPrefs(); + }); +}); diff --git a/accessible/tests/browser/browser_shutdown_scope_lifecycle.js b/accessible/tests/browser/browser_shutdown_scope_lifecycle.js new file mode 100644 index 000000000..be9c63d46 --- /dev/null +++ b/accessible/tests/browser/browser_shutdown_scope_lifecycle.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/. */ + +'use strict'; + +add_task(function* () { + // Create a11y service inside of the function scope. Its reference should be + // released once the anonimous function is called. + let a11yInitThenShutdown = initPromise().then(shutdownPromise); + + (function() { + let accService = Cc['@mozilla.org/accessibilityService;1'].getService( + Ci.nsIAccessibilityService); + ok(accService, 'Service initialized'); + })(); + + // Force garbage collection that should trigger shutdown. + forceGC(); + yield a11yInitThenShutdown; +}); diff --git a/accessible/tests/browser/browser_shutdown_start_restart.js b/accessible/tests/browser/browser_shutdown_start_restart.js new file mode 100644 index 000000000..53bd4daee --- /dev/null +++ b/accessible/tests/browser/browser_shutdown_start_restart.js @@ -0,0 +1,41 @@ +/* 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'; + +add_task(function* () { + info('Creating a service'); + // Create a11y service. + let a11yInit = initPromise(); + let accService = Cc['@mozilla.org/accessibilityService;1'].getService( + Ci.nsIAccessibilityService); + yield a11yInit; + ok(accService, 'Service initialized'); + + info('Removing a service'); + // Remove the only reference to an a11y service. + let a11yShutdown = shutdownPromise(); + accService = null; + ok(!accService, 'Service is removed'); + // Force garbage collection that should trigger shutdown. + forceGC(); + yield a11yShutdown; + + info('Recreating a service'); + // Re-create a11y service. + a11yInit = initPromise(); + accService = Cc['@mozilla.org/accessibilityService;1'].getService( + Ci.nsIAccessibilityService); + yield a11yInit; + ok(accService, 'Service initialized again'); + + info('Removing a service again'); + // Remove the only reference to an a11y service again. + a11yShutdown = shutdownPromise(); + accService = null; + ok(!accService, 'Service is removed again'); + // Force garbage collection that should trigger shutdown. + forceGC(); + yield a11yShutdown; +}); diff --git a/accessible/tests/browser/e10s/browser.ini b/accessible/tests/browser/e10s/browser.ini new file mode 100644 index 000000000..e0ca44dde --- /dev/null +++ b/accessible/tests/browser/e10s/browser.ini @@ -0,0 +1,51 @@ +[DEFAULT] +skip-if = (e10s && os == 'win') # Bug 1269369: Document loaded event does not fire in Windows +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 + doc_treeupdate_whitespace.html + !/accessible/tests/browser/shared-head.js + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + !/accessible/tests/mochitest/moz.png + +# Caching tests +[browser_caching_attributes.js] +[browser_caching_description.js] +[browser_caching_name.js] +[browser_caching_relations.js] +[browser_caching_states.js] +[browser_caching_value.js] + +# Events tests +[browser_events_caretmove.js] +[browser_events_hide.js] +[browser_events_show.js] +[browser_events_statechange.js] +[browser_events_textchange.js] + +# Tree update tests +[browser_treeupdate_ariadialog.js] +[browser_treeupdate_ariaowns.js] +[browser_treeupdate_canvas.js] +[browser_treeupdate_cssoverflow.js] +[browser_treeupdate_doc.js] +[browser_treeupdate_gencontent.js] +[browser_treeupdate_hidden.js] +[browser_treeupdate_imagemap.js] +skip-if = e10s # Bug 1318569 +[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] +skip-if = true # Failing due to incorrect index of test container children on document load. diff --git a/accessible/tests/browser/e10s/browser_caching_attributes.js b/accessible/tests/browser/e10s/browser_caching_attributes.js new file mode 100644 index 000000000..449ca9d91 --- /dev/null +++ b/accessible/tests/browser/e10s/browser_caching_attributes.js @@ -0,0 +1,117 @@ +/* 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_FOCUS */ + +loadScripts({ name: 'attributes.js', dir: MOCHITESTS_DIR }); + +/** + * Default textbox accessible attributes. + */ +const defaultAttributes = { + 'margin-top': '0px', + 'margin-right': '0px', + 'margin-bottom': '0px', + 'margin-left': '0px', + 'text-align': 'start', + 'text-indent': '0px', + 'id': 'textbox', + 'tag': 'input', + 'display': 'inline' +}; + +/** + * Test data has the format of: + * { + * desc {String} description for better logging + * expected {Object} expected attributes for given accessibles + * unexpected {Object} unexpected attributes for given accessibles + * + * action {?Function*} an optional action that yields a change in + * attributes + * attrs {?Array} an optional list of attributes to update + * waitFor {?Number} an optional event to wait for + * } + */ +const attributesTests = [{ + desc: 'Initiall accessible attributes', + expected: defaultAttributes, + unexpected: { + 'line-number': '1', + 'explicit-name': 'true', + 'container-live': 'polite', + 'live': 'polite' + } +}, { + desc: '@line-number attribute is present when textbox is focused', + action: function*(browser) { + yield invokeFocus(browser, 'textbox'); + }, + waitFor: EVENT_FOCUS, + expected: Object.assign({}, defaultAttributes, { 'line-number': '1' }), + unexpected: { + 'explicit-name': 'true', + 'container-live': 'polite', + 'live': 'polite' + } +}, { + desc: '@aria-live sets container-live and live attributes', + attrs: [{ + attr: 'aria-live', + value: 'polite' + }], + expected: Object.assign({}, defaultAttributes, { + 'line-number': '1', + 'container-live': 'polite', + 'live': 'polite' + }), + unexpected: { + 'explicit-name': 'true' + } +}, { + desc: '@title attribute sets explicit-name attribute to true', + attrs: [{ + attr: 'title', + value: 'textbox' + }], + expected: Object.assign({}, defaultAttributes, { + 'line-number': '1', + 'explicit-name': 'true', + 'container-live': 'polite', + 'live': 'polite' + }), + unexpected: {} +}]; + +/** + * Test caching of accessible object attributes + */ +addAccessibleTask(` + <input id="textbox" value="hello">`, + function* (browser, accDoc) { + let textbox = findAccessibleChildByID(accDoc, 'textbox'); + for (let { desc, action, attrs, expected, waitFor, unexpected } of attributesTests) { + info(desc); + let onUpdate; + + if (waitFor) { + onUpdate = waitForEvent(waitFor, 'textbox'); + } + + if (action) { + yield action(browser); + } else if (attrs) { + for (let { attr, value } of attrs) { + yield invokeSetAttribute(browser, 'textbox', attr, value); + } + } + + yield onUpdate; + testAttrs(textbox, expected); + testAbsentAttrs(textbox, unexpected); + } + } +); diff --git a/accessible/tests/browser/e10s/browser_caching_description.js b/accessible/tests/browser/e10s/browser_caching_description.js new file mode 100644 index 000000000..18ee58bd0 --- /dev/null +++ b/accessible/tests/browser/e10s/browser_caching_description.js @@ -0,0 +1,164 @@ +/* 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_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_REORDER */ + +loadScripts({ name: 'name.js', dir: MOCHITESTS_DIR }); + +/** + * Test data has the format of: + * { + * desc {String} description for better logging + * expected {String} expected description value for a given accessible + * attrs {?Array} an optional list of attributes to update + * waitFor {?Array} an optional list of accessible events to wait for when + * attributes are updated + * } + */ +const tests = [{ + desc: 'No description when there are no @alt, @title and @aria-describedby', + expected: '' +}, { + desc: 'Description from @aria-describedby attribute', + attrs: [{ + attr: 'aria-describedby', + value: 'description' + }], + waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }], + expected: 'aria description' +}, { + desc: 'No description from @aria-describedby since it is the same as the ' + + '@alt attribute which is used as the name', + attrs: [{ + attr: 'alt', + value: 'aria description' + }], + waitFor: [{ eventType: EVENT_REORDER, id: 'body' }], + expected: '' +}, { + desc: 'Description from @aria-describedby attribute when @alt and ' + + '@aria-describedby are not the same', + attrs: [{ + attr: 'aria-describedby', + value: 'description2' + }], + waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }], + expected: 'another description' +}, { + desc: 'Description from @aria-describedby attribute when @title (used for ' + + 'name) and @aria-describedby are not the same', + attrs: [{ + attr: 'alt' + }, { + attr: 'title', + value: 'title' + }], + waitFor: [{ eventType: EVENT_REORDER, id: 'body' }], + expected: 'another description' +}, { + desc: 'No description from @aria-describedby since it is the same as the ' + + '@title attribute which is used as the name', + attrs: [{ + attr: 'title', + value: 'another description' + }], + waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }], + expected: '' +}, { + desc: 'No description with only @title attribute which is used as the name', + attrs: [{ + attr: 'aria-describedby' + }], + waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }], + expected: '' +}, { + desc: 'Description from @title attribute when @alt and @atitle are not the ' + + 'same', + attrs: [{ + attr: 'alt', + value: 'aria description' + }], + waitFor: [{ eventType: EVENT_REORDER, id: 'body' }], + expected: 'another description' +}, { + desc: 'No description from @title since it is the same as the @alt ' + + 'attribute which is used as the name', + attrs: [{ + attr: 'alt', + value: 'another description' + }], + waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }], + expected: '' +}, { + desc: 'No description from @aria-describedby since it is the same as the ' + + '@alt (used for name) and @title attributes', + attrs: [{ + attr: 'aria-describedby', + value: 'description2' + }], + waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }], + expected: '' +}, { + desc: 'Description from @aria-describedby attribute when it is different ' + + 'from @alt (used for name) and @title attributes', + attrs: [{ + attr: 'aria-describedby', + value: 'description' + }], + waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }], + expected: 'aria description' +}, { + desc: 'No description from @aria-describedby since it is the same as the ' + + '@alt attribute (used for name) but different from title', + attrs: [{ + attr: 'alt', + value: 'aria description' + }], + waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }], + expected: '' +}, { + desc: 'Description from @aria-describedby attribute when @alt (used for ' + + 'name) and @aria-describedby are not the same but @title and ' + + 'aria-describedby are', + attrs: [{ + attr: 'aria-describedby', + value: 'description2' + }], + waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }], + expected: 'another description' +}]; + +/** + * Test caching of accessible object description + */ +addAccessibleTask(` + <p id="description">aria description</p> + <p id="description2">another description</p> + <img id="image" />`, + function*(browser, accDoc) { + let imgAcc = findAccessibleChildByID(accDoc, 'image'); + + for (let { desc, waitFor, attrs, expected } of tests) { + info(desc); + let onUpdate; + if (waitFor) { + onUpdate = waitForMultipleEvents(waitFor); + } + if (attrs) { + for (let { attr, value } of attrs) { + yield invokeSetAttribute(browser, 'image', attr, value); + } + } + yield onUpdate; + // When attribute change (alt) triggers reorder event, accessible will + // become defunct. + if (isDefunct(imgAcc)) { + imgAcc = findAccessibleChildByID(accDoc, 'image'); + } + testDescr(imgAcc, expected); + } + } +); diff --git a/accessible/tests/browser/e10s/browser_caching_name.js b/accessible/tests/browser/e10s/browser_caching_name.js new file mode 100644 index 000000000..08e635014 --- /dev/null +++ b/accessible/tests/browser/e10s/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: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <label for="btn">test4</label> + <button id="btn" + aria-label="test1" + aria-labelledby="l1 l2" + title="test5">press me</button>`, + expected: ['test2 test3', 'test1', 'test4', 'press me', 'test5'] +}, { + id: 'btn', + ruleset: 'HTMLInputButton', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <label for="btn">test4</label> + <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"/>`, + expected: ['test2 test3', 'test1', 'test4', 'name from value', + 'name from title'] +}, { + id: 'btn-submit', + ruleset: 'HTMLInputSubmit', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <label for="btn-submit">test4</label> + <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"/>`, + expected: ['test2 test3', 'test1', 'test4', 'name from value'] +}, { + id: 'btn-reset', + ruleset: 'HTMLInputReset', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <label for="btn-reset">test4</label> + <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"/>`, + expected: ['test2 test3', 'test1', 'test4', 'name from value'] +}, { + id: 'btn-image', + ruleset: 'HTMLInputImage', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <label for="btn-image">test4</label> + <input id="btn-image" + type="image" + aria-label="test1" + aria-labelledby="l1 l2" + alt="name from alt" + value="name from value" + src="http://example.com/a11y/accessible/tests/mochitest/moz.png" + data="no name from data" + title="name from title"/>`, + expected: ['test2 test3', 'test1', 'test4', 'name from alt', + 'name from value', 'name from title'] +}, { + id: 'btn-image', + ruleset: 'HTMLInputImageNoValidSrc', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <label for="btn-image">test4</label> + <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"/>`, + expected: ['test2 test3', 'test1', 'test4', 'name from alt', + 'name from value'] +}, { + id: 'opt', + ruleset: 'HTMLOption', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <select> + <option id="opt" + aria-label="test1" + aria-labelledby="l1 l2" + label="test4" + title="test5">option1</option> + <option>option2</option> + </select>`, + expected: ['test2 test3', 'test1', 'test4', 'option1', 'test5'] +}, { + id: 'img', + ruleset: 'HTMLImg', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <img id="img" + aria-label="Logo of Mozilla" + aria-labelledby="l1 l2" + alt="Mozilla logo" + title="This is a logo" + src="http://example.com/a11y/accessible/tests/mochitest/moz.png"/>`, + expected: ['test2 test3', 'Logo of Mozilla', 'Mozilla logo', 'This is a logo'] +}, { + id: 'imgemptyalt', + ruleset: 'HTMLImgEmptyAlt', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <img id="imgemptyalt" + aria-label="Logo of Mozilla" + aria-labelledby="l1 l2" + title="This is a logo" + alt="" + src="http://example.com/a11y/accessible/tests/mochitest/moz.png"/>`, + expected: ['test2 test3', 'Logo of Mozilla', 'This is a logo', ''] +}, { + id: 'tc', + ruleset: 'HTMLElm', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <label for="tc">test4</label> + <table> + <tr> + <td id="tc" + aria-label="test1" + aria-labelledby="l1 l2" + title="test5"> + <p>This is a paragraph</p> + <a href="#">This is a link</a> + <ul> + <li>This is a list</li> + </ul> + </td> + </tr> + </table>`, + expected: ['test2 test3', 'test1', 'test5'] +}, { + id: 'gc', + ruleset: 'HTMLARIAGridCell', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <label for="gc">test4</label> + <table> + <tr> + <td id="gc" + role="gridcell" + aria-label="test1" + aria-labelledby="l1 l2" + title="This is a paragraph This is a link This is a list"> + <p>This is a paragraph</p> + <a href="#">This is a link</a> + <ul> + <li>Listitem1</li> + <li>Listitem2</li> + </ul> + </td> + </tr> + </table>`, + 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: ` + <span id="l1">lby_tst6_1</span> + <span id="l2">lby_tst6_2</span> + <label for="t">label_tst6</label> + <table id="t" + aria-label="arialabel_tst6" + aria-labelledby="l1 l2" + summary="summary_tst6" + title="title_tst6"> + <caption>caption_tst6</caption> + <tr> + <td>cell1</td> + <td>cell2</td> + </tr> + </table>`, + expected: ['lby_tst6_1 lby_tst6_2', 'arialabel_tst6', 'caption_tst6', + 'summary_tst6', 'title_tst6'] +}, { + id: 'btn', + ruleset: 'CSSContent', + markup: ` + <style> + button::before { + content: "do not "; + } + </style> + <button id="btn">press me</button>`, + expected: ['do not press me', 'press me'] +}, { + // TODO: uncomment when Bug-1256382 is resoved. + // id: 'li', + // ruleset: 'CSSContent', + // markup: ` + // <style> + // ul { + // list-style-type: decimal; + // } + // </style> + // <ul id="ul"> + // <li id="li">Listitem</li> + // </ul>`, + // expected: ['1. Listitem', `${String.fromCharCode(0x2022)} Listitem`] +// }, { + id: 'a', + ruleset: 'HTMLLink', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <a id="a" + aria-label="test1" + aria-labelledby="l1 l2" + title="test4">test5</a>`, + expected: ['test2 test3', 'test1', 'test5', 'test4'] +}, { + id: 'a-img', + ruleset: 'HTMLLinkImage', + markup: ` + <span id="l1">test2</span> + <span id="l2">test3</span> + <a id="a-img" + aria-label="test1" + aria-labelledby="l1 l2" + title="test4"><img alt="test5"/></a>`, + 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/e10s/browser_caching_relations.js b/accessible/tests/browser/e10s/browser_caching_relations.js new file mode 100644 index 000000000..772aee96a --- /dev/null +++ b/accessible/tests/browser/e10s/browser_caching_relations.js @@ -0,0 +1,86 @@ +/* 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 RELATION_LABELLED_BY, RELATION_LABEL_FOR, RELATION_DESCRIBED_BY, + RELATION_DESCRIPTION_FOR, RELATION_CONTROLLER_FOR, + RELATION_CONTROLLED_BY, RELATION_FLOWS_TO, RELATION_FLOWS_FROM */ + +loadScripts({ name: 'relations.js', dir: MOCHITESTS_DIR }); + +/** + * A test specification that has the following format: + * [ + * attr relevant aria attribute + * hostRelation corresponding host relation type + * dependantRelation corresponding dependant relation type + * ] + */ +const attrRelationsSpec = [ + ['aria-labelledby', RELATION_LABELLED_BY, RELATION_LABEL_FOR], + ['aria-describedby', RELATION_DESCRIBED_BY, RELATION_DESCRIPTION_FOR], + ['aria-controls', RELATION_CONTROLLER_FOR, RELATION_CONTROLLED_BY], + ['aria-flowto', RELATION_FLOWS_TO, RELATION_FLOWS_FROM] +]; + +function* testRelated(browser, accDoc, attr, hostRelation, dependantRelation) { + let host = findAccessibleChildByID(accDoc, 'host'); + let dependant1 = findAccessibleChildByID(accDoc, 'dependant1'); + let dependant2 = findAccessibleChildByID(accDoc, 'dependant2'); + + /** + * Test data has the format of: + * { + * desc {String} description for better logging + * attrs {?Array} an optional list of attributes to update + * expected {Array} expected relation values for dependant1, dependant2 + * and host respectively. + * } + */ + const tests = [{ + desc: 'No attribute', + expected: [ null, null, null ] + }, { + desc: 'Set attribute', + attrs: [{ key: attr, value: 'dependant1' }], + expected: [ host, null, dependant1 ] + }, { + desc: 'Change attribute', + attrs: [{ key: attr, value: 'dependant2' }], + expected: [ null, host, dependant2 ] + }, { + desc: 'Remove attribute', + attrs: [{ key: attr }], + expected: [ null, null, null ] + }]; + + for (let { desc, attrs, expected } of tests) { + info(desc); + + if (attrs) { + for (let { key, value } of attrs) { + yield invokeSetAttribute(browser, 'host', key, value); + } + } + + testRelation(dependant1, dependantRelation, expected[0]); + testRelation(dependant2, dependantRelation, expected[1]); + testRelation(host, hostRelation, expected[2]); + } +} + +/** + * Test caching of relations between accessible objects. + */ +addAccessibleTask(` + <div id="dependant1">label</div> + <div id="dependant2">label2</div> + <div role="checkbox" id="host"></div>`, + function* (browser, accDoc) { + for (let spec of attrRelationsSpec) { + yield testRelated(browser, accDoc, ...spec); + } + } +); diff --git a/accessible/tests/browser/e10s/browser_caching_states.js b/accessible/tests/browser/e10s/browser_caching_states.js new file mode 100644 index 000000000..69e4931ea --- /dev/null +++ b/accessible/tests/browser/e10s/browser_caching_states.js @@ -0,0 +1,120 @@ +/* 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_STATE_CHANGE, STATE_CHECKED, STATE_BUSY, STATE_REQUIRED, + STATE_INVALID, EXT_STATE_ENABLED */ + +loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR }, + { name: 'states.js', dir: MOCHITESTS_DIR }); + +/** + * Test data has the format of: + * { + * desc {String} description for better logging + * expected {Array} expected states for a given accessible that have the + * following format: + * [ + * expected state, + * expected extra state, + * absent state, + * absent extra state + * ] + * attrs {?Array} an optional list of attributes to update + * } + */ + +// State caching tests for attribute changes +const attributeTests = [{ + desc: 'Checkbox with @checked attribute set to true should have checked ' + + 'state', + attrs: [{ + attr: 'checked', + value: 'true' + }], + expected: [STATE_CHECKED, 0] +}, { + desc: 'Checkbox with no @checked attribute should not have checked state', + attrs: [{ + attr: 'checked' + }], + expected: [0, 0, STATE_CHECKED] +}]; + +// State caching tests for ARIA changes +const ariaTests = [{ + desc: 'File input has busy state when @aria-busy attribute is set to true', + attrs: [{ + attr: 'aria-busy', + value: 'true' + }], + expected: [STATE_BUSY, 0, STATE_REQUIRED | STATE_INVALID] +}, { + desc: 'File input has required state when @aria-required attribute is set ' + + 'to true', + attrs: [{ + attr: 'aria-required', + value: 'true' + }], + expected: [STATE_REQUIRED, 0, STATE_INVALID] +}, { + desc: 'File input has invalid state when @aria-invalid attribute is set to ' + + 'true', + attrs: [{ + attr: 'aria-invalid', + value: 'true' + }], + expected: [STATE_INVALID, 0] +}]; + +// Extra state caching tests +const extraStateTests = [{ + desc: 'Input has no extra enabled state when aria and native disabled ' + + 'attributes are set at once', + attrs: [{ + attr: 'aria-disabled', + value: 'true' + }, { + attr: 'disabled', + value: 'true' + }], + expected: [0, 0, 0, EXT_STATE_ENABLED] +}, { + desc: 'Input has an extra enabled state when aria and native disabled ' + + 'attributes are unset at once', + attrs: [{ + attr: 'aria-disabled' + }, { + attr: 'disabled' + }], + expected: [0, EXT_STATE_ENABLED] +}]; + +function* runStateTests(browser, accDoc, id, tests) { + let acc = findAccessibleChildByID(accDoc, id); + for (let { desc, attrs, expected } of tests) { + info(desc); + let onUpdate = waitForEvent(EVENT_STATE_CHANGE, id); + for (let { attr, value } of attrs) { + yield invokeSetAttribute(browser, id, attr, value); + } + yield onUpdate; + testStates(acc, ...expected); + } +} + +/** + * Test caching of accessible object states + */ +addAccessibleTask(` + <input id="checkbox" type="checkbox"> + <input id="file" type="file"> + <input id="text">`, + function* (browser, accDoc) { + yield runStateTests(browser, accDoc, 'checkbox', attributeTests); + yield runStateTests(browser, accDoc, 'file', ariaTests); + yield runStateTests(browser, accDoc, 'text', extraStateTests); + } +); diff --git a/accessible/tests/browser/e10s/browser_caching_value.js b/accessible/tests/browser/e10s/browser_caching_value.js new file mode 100644 index 000000000..2669cbfab --- /dev/null +++ b/accessible/tests/browser/e10s/browser_caching_value.js @@ -0,0 +1,155 @@ +/* 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 nsIAccessibleValue, EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE */ + +loadScripts({ name: 'value.js', dir: MOCHITESTS_DIR }); + +/** + * Test data has the format of: + * { + * desc {String} description for better logging + * id {String} given accessible DOMNode ID + * expected {String} expected value for a given accessible + * action {?Function*} an optional action that yields a value change + * attrs {?Array} an optional list of attributes to update + * waitFor {?Number} an optional value change event to wait for + * } + */ +const valueTests = [{ + desc: 'Initially value is set to 1st element of select', + id: 'select', + expected: '1st' +}, { + desc: 'Value should update to 3rd when 3 is pressed', + id: 'select', + action: function*(browser) { + yield invokeFocus(browser, 'select'); + yield BrowserTestUtils.synthesizeKey('3', {}, browser); + }, + waitFor: EVENT_TEXT_VALUE_CHANGE, + expected: '3rd' +}, { + desc: 'Initially value is set to @aria-valuenow for slider', + id: 'slider', + expected: ['5', 5, 0, 7, 0] +}, { + desc: 'Value should change when @aria-valuenow is updated', + id: 'slider', + attrs: [{ + attr: 'aria-valuenow', + value: '6' + }], + waitFor: EVENT_VALUE_CHANGE, + expected: ['6', 6, 0, 7, 0] +}, { + desc: 'Value should change when @aria-valuetext is set', + id: 'slider', + attrs: [{ + attr: 'aria-valuetext', + value: 'plain' + }], + waitFor: EVENT_TEXT_VALUE_CHANGE, + expected: ['plain', 6, 0, 7, 0] +}, { + desc: 'Value should change when @aria-valuetext is updated', + id: 'slider', + attrs: [{ + attr: 'aria-valuetext', + value: 'hey!' + }], + waitFor: EVENT_TEXT_VALUE_CHANGE, + expected: ['hey!', 6, 0, 7, 0] +}, { + desc: 'Value should change to @aria-valuetext when @aria-valuenow is removed', + id: 'slider', + attrs: [{ + attr: 'aria-valuenow' + }], + expected: ['hey!', 0, 0, 7, 0] +}, { + desc: 'Initially value is not set for combobox', + id: 'combobox', + expected: '' +}, { + desc: 'Value should change when @value attribute is updated', + id: 'combobox', + attrs: [{ + attr: 'value', + value: 'hello' + }], + waitFor: EVENT_TEXT_VALUE_CHANGE, + expected: 'hello' +}, { + desc: 'Initially value corresponds to @value attribute for progress', + id: 'progress', + expected: '22%' +}, { + desc: 'Value should change when @value attribute is updated', + id: 'progress', + attrs: [{ + attr: 'value', + value: '50' + }], + waitFor: EVENT_VALUE_CHANGE, + expected: '50%' +}, { + desc: 'Initially value corresponds to @value attribute for range', + id: 'range', + expected: '6' +}, { + desc: 'Value should change when slider is moved', + id: 'range', + action: function*(browser) { + yield invokeFocus(browser, 'range'); + yield BrowserTestUtils.synthesizeKey('VK_LEFT', {}, browser); + }, + waitFor: EVENT_VALUE_CHANGE, + expected: '5' +}]; + +/** + * Test caching of accessible object values + */ +addAccessibleTask(` + <div id="slider" role="slider" aria-valuenow="5" + aria-valuemin="0" aria-valuemax="7">slider</div> + <select id="select"> + <option>1st</option> + <option>2nd</option> + <option>3rd</option> + </select> + <input id="combobox" role="combobox" aria-autocomplete="inline"> + <progress id="progress" value="22" max="100"></progress> + <input type="range" id="range" min="0" max="10" value="6">`, + function* (browser, accDoc) { + for (let { desc, id, action, attrs, expected, waitFor } of valueTests) { + info(desc); + let acc = findAccessibleChildByID(accDoc, id); + let onUpdate; + + if (waitFor) { + onUpdate = waitForEvent(waitFor, id); + } + + if (action) { + yield action(browser); + } else if (attrs) { + for (let { attr, value } of attrs) { + yield invokeSetAttribute(browser, id, attr, value); + } + } + + yield onUpdate; + if (Array.isArray(expected)) { + acc.QueryInterface(nsIAccessibleValue); + testValue(acc, ...expected); + } else { + is(acc.value, expected, `Correct value for ${prettyName(acc)}`); + } + } + } +); diff --git a/accessible/tests/browser/e10s/browser_events_caretmove.js b/accessible/tests/browser/e10s/browser_events_caretmove.js new file mode 100644 index 000000000..506945f30 --- /dev/null +++ b/accessible/tests/browser/e10s/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('<input id="textbox" value="hello"/>', 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/e10s/browser_events_hide.js b/accessible/tests/browser/e10s/browser_events_hide.js new file mode 100644 index 000000000..bb9ee3961 --- /dev/null +++ b/accessible/tests/browser/e10s/browser_events_hide.js @@ -0,0 +1,35 @@ +/* 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(` + <div id="parent"> + <div id="previous"></div> + <div id="to-hide"></div> + <div id="next"></div> + </div>`, + function*(browser, accDoc) { + let acc = findAccessibleChildByID(accDoc, 'to-hide'); + let onHide = waitForEvent(EVENT_HIDE, acc); + yield invokeSetStyle(browser, 'to-hide', '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/e10s/browser_events_show.js b/accessible/tests/browser/e10s/browser_events_show.js new file mode 100644 index 000000000..5003c14f7 --- /dev/null +++ b/accessible/tests/browser/e10s/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('<div id="div" style="visibility: hidden;"></div>', + function*(browser) { + let onShow = waitForEvent(EVENT_SHOW, 'div'); + yield invokeSetStyle(browser, 'div', 'visibility', 'visible'); + yield onShow; + }); diff --git a/accessible/tests/browser/e10s/browser_events_statechange.js b/accessible/tests/browser/e10s/browser_events_statechange.js new file mode 100644 index 000000000..7f353efa7 --- /dev/null +++ b/accessible/tests/browser/e10s/browser_events_statechange.js @@ -0,0 +1,62 @@ +/* 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, + <html> + <head> + <meta charset='utf-8'/> + <title>Inner Iframe</title> + </head> + <body id='iframe'></body> + </html>`; + +/** + * Test state change event and its interface: + * - state + * - isExtraState + * - isEnabled + */ +addAccessibleTask(` + <iframe id="iframe" src="${iframeSrc}"></iframe> + <input id="checkbox" type="checkbox" />`, 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/e10s/browser_events_textchange.js b/accessible/tests/browser/e10s/browser_events_textchange.js new file mode 100644 index 000000000..a1aed52d1 --- /dev/null +++ b/accessible/tests/browser/e10s/browser_events_textchange.js @@ -0,0 +1,74 @@ +/* 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], ([contentId, contentValue]) => { + content.document.getElementById(contentId).firstChild.textContent = + contentValue; + }); + 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], ([contentId, contentStart, contentEnd]) => { + let el = content.document.getElementById(contentId); + el.focus(); + el.setSelectionRange(contentStart, contentEnd); + }); + 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(` + <p id="p">abc</p> + <input id="input" value="input" />`, 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/e10s/browser_treeupdate_ariadialog.js b/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js new file mode 100644 index 000000000..a9544bc5c --- /dev/null +++ b/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.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_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/e10s/browser_treeupdate_ariaowns.js b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js new file mode 100644 index 000000000..998da32a1 --- /dev/null +++ b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js @@ -0,0 +1,318 @@ +/* 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, contentId => { + let div = content.document.createElement('div'); + div.setAttribute('id', 't1_child3'); + div.setAttribute('role', 'radio'); + content.document.getElementById(contentId).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, contentId => { + content.document.getElementById(contentId).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/e10s/browser_treeupdate_canvas.js b/accessible/tests/browser/e10s/browser_treeupdate_canvas.js new file mode 100644 index 000000000..e4b3b70f7 --- /dev/null +++ b/accessible/tests/browser/e10s/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(` + <canvas id="canvas"> + <div id="dialog" role="dialog" style="display: none;"></div> + </canvas>`, 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/e10s/browser_treeupdate_cssoverflow.js b/accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js new file mode 100644 index 000000000..d8b217380 --- /dev/null +++ b/accessible/tests/browser/e10s/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(` + <div id="container"><div id="scrollarea" style="overflow:auto;"><input> + </div></div> + <div id="container2"><div id="scrollarea2" style="overflow:hidden;"> + </div></div>`, 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/e10s/browser_treeupdate_doc.js b/accessible/tests/browser/e10s/browser_treeupdate_doc.js new file mode 100644 index 000000000..ccb1d1566 --- /dev/null +++ b/accessible/tests/browser/e10s/browser_treeupdate_doc.js @@ -0,0 +1,312 @@ +/* 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, + <html> + <head> + <meta charset='utf-8'/> + <title>Inner Iframe</title> + </head> + <body id='inner-iframe'></body> + </html>`; + +addAccessibleTask(` + <iframe id="iframe" src="${iframeSrc}"></iframe>`, 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 reorderEventPromise = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, contentId => { + 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 = contentId; + newBodyNode.appendChild(newTextNode); + newHTMLNode.appendChild(newBodyNode); + docNode.replaceChild(newHTMLNode, docNode.documentElement); + }); + yield reorderEventPromise; + + tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'New Wave' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Replace iframe HTML element ========================== */ + reorderEventPromise = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, contentId => { + 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('<body id="${contentId}">hello</body>'); + document.close();`; + docNode.body.appendChild(script); + }); + yield reorderEventPromise; + + tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'hello' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Replace iframe body ================================== */ + reorderEventPromise = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, contentId => { + let docNode = content.document.getElementById('iframe').contentDocument; + let newBodyNode = docNode.createElement('body'); + let newTextNode = docNode.createTextNode('New Hello'); + newBodyNode.id = contentId; + newBodyNode.appendChild(newTextNode); + newBodyNode.setAttribute('role', 'button'); + docNode.documentElement.replaceChild(newBodyNode, docNode.body); + }); + yield reorderEventPromise; + + tree = { + role: ROLE_PUSHBUTTON, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'New Hello' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Open iframe document ================================= */ + reorderEventPromise = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, contentId => { + // 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('<body id="${contentId}"></body>');`; + docNode.body.appendChild(script); + }); + yield reorderEventPromise; + + tree = { + role: ROLE_DOCUMENT, + children: [ ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Close iframe document ================================ */ + reorderEventPromise = 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 reorderEventPromise; + + tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'Works?' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Remove HTML from iframe document ===================== */ + reorderEventPromise = waitForEvent(EVENT_REORDER, iframe); + yield ContentTask.spawn(browser, {}, () => { + // Remove HTML element. + let docNode = content.document.getElementById('iframe').contentDocument; + docNode.removeChild(docNode.firstChild); + }); + let event = yield reorderEventPromise; + + ok(event.accessible instanceof nsIAccessibleDocument, + 'Reorder should happen on the document'); + tree = { + role: ROLE_DOCUMENT, + children: [ ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Insert HTML to iframe document ======================= */ + reorderEventPromise = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, contentId => { + // 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 = contentId; + html.appendChild(body); + docNode.appendChild(html); + }); + yield reorderEventPromise; + + tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'Haha' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Remove body from iframe document ===================== */ + reorderEventPromise = waitForEvent(EVENT_REORDER, iframe); + yield ContentTask.spawn(browser, {}, () => { + // Remove body element. + let docNode = content.document.getElementById('iframe').contentDocument; + docNode.documentElement.removeChild(docNode.body); + }); + event = yield reorderEventPromise; + + 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 */ + reorderEventPromise = waitForEvent(EVENT_REORDER, iframe); + 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 reorderEventPromise; + + ok(event.accessible instanceof nsIAccessibleDocument, + 'Reorder should happen on the document'); + tree = { + DOCUMENT: [ + { ENTRY: [ ] } + ] + }; + testAccessibleTree(iframe, tree); + + reorderEventPromise = waitForEvent(EVENT_REORDER, iframe); + yield ContentTask.spawn(browser, {}, () => { + let docEl = + content.document.getElementById('iframe').contentDocument.documentElement; + // Remove aftermath of this test before next test starts. + docEl.removeChild(docEl.firstChild); + }); + // Make sure reorder event was fired and that the input was removed. + yield reorderEventPromise; + tree = { + role: ROLE_DOCUMENT, + children: [ ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Insert body to iframe document ======================= */ + reorderEventPromise = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, contentId => { + // 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 = contentId; + docNode.documentElement.appendChild(body); + }); + yield reorderEventPromise; + + tree = { + role: ROLE_DOCUMENT, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'Yo ho ho i butylka roma!' + } + ] + }; + testAccessibleTree(iframe, tree); + + /* ================= Change source ======================================== */ + reorderEventPromise = waitForEvent(EVENT_REORDER, 'iframe'); + yield invokeSetAttribute(browser, 'iframe', 'src', + `data:text/html,<html><body id="${id}"><input></body></html>`); + event = yield reorderEventPromise; + + tree = { + INTERNAL_FRAME: [ + { DOCUMENT: [ + { ENTRY: [ ] } + ] } + ] + }; + testAccessibleTree(event.accessible, tree); + iframe = findAccessibleChildByID(event.accessible, id); + + /* ================= Replace iframe body on ARIA role body ================ */ + reorderEventPromise = waitForEvent(EVENT_REORDER, id); + yield ContentTask.spawn(browser, id, contentId => { + 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 = contentId; + docNode.documentElement.replaceChild(newBodyNode, docNode.body); + }); + yield reorderEventPromise; + + tree = { + role: ROLE_PUSHBUTTON, + children: [ + { + role: ROLE_TEXT_LEAF, + name: 'New Hello' + } + ] + }; + testAccessibleTree(iframe, tree); +}); diff --git a/accessible/tests/browser/e10s/browser_treeupdate_gencontent.js b/accessible/tests/browser/e10s/browser_treeupdate_gencontent.js new file mode 100644 index 000000000..126419288 --- /dev/null +++ b/accessible/tests/browser/e10s/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(` + <style> + .gentext:before { + content: "START" + } + .gentext:after { + content: "END" + } + </style> + <div id="container1"></div> + <div id="container2"><div id="container2_child">text</div></div>`, + 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/e10s/browser_treeupdate_hidden.js b/accessible/tests/browser/e10s/browser_treeupdate_hidden.js new file mode 100644 index 000000000..00369ec05 --- /dev/null +++ b/accessible/tests/browser/e10s/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('<div id="container"><input id="child"></div>', + 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/e10s/browser_treeupdate_imagemap.js b/accessible/tests/browser/e10s/browser_treeupdate_imagemap.js new file mode 100644 index 000000000..d299c0acb --- /dev/null +++ b/accessible/tests/browser/e10s/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, contentId => { + 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(contentId).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/e10s/browser_treeupdate_list.js b/accessible/tests/browser/e10s/browser_treeupdate_list.js new file mode 100644 index 000000000..023adf8e3 --- /dev/null +++ b/accessible/tests/browser/e10s/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(` + <ul id="ul"> + <li id="li">item1</li> + </ul>`, 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/e10s/browser_treeupdate_list_editabledoc.js b/accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js new file mode 100644 index 000000000..7b01af87a --- /dev/null +++ b/accessible/tests/browser/e10s/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('<ol id="list"></ol>', 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/e10s/browser_treeupdate_listener.js b/accessible/tests/browser/e10s/browser_treeupdate_listener.js new file mode 100644 index 000000000..7b7880312 --- /dev/null +++ b/accessible/tests/browser/e10s/browser_treeupdate_listener.js @@ -0,0 +1,29 @@ +/* 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('<span id="parent"><span id="child"></span></span>', + 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); + }); diff --git a/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js b/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js new file mode 100644 index 000000000..45001afaa --- /dev/null +++ b/accessible/tests/browser/e10s/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('<select id="select"></select>', 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 contentSelect = 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); + } + contentSelect.add(optGroup, null); + + for (let i = 0; i < 2; i++) { + let opt = doc.createElement('option'); + contentSelect.add(opt, null); + } + contentSelect.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 contentSelect = content.document.getElementById('select'); + contentSelect.removeChild(contentSelect.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 contentSelect = content.document.getElementById('select'); + while (contentSelect.length) { + contentSelect.remove(0); + } + }); + yield onEvent; + + tree = { + COMBOBOX: [ { + COMBOBOX_LIST: [ ] + } ] + }; + testAccessibleTree(select, tree); +}); diff --git a/accessible/tests/browser/e10s/browser_treeupdate_removal.js b/accessible/tests/browser/e10s/browser_treeupdate_removal.js new file mode 100644 index 000000000..9892bbcd6 --- /dev/null +++ b/accessible/tests/browser/e10s/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/e10s/browser_treeupdate_table.js b/accessible/tests/browser/e10s/browser_treeupdate_table.js new file mode 100644 index 000000000..9609f51ac --- /dev/null +++ b/accessible/tests/browser/e10s/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(` + <table id="table"> + <tr> + <td>cell1</td> + <td>cell2</td> + </tr> + </table>`, 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/e10s/browser_treeupdate_textleaf.js b/accessible/tests/browser/e10s/browser_treeupdate_textleaf.js new file mode 100644 index 000000000..da45e2bc9 --- /dev/null +++ b/accessible/tests/browser/e10s/browser_treeupdate_textleaf.js @@ -0,0 +1,35 @@ +/* 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, contentId => { + content.document.getElementById(contentId).firstChild.textContent = ''; + }); + yield onReorder; + + tree = { role: role, children: [] }; + testAccessibleTree(accessible, tree); +} + +addAccessibleTask(` + <p id="p">text</p> + <pre id="pre">text</pre>`, 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/e10s/browser_treeupdate_visibility.js b/accessible/tests/browser/e10s/browser_treeupdate_visibility.js new file mode 100644 index 000000000..65a55c914 --- /dev/null +++ b/accessible/tests/browser/e10s/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/e10s/browser_treeupdate_whitespace.js b/accessible/tests/browser/e10s/browser_treeupdate_whitespace.js new file mode 100644 index 000000000..c9dbde691 --- /dev/null +++ b/accessible/tests/browser/e10s/browser_treeupdate_whitespace.js @@ -0,0 +1,71 @@ +/* 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_whitespace.html', 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/e10s/doc_treeupdate_ariadialog.html b/accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html new file mode 100644 index 000000000..9d08854b9 --- /dev/null +++ b/accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html @@ -0,0 +1,23 @@ +<html> + <head> + <meta charset="utf-8"/> + <title>Tree Update ARIA Dialog Test</title> + </head> + <body id="body"> + <div id="dialog" role="dialog" style="display: none;"> + <table id="table" role="presentation" + style="display: block; position: fixed; top: 88px; left: 312.5px; z-index: 10010;"> + <tbody> + <tr> + <td role="presentation"> + <div role="presentation"> + <a id="a" role="button">text</a> + </div> + <input id="input"> + </td> + </tr> + </tbody> + </table> + </div> + </body> +</html> diff --git a/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html b/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html new file mode 100644 index 000000000..38b5c333a --- /dev/null +++ b/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html @@ -0,0 +1,44 @@ +<html> + <head> + <meta charset="utf-8"/> + <title>Tree Update ARIA Owns Test</title> + </head> + <body id="body"> + <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> + + <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> + </body> +</html> diff --git a/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html b/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html new file mode 100644 index 000000000..4dd230fc2 --- /dev/null +++ b/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html @@ -0,0 +1,21 @@ +<html> + <head> + <meta charset="utf-8"/> + <title>Tree Update Imagemap Test</title> + </head> + <body id="body"> + <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="http://example.com/a11y/accessible/tests/mochitest/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/browser/e10s/doc_treeupdate_removal.xhtml b/accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml new file mode 100644 index 000000000..9c59fb9d1 --- /dev/null +++ b/accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml @@ -0,0 +1,11 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta charset="utf-8"/> + <title>Tree Update Removal Test</title> + </head> + <body id="body"> + <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/browser/e10s/doc_treeupdate_visibility.html b/accessible/tests/browser/e10s/doc_treeupdate_visibility.html new file mode 100644 index 000000000..c33a2bc02 --- /dev/null +++ b/accessible/tests/browser/e10s/doc_treeupdate_visibility.html @@ -0,0 +1,78 @@ +<html> + <head> + <meta charset="utf-8"/> + <title>Tree Update Visibility Test</title> + </head> + <body id="body"> + <!-- 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> + <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 id="t4_parent"> + <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> + </body> +</html> diff --git a/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html b/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html new file mode 100644 index 000000000..d1770d300 --- /dev/null +++ b/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html @@ -0,0 +1,10 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta charset="utf-8"/> + <title>Whitespace text accessible creation/desctruction</title> + </head> + <body id="body"> + <div id="container1"> <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> <img id="img1" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> </div> + <div id="container2-parent"> <a id="container2"></a> <a><img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"></a> </div> + </body> +</html> diff --git a/accessible/tests/browser/e10s/events.js b/accessible/tests/browser/e10s/events.js new file mode 100644 index 000000000..39dd743ef --- /dev/null +++ b/accessible/tests/browser/e10s/events.js @@ -0,0 +1,127 @@ +/* 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_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_STATE_CHANGE, + EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE, EVENT_FOCUS, + 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; +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; + +/** + * 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 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 {String|nsIAccessible} expectedIdOrAcc 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, expectedIdOrAcc) { + return new Promise(resolve => { + let eventObserver = { + observe(subject, topic, data) { + if (topic !== 'accessible-event') { + return; + } + + let event = subject.QueryInterface(nsIAccessibleEvent); + if (Logger.enabled) { + // Avoid calling eventToString if the logger isn't enabled in order + // to avoid an intermittent crash (bug 1307645). + Logger.log(eventToString(event)); + } + + // If event type does not match expected type, skip the event. + if (event.eventType !== eventType) { + return; + } + + let acc = event.accessible; + let id = getAccessibleDOMNodeID(acc); + let isID = typeof expectedIdOrAcc === 'string'; + let actualIdOrAcc = isID ? id : acc; + // If event's accessible does not match expected DOMNode id or + // accessible, skip the event. + if (actualIdOrAcc === expectedIdOrAcc) { + if (isID) { + Logger.log(`Correct event DOMNode id: ${id}`); + } else { + Logger.log(`Correct event accessible: ${prettyName(acc)}`); + } + 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/e10s/head.js b/accessible/tests/browser/e10s/head.js new file mode 100644 index 000000000..5cc102697 --- /dev/null +++ b/accessible/tests/browser/e10s/head.js @@ -0,0 +1,84 @@ +/* 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, CURRENT_CONTENT_DIR, loadFrameScripts */ + +/* exported addAccessibleTask */ + +// Load the shared-head file first. +Services.scriptloader.loadSubScript( + 'chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js', + this); + +/** + * 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}e10s/${doc}`; + } else { + // Assume it's a markup snippet. + url = `data:text/html, + <html> + <head> + <meta charset="utf-8"/> + <title>Accessibility Test</title> + </head> + <body id="body">${doc}</body> + </html>`; + } + + registerCleanupFunction(() => { + let observers = Services.obs.enumerateObservers('accessible-event'); + while (observers.hasMoreElements()) { + Services.obs.removeObserver( + observers.getNext().QueryInterface(Ci.nsIObserver), + 'accessible-event'); + } + }); + + let onDocLoad = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, 'body'); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: url + }, function*(browser) { + registerCleanupFunction(() => { + if (browser) { + let tab = gBrowser.getTabForBrowser(browser); + if (tab && !tab.closing && tab.linkedBrowser) { + gBrowser.removeTab(tab); + } + } + }); + + yield SimpleTest.promiseFocus(browser); + + loadFrameScripts(browser, + 'let { document, window, navigator } = content;', + { name: 'common.js', dir: MOCHITESTS_DIR }); + + Logger.log( + `e10s enabled: ${Services.appinfo.browserTabsRemoteAutostart}`); + Logger.log(`Actually remote browser: ${browser.isRemoteBrowser}`); + + 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 }, 'e10s/events.js'); diff --git a/accessible/tests/browser/head.js b/accessible/tests/browser/head.js new file mode 100644 index 000000000..8e8d82205 --- /dev/null +++ b/accessible/tests/browser/head.js @@ -0,0 +1,116 @@ +/* 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'; + +/* exported initPromise, shutdownPromise, + setE10sPrefs, unsetE10sPrefs, forceGC */ + +/** + * Set e10s related preferences in the test environment. + * @return {Promise} promise that resolves when preferences are set. + */ +function setE10sPrefs() { + return new Promise(resolve => + SpecialPowers.pushPrefEnv({ + set: [ + ['browser.tabs.remote.autostart', true], + ['browser.tabs.remote.force-enable', true], + ['extensions.e10sBlocksEnabling', false] + ] + }, resolve)); +} + +/** + * Unset e10s related preferences in the test environment. + * @return {Promise} promise that resolves when preferences are unset. + */ +function unsetE10sPrefs() { + return new Promise(resolve => { + SpecialPowers.popPrefEnv(resolve); + }); +} + +// Load the shared-head file first. +Services.scriptloader.loadSubScript( + 'chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js', + this); + +/** + * Returns a promise that resolves when 'a11y-init-or-shutdown' event is fired. + * @return {Promise} event promise evaluating to event's data + */ +function a11yInitOrShutdownPromise() { + return new Promise(resolve => { + let observe = (subject, topic, data) => { + Services.obs.removeObserver(observe, 'a11y-init-or-shutdown'); + resolve(data); + }; + Services.obs.addObserver(observe, 'a11y-init-or-shutdown', false); + }); +} + +/** + * Returns a promise that resolves when 'a11y-init-or-shutdown' event is fired + * in content. + * @param {Object} browser current "tabbrowser" element + * @return {Promise} event promise evaluating to event's data + */ +function contentA11yInitOrShutdownPromise(browser) { + return ContentTask.spawn(browser, {}, a11yInitOrShutdownPromise); +} + +/** + * A helper function that maps 'a11y-init-or-shutdown' event to a promise that + * resovles or rejects depending on whether accessibility service is expected to + * be initialized or shut down. + */ +function promiseOK(promise, expected) { + return promise.then(flag => + flag === expected ? Promise.resolve() : Promise.reject()); +} + +/** + * Checks and returns a promise that resolves when accessibility service is + * initialized with the correct flag. + * @param {?Object} contentBrowser optinal remove browser object that indicates + * that accessibility service is expected to be + * initialized in content process. + * @return {Promise} promise that resolves when the accessibility + * service initialized correctly. + */ +function initPromise(contentBrowser) { + let a11yInitPromise = contentBrowser ? + contentA11yInitOrShutdownPromise(contentBrowser) : + a11yInitOrShutdownPromise(); + return promiseOK(a11yInitPromise, '1').then( + () => ok(true, 'Service initialized correctly'), + () => ok(false, 'Service shutdown incorrectly')); +} + +/** + * Checks and returns a promise that resolves when accessibility service is + * shut down with the correct flag. + * @param {?Object} contentBrowser optinal remove browser object that indicates + * that accessibility service is expected to be + * shut down in content process. + * @return {Promise} promise that resolves when the accessibility + * service shuts down correctly. + */ +function shutdownPromise(contentBrowser) { + let a11yShutdownPromise = contentBrowser ? + contentA11yInitOrShutdownPromise(contentBrowser) : + a11yInitOrShutdownPromise(); + return promiseOK(a11yShutdownPromise, '0').then( + () => ok(true, 'Service shutdown correctly'), + () => ok(false, 'Service initialized incorrectly')); +} + +/** + * Force garbage collection. + */ +function forceGC() { + Cu.forceCC(); + Cu.forceGC(); +} diff --git a/accessible/tests/browser/shared-head.js b/accessible/tests/browser/shared-head.js new file mode 100644 index 000000000..83a9fa612 --- /dev/null +++ b/accessible/tests/browser/shared-head.js @@ -0,0 +1,229 @@ +/* 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'; + +/* exported Logger, MOCHITESTS_DIR, isDefunct, invokeSetAttribute, invokeFocus, + invokeSetStyle, findAccessibleChildByID, getAccessibleDOMNodeID, + CURRENT_CONTENT_DIR, loadScripts, loadFrameScripts, Cc, Cu */ + +const { interfaces: Ci, utils: Cu, classes: Cc } = Components; + +/** + * 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) { + if (this.enabled) { + 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) { + let defunct = false; + try { + let extState = {}; + accessible.getState({}, extState); + defunct = extState.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT; + } catch (x) { + defunct = true; + } finally { + if (defunct) { + Logger.log(`Defunct accessible: ${prettyName(accessible)}`); + } + } + return defunct; +} + +/** + * 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) { + if (value) { + Logger.log(`Setting ${attr} attribute to ${value} for node with id: ${id}`); + } else { + Logger.log(`Removing ${attr} attribute from node with id: ${id}`); + } + return ContentTask.spawn(browser, [id, attr, value], + ([contentId, contentAttr, contentValue]) => { + let elm = content.document.getElementById(contentId); + if (contentValue) { + elm.setAttribute(contentAttr, contentValue); + } else { + elm.removeAttribute(contentAttr); + } + }); +} + +/** + * 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) { + if (value) { + Logger.log(`Setting ${style} style to ${value} for node with id: ${id}`); + } else { + Logger.log(`Removing ${style} style from node with id: ${id}`); + } + return ContentTask.spawn(browser, [id, style, value], + ([contentId, contentStyle, contentValue]) => { + let elm = content.document.getElementById(contentId); + if (contentValue) { + elm.style[contentStyle] = contentValue; + } else { + delete elm.style[contentStyle]; + } + }); +} + +/** + * 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) { + Logger.log(`Setting focus on a node with id: ${id}`); + return ContentTask.spawn(browser, id, contentId => { + let elm = content.document.getElementById(contentId); + 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); + } +} diff --git a/accessible/tests/crashtests/448064.xhtml b/accessible/tests/crashtests/448064.xhtml new file mode 100644 index 000000000..64d6d851d --- /dev/null +++ b/accessible/tests/crashtests/448064.xhtml @@ -0,0 +1,73 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> +</head> +<body> +<div id="mw_b"> +<div id="mw_f"> +<div id="mw_g" style="display: none;"/> +</div> +</div> + +<div id="mw_c" style="display: none;"> +<div id="mw_d"> +<div id="mw_e"></div> +</div> +</div> + +<input id="mw_a"/> + + +<script> +function dumpAccessibleNode(aNode, level) { + var msg = ""; + + try { + msg += "name=\"" + aNode.name + "\" "; + } catch (e) { + msg += " noName "; + } + + dump(msg + '\n'); +} + + +function dumpAccessibleTree(aNode, level) { +netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + level = level || 0; + + dumpAccessibleNode(aNode, level); + try { + var child = aNode.firstChild; + while (child) { + dumpAccessibleTree(child, level + 1); + child = child.nextSibling; + } + } catch (e) { + dump("Error visiting child nodes: " + e + '\n'); + } +} + +function A(o) { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + var acc = Components.classes['@mozilla.org/accessibilityService;1'] + .getService(Components.interfaces.nsIAccessibilityService); + return acc.getAccessibleFor(o); +} + +function beginAccessible() { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + dumpAccessibleTree(A(document),0); +} +setTimeout(beginAccessible, 100); + + +setTimeout(doe, 200); +function doe() { + document.getElementById('mw_a').appendChild(document.getElementById('mw_b')); + document.getElementById('mw_c').appendChild(document.getElementById('mw_d')); + document.getElementById('mw_e').appendChild(document.getElementById('mw_f')); + document.getElementById('mw_g').appendChild(document.getElementById('mw_b')); +} +</script> +</body> +</html>
\ No newline at end of file diff --git a/accessible/tests/crashtests/471493.xul b/accessible/tests/crashtests/471493.xul new file mode 100644 index 000000000..2524d47cc --- /dev/null +++ b/accessible/tests/crashtests/471493.xul @@ -0,0 +1,35 @@ +<?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="bug 471493 'crash [@ nsPropertyTable::GetPropertyInternal]'" + onload="doTest();"> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + var accService = SpecialPowers.Cc["@mozilla.org/accessibilityService;1"]. + getService(SpecialPowers.Ci.nsIAccessibilityService); + + var treecol = document.getElementById("col"); + var x = treecol.boxObject.screenX; + var y = treecol.boxObject.screenY; + + var tree = document.getElementById("tree"); + var treeAcc = accService.getAccessibleFor(tree); + treeAcc.getChildAtPoint(x + 1, y + 1); + } + ]]> + </script> + + <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> + +</window> + diff --git a/accessible/tests/crashtests/crashtests.list b/accessible/tests/crashtests/crashtests.list new file mode 100644 index 000000000..6718706aa --- /dev/null +++ b/accessible/tests/crashtests/crashtests.list @@ -0,0 +1,3 @@ +# Disabled because they cause assertions/crashes in later crashtests, see bug 918246 +skip load 448064.xhtml +skip load 471493.xul diff --git a/accessible/tests/mochitest/a11y.ini b/accessible/tests/mochitest/a11y.ini new file mode 100644 index 000000000..b197c7007 --- /dev/null +++ b/accessible/tests/mochitest/a11y.ini @@ -0,0 +1,17 @@ +[DEFAULT] +support-files = + ../../../dom/media/test/bug461281.ogg + dumbfile.zip + formimage.png + letters.gif + moz.png + longdesc_src.html + *.js + treeview.css + +[test_aria_token_attrs.html] +[test_bug420863.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 000000000..0c2765eeb --- /dev/null +++ b/accessible/tests/mochitest/actions.js @@ -0,0 +1,187 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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] 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 = new Array(); + if (events) { + var elm = getNode(accOrElmOrIDOfTarget); + if (events & MOUSEDOWN_EVENT) + eventSeq.push(new checkerOfActionInvoker("mousedown", elm)); + + if (events & MOUSEUP_EVENT) + eventSeq.push(new checkerOfActionInvoker("mouseup", elm)); + + if (events & CLICK_EVENT) + eventSeq.push(new checkerOfActionInvoker("click", elm, actionObj)); + + if (events & COMMAND_EVENT) + eventSeq.push(new checkerOfActionInvoker("command", elm)); + + 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; + } + } + + 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; + + this.phase = false; + + this.getID = function getID() + { + return aType + " event handling"; + } + + this.check = function check(aEvent) + { + if (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" +}; diff --git a/accessible/tests/mochitest/actions/a11y.ini b/accessible/tests/mochitest/actions/a11y.ini new file mode 100644 index 000000000..ca01f4104 --- /dev/null +++ b/accessible/tests/mochitest/actions/a11y.ini @@ -0,0 +1,18 @@ +[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.xul] +[test_keys.html] +[test_keys_menu.xul] +[test_link.html] +[test_media.html] +skip-if = buildapp == 'mulet' +[test_select.html] +[test_tree.xul] +[test_treegrid.xul] diff --git a/accessible/tests/mochitest/actions/test_anchors.html b/accessible/tests/mochitest/actions/test_anchors.html new file mode 100644 index 000000000..5e8330683 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_anchors.html @@ -0,0 +1,150 @@ +<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 type="application/javascript" + 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" + 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" + 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" + 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 000000000..c4eae812d --- /dev/null +++ b/accessible/tests/mochitest/actions/test_aria.html @@ -0,0 +1,202 @@ +<html> + +<head> + <title>nsIAccessible actions testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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" + 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 000000000..d6109a82f --- /dev/null +++ b/accessible/tests/mochitest/actions/test_controls.html @@ -0,0 +1,109 @@ +<html> + +<head> + <title>nsIAccessible actions testing for inputs</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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" + 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 000000000..5b9a18dab --- /dev/null +++ b/accessible/tests/mochitest/actions/test_general.html @@ -0,0 +1,107 @@ +<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 type="application/javascript" + 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" + 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" + 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=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.xul b/accessible/tests/mochitest/actions/test_general.xul new file mode 100644 index 000000000..14d8bb0d0 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_general.xul @@ -0,0 +1,145 @@ +<?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" + title="nsIAccessible actions testing"> + + <script type="application/javascript" + 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"> + <![CDATA[ + //gA11yEventDumpToConsole = true; + //enableLogging("tree,verbose"); // debug + + if (navigator.platform.startsWith("Mac")) { + SimpleTest.expectAssertions(0, 1); + } else { + SimpleTest.expectAssertions(0, 1); + } + + function doTest() + { + var actionsArray = [ + { + ID: "menu", + actionName: "click", + events: CLICK_EVENTS, + // Wait for focus event, it guarantees us the submenu tree is created, + // that's necessary for next test. + eventSeq: [ + new invokerChecker(EVENT_FOCUS, getNode("menu")) + ] + }, + { + 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 + }/*, // 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" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=410765" + title="nsIAccessible actions testing"> + Mozilla Bug 410765 + </a> + <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> + + <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"/> + <textbox id="name_entry"/> + </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 000000000..2feebcadb --- /dev/null +++ b/accessible/tests/mochitest/actions/test_keys.html @@ -0,0 +1,60 @@ +<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 type="application/javascript" + 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" + 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_menu.xul b/accessible/tests/mochitest/actions/test_keys_menu.xul new file mode 100644 index 000000000..b63b6fab1 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_keys_menu.xul @@ -0,0 +1,99 @@ +<?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 access keys and shortcut keys 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="../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() + { + 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" + 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"> + <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 000000000..dc96951d1 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_link.html @@ -0,0 +1,147 @@ +<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 type="application/javascript" + 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: "jump", + events: CLICK_EVENTS, + eventSeq: [ + new linkChecker("link1") + ] + }, + { + ID: "link2", + actionName: "click", + events: CLICK_EVENTS + }, + { + ID: "img2", + targetID: "link2", + actionName: "jump", + events: CLICK_EVENTS + }, + { + ID: "link3", + actionName: "click", + events: CLICK_EVENTS + }, + { + ID: "img3", + targetID: "link3", + actionName: "jump", + events: CLICK_EVENTS + }, + { + ID: "link4", + actionName: "click", + events: CLICK_EVENTS + }, + { + ID: "img4", + targetID: "link4", + actionName: "jump", + events: CLICK_EVENTS + } + ]; + testActions(actionsArray); + } + + 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> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <a href="about:mozilla" id="link1" target="_blank"> + <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 000000000..beb014ebc --- /dev/null +++ b/accessible/tests/mochitest/actions/test_media.html @@ -0,0 +1,121 @@ +<!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 type="application/javascript" + 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"); + } + } + + 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", + events: CLICK_EVENTS, + eventSeq: [ + // new focusChecker(muteBtn), + new nameChecker(muteBtn, "Unmute"), + ] + }, + // { + // ID: scrubber, + // actionName: "activate", + // events: null, + // eventSeq: [ + // new focusChecker(scrubber) + // ] + // }, + { + ID: playBtn, + actionName: "press", + events: CLICK_EVENTS, + eventSeq: [ + // new focusChecker(playBtn), + new nameChecker(playBtn, "Pause"), + ] + } + ]; + + testActions(actions); // Will call 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> + + <audio id="audio" src="../bug461281.ogg" + 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 000000000..1a6c89619 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_select.html @@ -0,0 +1,105 @@ +<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 type="application/javascript" + 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") + ] + }, + { + ID: "combobox", + actionIndex: 0, + actionName: "open", + events: CLICK_EVENTS, + eventSeq: [ + new focusChecker("cb_orange") + ] + }, + { + ID: "cb_apple", + actionIndex: 0, + actionName: "select", + events: CLICK_EVENTS, + eventSeq: [ + new focusChecker("combobox") + ] + }, + { + ID: "combobox", + actionIndex: 0, + actionName: "open", + events: CLICK_EVENTS, + eventSeq: [ + new focusChecker("cb_apple") + ] + }, + { + ID: "combobox", + actionIndex: 0, + actionName: "close", + events: CLICK_EVENTS, + eventSeq: [ + new focusChecker("combobox") + ] + } + ]; + + testActions(actionsArray); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </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> + + <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.xul b/accessible/tests/mochitest/actions/test_tree.xul new file mode 100644 index 000000000..3958ed2fb --- /dev/null +++ b/accessible/tests/mochitest/actions/test_tree.xul @@ -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="Accessible XUL tree actions tests"> + + <script type="application/javascript" + 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.boxObject.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" + 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.xul b/accessible/tests/mochitest/actions/test_treegrid.xul new file mode 100644 index 000000000..0bfb3d662 --- /dev/null +++ b/accessible/tests/mochitest/actions/test_treegrid.xul @@ -0,0 +1,197 @@ +<?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 type="application/javascript" + 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.boxObject.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(); + } + + function test1() + { + getNode("tabletree").view.setCellValue(0, boxObj.columns.firstColumn, "false"); + } + + 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="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"/> + <button oncommand="test1();" label="uncheck"/> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/aom/a11y.ini b/accessible/tests/mochitest/aom/a11y.ini new file mode 100644 index 000000000..03085c1de --- /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 000000000..5812ac55f --- /dev/null +++ b/accessible/tests/mochitest/aom/test_general.html @@ -0,0 +1,55 @@ +<!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 type="application/javascript" + 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 = "about:blank"; + iframe.onload = () => resolve(iframe.contentDocument); + document.body.appendChild(iframe); + }); + } + + // Check that the WebIDL is as expected. + function checkImplementation(ifrDoc) { + let anode = ifrDoc.accessibleNode; + ok(anode, "DOM document has accessible node"); + + is(anode.role, 'document', 'correct role of a document accessible node'); + is(anode.DOMNode, ifrDoc, 'correct DOM Node of a document accessible node'); + + finish(); + } + </script> +</head> diff --git a/accessible/tests/mochitest/attributes.js b/accessible/tests/mochitest/attributes.js new file mode 100644 index 000000000..b2ac78cba --- /dev/null +++ b/accessible/tests/mochitest/attributes.js @@ -0,0 +1,382 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 + */ +function testAttrs(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs) +{ + testAttrsInternal(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs); +} + +/** + * 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) + */ +function testAbsentAttrs(aAccOrElmOrID, aAbsentAttrs) +{ + testAttrsInternal(aAccOrElmOrID, {}, true, aAbsentAttrs); +} + +/** + * 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) +{ + var acc = getAccessible(aAccOrElmOrID); + var levelObj = {}, posInSetObj = {}, setSizeObj = {}; + acc.groupPosition(levelObj, setSizeObj, posInSetObj); + + if (aPosInSet && aSetSize) { + is(posInSetObj.value, aPosInSet, + "Wrong group position (posinset) for " + prettyName(aAccOrElmOrID)); + is(setSizeObj.value, aSetSize, + "Wrong size of the group (setsize) for " + prettyName(aAccOrElmOrID)); + + var attrs = { + "posinset": String(aPosInSet), + "setsize": String(aSetSize) + }; + testAttrs(aAccOrElmOrID, attrs, true); + } + + if (aLevel) { + is(levelObj.value, aLevel, + "Wrong group level for " + prettyName(aAccOrElmOrID)); + + var attrs = { "level" : String(aLevel) }; + testAttrs(aAccOrElmOrID, attrs, 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 accessbile + 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 accessbile + var expectedAttrs = {}; + for (var name in aAttrs) + expectedAttrs[name] = aAttrs[name]; + + for (var 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 = {}; + var acc = getAccessible(ID, [nsIAccessibleText]); + acc.getTextAttributes(false, 157, s, e); + } catch (e) { + 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; } + +// The pt font size of the input element can vary by Linux distro. +const kInputFontSize = WIN ? + "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; + } +} + +/** + * 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 == "transparent" ? + "rgb(255, 255, 255)" : 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) +{ + 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); +} + +function compareAttrs(aErrorMsg, aAttrs, aExpectedAttrs, aSkipUnexpectedAttrs, + aAbsentAttrs) +{ + // Check if all obtained attributes are expected and have expected value. + var enumerate = aAttrs.enumerate(); + while (enumerate.hasMoreElements()) { + var prop = enumerate.getNext().QueryInterface(nsIPropertyElement); + + if (!(prop.key in aExpectedAttrs)) { + if (!aSkipUnexpectedAttrs) + ok(false, "Unexpected attribute '" + prop.key + "' having '" + + prop.value + "'" + aErrorMsg); + } else { + var msg = "Attribute '" + prop.key + "' has wrong value" + aErrorMsg; + var expectedValue = aExpectedAttrs[prop.key]; + + if (typeof expectedValue == "function") + ok(expectedValue(prop.value), msg); + else + is(prop.value, expectedValue, msg); + } + } + + // Check if all expected attributes are presented. + for (var name in aExpectedAttrs) { + var value = ""; + try { + value = aAttrs.getStringProperty(name); + } catch(e) { } + + if (!value) + ok(false, + "There is no expected attribute '" + name + "' " + aErrorMsg); + } + + // Check if all unexpected attributes are absent. + if (aAbsentAttrs) { + for (var name in aAbsentAttrs) { + var wasFound = false; + + var enumerate = aAttrs.enumerate(); + while (enumerate.hasMoreElements()) { + var prop = enumerate.getNext().QueryInterface(nsIPropertyElement); + if (prop.key == name) + wasFound = true; + } + } + + ok(!wasFound, + "There is an unexpected attribute '" + name + "' " + aErrorMsg); + } +} diff --git a/accessible/tests/mochitest/attributes/a11y.ini b/accessible/tests/mochitest/attributes/a11y.ini new file mode 100644 index 000000000..ad53ecd27 --- /dev/null +++ b/accessible/tests/mochitest/attributes/a11y.ini @@ -0,0 +1,12 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_obj.html] +[test_obj_css.html] +[test_obj_css.xul] +[test_obj_group.html] +[test_obj_group.xul] +[test_obj_group_tree.xul] +[test_tag.html] +[test_xml-roles.html] diff --git a/accessible/tests/mochitest/attributes/test_obj.html b/accessible/tests/mochitest/attributes/test_obj.html new file mode 100644 index 000000000..9e147e1d1 --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_obj.html @@ -0,0 +1,278 @@ +<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 type="application/javascript" + 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); + testAttrs("checkedMenuitem", {"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); + testAbsentAttrs("haspopup", { "haspopup": "false" }); + testAttrs("hidden", {"hidden" : "true"}, true); + testAbsentAttrs("hidden_false", { "hidden": "false" }); + 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); + + // inherited attributes by subdocuments + var subdoc = getAccessible("iframe").firstChild; + testAttrs(subdoc, {"busy" : "true"}, true); + + // live object attribute + + // HTML + testAttrs("output", {"live" : "polite"}, true); + + // ARIA + testAttrs("live", {"live" : "polite"}, true); + testAttrs("live2", {"live" : "polite"}, true); + testAbsentAttrs("live3", {"live" : ""}); + 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); + 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"}); + + // 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); + + // 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": "" }, true); + + // application accessible + if (WIN) { + var gfxInfo = Components.classes["@mozilla.org/gfx/info;1"]. + getService(Components.interfaces.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); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=475006" + title="Extend nsARIAMap to capture ARIA attribute characteristics"> + Mozilla Bug 475006 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=391829" + title="Add support for container-live-role to object attributes"> + Mozilla Bug 391829 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=581952" + title="Make explicit that aria-label is not an object attribute"> + Mozilla Bug 475006 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=558036" + title="make HTML <output> accessible"> + Mozilla Bug 558036 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=896400" + title="Tablist should no longer be an implicit live region"> + Mozilla Bug 896400 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=563862" + title="Expand support for nsIAccessibleEvent::OBJECT_ATTRIBUTE_CHANGE"> + Mozilla Bug 563862 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=819303" + title="crash in nsTextEquivUtils::AppendTextEquivFromTextContent"> + Mozilla Bug 819303 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=838407" + title="aria-hidden false value shouldn't be exposed via object attributes"> + Mozilla Bug 838407 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121518" + title="ARIA 1.1: Support role 'searchbox'"> + Mozilla Bug 1121518 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <!-- 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="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="haspopup" aria-haspopup="true"></div> + <div id="hidden" aria-hidden="true"></div> + <div id="hidden_false" aria-hidden="false"></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> + + <!-- inherited from iframe --> + <iframe id="iframe" src="data:text/html,<html><body></body></html>" + aria-busy="true"></iframe> + + <!-- 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="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"/> + <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> +</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 000000000..6bf34543f --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_obj_css.html @@ -0,0 +1,231 @@ +<html> +<head> + <title>CSS-like attributes tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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.parentNode.removeChild(this.node); + } + + 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_css.xul b/accessible/tests/mochitest/attributes/test_obj_css.xul new file mode 100644 index 000000000..a5e93571e --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_obj_css.xul @@ -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="Accessibility CSS-based Object Attributes Test."> + + <script type="application/javascript" + 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 doTest() + { + // CSS display + testCSSAttrs("display_mozbox"); + testCSSAttrs("display_mozinlinebox"); + testCSSAttrs("display_mozgrid"); + testCSSAttrs("display_mozinlinegrid"); + testCSSAttrs("display_mozgridgroup"); + testCSSAttrs("display_mozgridline"); + testCSSAttrs("display_mozstack"); + testCSSAttrs("display_mozinlinestack"); + testCSSAttrs("display_mozdeck"); + testCSSAttrs("display_mozpopup"); + testCSSAttrs("display_mozgroupbox"); + + 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=714579" + title="Don't use GetComputedStyle for object attribute calculation"> + Mozilla Bug 714579 + </a><br/> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox id="display_mozbox" style="display: -moz-box;" role="img"/> + <vbox id="display_mozinlinebox" style="display: -moz-inline-box;" role="img"/> + <vbox id="display_mozgrid" style="display: -moz-grid;" role="img"/> + <vbox id="display_mozinlinegrid" style="display: -moz-inline-grid;" role="img"/> + <vbox id="display_mozgridgroup" style="display: -moz-grid-group;" role="img"/> + <vbox id="display_mozgridline" style="display: -moz-grid-line;" role="img"/> + <vbox id="display_mozstack" style="display: -moz-stack;" role="img"/> + <vbox id="display_mozinlinestack" style="display: -moz-inline-stack;" role="img"/> + <vbox id="display_mozdeck" style="display: -moz-deck;" role="img"/> + <vbox id="display_mozpopup" style="display: -moz-popup;" role="img"/> + <vbox id="display_mozgroupbox" style="display: -moz-groupbox;" role="img"/> + + </hbox> +</window> + 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 000000000..d5fe89471 --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_obj_group.html @@ -0,0 +1,469 @@ +<html> + +<head> + <title>Group attributes tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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); + + ////////////////////////////////////////////////////////////////////////// + // HTML select + testGroupAttrs("opt1", 1, 2); + testGroupAttrs("opt2", 2, 2); + + ////////////////////////////////////////////////////////////////////////// + // HTML select with options + // XXX bug 469123 + //testGroupAttrs("select2_optgroup", 1, 3, 1); + //testGroupAttrs("select2_opt3", 2, 3, 1); + //testGroupAttrs("select2_opt4", 3, 3, 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); + + ////////////////////////////////////////////////////////////////////////// + // HTML ul/ol (nested lists) + + testGroupAttrs("li4", 1, 3, 1); + testGroupAttrs("li5", 2, 3, 1); + testGroupAttrs("li6", 3, 3, 1); + + testGroupAttrs("n_li4", 1, 3, 2); + testGroupAttrs("n_li5", 2, 3, 2); + testGroupAttrs("n_li6", 3, 3, 2); + + ////////////////////////////////////////////////////////////////////////// + // ARIA list + testGroupAttrs("li7", 1, 3); + testGroupAttrs("li8", 2, 3); + testGroupAttrs("li9", 3, 3); + + ////////////////////////////////////////////////////////////////////////// + // ARIA list (nested lists: list -> listitem -> list -> listitem) + testGroupAttrs("li10", 1, 3, 1); + testGroupAttrs("li11", 2, 3, 1); + testGroupAttrs("li12", 3, 3, 1); + + testGroupAttrs("n_li10", 1, 3, 2); + testGroupAttrs("n_li11", 2, 3, 2); + testGroupAttrs("n_li12", 3, 3, 2); + + ////////////////////////////////////////////////////////////////////////// + // 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 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); + + ////////////////////////////////////////////////////////////////////////// + // ARIA tab + testGroupAttrs("tab_1", 1, 3); + testGroupAttrs("tab_2", 2, 3); + testGroupAttrs("tab_3", 3, 3); + + ////////////////////////////////////////////////////////////////////////// + // ARIA radio + testGroupAttrs("r1", 1, 3); + testGroupAttrs("r2", 2, 3); + testGroupAttrs("r3", 3, 3); + + ////////////////////////////////////////////////////////////////////////// + // 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); + + ////////////////////////////////////////////////////////////////////////// + // 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); + + ////////////////////////////////////////////////////////////////////////// + // 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); + + ////////////////////////////////////////////////////////////////////////// + // 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":""}); + + ////////////////////////////////////////////////////////////////////////// + // 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":""}); + + ////////////////////////////////////////////////////////////////////////// + // 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); + + ////////////////////////////////////////////////////////////////////////// + // ARIA combobox + testGroupAttrs("combo1_opt1", 1, 4); + testGroupAttrs("combo1_opt2", 2, 4); + testGroupAttrs("combo1_opt3", 3, 4); + testGroupAttrs("combo1_opt4", 4, 4); + + ////////////////////////////////////////////////////////////////////////// + // ARIA table + testGroupAttrs("table_cell", 3, 4); + testGroupAttrs("table_row", 2, 2); + + ////////////////////////////////////////////////////////////////////////// + // ARIA list constructed by ARIA owns + testGroupAttrs("t1_li1", 1, 3); + testGroupAttrs("t1_li2", 2, 3); + testGroupAttrs("t1_li3", 3, 3); + + // Test that group position information updates after deleting node. + testGroupAttrs("tree4_ti1", 1, 2, 1); + testGroupAttrs("tree4_ti2", 2, 2, 1); + var tree4element = document.getElementById("tree4_ti1"); + var tree4acc = getAccessible("tree4"); + tree4element.parentNode.removeChild(tree4element); + waitForEvent(EVENT_REORDER, tree4acc, function() { + testGroupAttrs("tree4_ti2", 1, 1, 1); + 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> + <li id="li1">Oranges</li> + <li id="li2">Apples</li> + <li id="li3">Bananas</li> + </ul> + + <ol> + <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> + + <span role="list"> + <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"> + <span role="listitem" id="li10">Oranges</span> + <span role="listitem" id="li11">Apples</span> + <span role="listitem" id="li12">Bananas + <span role="list"> + <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"> + <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"> + <li role="menuitem" aria-haspopup="true" id="menu_item1">File + <ul role="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"> + <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"> + <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"> + <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"> + <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"> + <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> + + <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> + + <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"> + <div role="row" id="table_row" aria-rowindex="2"> + <div role="cell" id="table_cell" aria-colindex="3">cell</div> + </div> + </div> + + <div role="list" aria-owns="t1_li1 t1_li2 t1_li3"> + <div role="listitem" id="t1_li2">Apples</div> + <div role="listitem" id="t1_li1">Oranges</div> + </span> + <div role="listitem" id="t1_li3">Bananas</div> +</body> +</html> diff --git a/accessible/tests/mochitest/attributes/test_obj_group.xul b/accessible/tests/mochitest/attributes/test_obj_group.xul new file mode 100644 index 000000000..c6943ae37 --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_obj_group.xul @@ -0,0 +1,216 @@ +<?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 type="application/javascript" + 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"> + + <listbox> + <listitem label="listitem1" id="listitem1"/> + <listitem label="listitem2" id="listitem2" type="checkbox"/> + <listitem label="listitem3" id="listitem3" type="checkbox"/> + <listitem label="listitem4" id="listitem4"/> + </listbox> + + <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.xul b/accessible/tests/mochitest/attributes/test_obj_group_tree.xul new file mode 100644 index 000000000..6b8461ef7 --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_obj_group_tree.xul @@ -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="Accessible XUL tree attributes tests"> + + <script type="application/javascript" + 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 000000000..e43147b24 --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_tag.html @@ -0,0 +1,82 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML landmark tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 000000000..9b3d5aa5a --- /dev/null +++ b/accessible/tests/mochitest/attributes/test_xml-roles.html @@ -0,0 +1,251 @@ +<!DOCTYPE html> +<html> +<head> + <title>XML roles tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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("section_header", {"xml-roles" : "banner"}); + testAttrs("footer", {"xml-roles" : "contentinfo"}, true); + testAbsentAttrs("article_footer", {"xml-roles" : "contentinfo"}); + testAbsentAttrs("section_footer", {"xml-roles" : "contentinfo"}); + testAttrs("aside", {"xml-roles" : "complementary"}, true); + testAttrs("section", {"xml-roles" : "region"}, true); + 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("search", {"xml-roles" : "searchbox"}, 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> + <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> + <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> + + <input id="search" type="search"/> + + <!-- 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 000000000..4de881b4d --- /dev/null +++ b/accessible/tests/mochitest/autocomplete.js @@ -0,0 +1,221 @@ + +const nsISupports = Components.interfaces.nsISupports; +const nsIAutoCompleteResult = Components.interfaces.nsIAutoCompleteResult; +const nsIAutoCompleteSearch = Components.interfaces.nsIAutoCompleteSearch; +const nsIFactory = Components.interfaces.nsIFactory; +const nsIUUIDGenerator = Components.interfaces.nsIUUIDGenerator; +const nsIComponentRegistrar = Components.interfaces.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 = Components.classes["@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: function(aSearchString) + { + var values = [], comments = []; + for (var idx = 0; idx < this.values.length; idx++) { + if (this.values[idx].indexOf(aSearchString) != -1) { + 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: function(aSearchString, aSearchParam, aPreviousResult, + aListener) + { + var result = this.allResults.getAutoCompleteResultFor(aSearchString); + aListener.onSearchResult(this, result); + }, + + stopSearch: function() {}, + + // nsISupports implementation + QueryInterface: function(iid) + { + if (iid.equals(nsISupports) || + iid.equals(nsIFactory) || + iid.equals(nsIAutoCompleteSearch)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + // nsIFactory implementation + createInstance: function(outer, 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 > 0) + 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: function(aIndex) + { + return this.values[aIndex]; + }, + + getLabelAt: function(aIndex) + { + return this.getValueAt(aIndex); + }, + + getCommentAt: function(aIndex) + { + return this.comments[aIndex]; + }, + + getStyleAt: function(aIndex) + { + return null; + }, + + getImageAt: function(aIndex) + { + return ""; + }, + + getFinalCompleteValueAt: function(aIndex) + { + return this.getValueAt(aIndex); + }, + + removeValueAt: function (aRowIndex, aRemoveFromDb) {}, + + // nsISupports implementation + QueryInterface: function(iid) { + if (iid.equals(nsISupports) || + iid.equals(nsIAutoCompleteResult)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + // 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 000000000..d60bd46a5 --- /dev/null +++ b/accessible/tests/mochitest/bounds/a11y.ini @@ -0,0 +1,8 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_list.html] +[test_select.html] +[test_zoom.html] +[test_zoom_text.html] diff --git a/accessible/tests/mochitest/bounds/test_list.html b/accessible/tests/mochitest/bounds/test_list.html new file mode 100644 index 000000000..38a79689b --- /dev/null +++ b/accessible/tests/mochitest/bounds/test_list.html @@ -0,0 +1,81 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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); + is(heightLI, heightLIElm, + "Outside list item height should match to list item element height"); + + 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/bounds/test_select.html b/accessible/tests/mochitest/bounds/test_select.html new file mode 100644 index 000000000..395dad1b6 --- /dev/null +++ b/accessible/tests/mochitest/bounds/test_select.html @@ -0,0 +1,85 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + function openComboboxNCheckBounds(aID) + { + this.combobox = getAccessible(aID); + this.comboboxList = this.combobox.firstChild; + this.comboboxOption = this.comboboxList.firstChild; + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, this.comboboxOption) + ]; + + this.invoke = function openComboboxNCheckBounds_invoke() + { + getNode(aID).focus(); + synthesizeKey("VK_DOWN", { altKey: true }); + } + + this.finalCheck = function openComboboxNCheckBounds_invoke() + { + testBounds(this.comboboxOption); + } + + this.getID = function openComboboxNCheckBounds_getID() + { + return "open combobox and test boundaries"; + } + } + + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() + { + // Combobox + testBounds("combobox"); + + // Option boundaries matches to combobox boundaries when collapsed. + var selectBounds = getBoundsForDOMElm("combobox"); + testBounds("option1", selectBounds); + + // Open combobox and test option boundaries. + gQueue = new eventQueue(); + gQueue.push(new openComboboxNCheckBounds("combobox")); + 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="combobox"> + <option id="option1">item1</option> + <option>item2</option> + </select> +</body> +</html> diff --git a/accessible/tests/mochitest/bounds/test_zoom.html b/accessible/tests/mochitest/bounds/test_zoom.html new file mode 100644 index 000000000..fc2dee482 --- /dev/null +++ b/accessible/tests/mochitest/bounds/test_zoom.html @@ -0,0 +1,96 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + <script type="application/javascript" + src="../browser.js"></script> + + <script type="application/javascript"> + //gA11yEventDumpToConsole = true; + //enableLogging("tree,verbose"); + function doPreTest() + { + var tabDocument = currentTabDocument(); + var imgMap = tabDocument.getElementById("imgmap"); + waitForImageMap(imgMap, doTest); + } + + function doTest() + { + // Bug 746176: Failure of this whole test file on OS X. + if (MAC) { + todo(false, "Fix bug 746176 on Mac"); + closeBrowserWindow(); + SimpleTest.finish(); + return; + } + + var tabDocument = currentTabDocument(); + var p1 = tabDocument.getElementById("p1"); + var p2 = tabDocument.getElementById("p2"); + + var imgMap = tabDocument.getElementById("imgmap"); + var imgMapAcc = getAccessible(imgMap); + var area = imgMapAcc.firstChild; + + testBounds(p1); + testBounds(p2); + testBounds(area); + + zoomDocument(tabDocument, 2.0); + + testBounds(p1); + testBounds(p2); + testBounds(area); + + closeBrowserWindow(); + SimpleTest.finish(); + } + + var url = "data:text/html,<html><body>"; + url += "<p id='p1'>para 1</p><p id='p2'>para 2</p>"; + url += "<map name='atoz_map' id='map'>"; + url += " <area id='area1' href='http%3A%2F%2Fmozilla.org'"; + url += " coords=17,0,30,14' alt='mozilla.org' shape='rect'>"; + url += "</map>"; + url += "<img id='imgmap' width='447' height='15'"; + url += " usemap='%23atoz_map'"; + url += " src='chrome%3A%2F%2Fmochitests%2Fcontent%2Fa11y%2Faccessible%2Fletters.gif'>"; + url += "</body></html>"; + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doPreTest, + url, + { left: 0, top: 0, width: 600, height: 600 }); + </script> + +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=650241" + title="Location returned by accessibles incorrect when page zoomed"> + Mozilla Bug 650241 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/bounds/test_zoom_text.html b/accessible/tests/mochitest/bounds/test_zoom_text.html new file mode 100644 index 000000000..1637f344e --- /dev/null +++ b/accessible/tests/mochitest/bounds/test_zoom_text.html @@ -0,0 +1,77 @@ +<!DOCTYPE html> +<html> +<head> + <title>The text range boundary when page is zoomed</title> + <meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 testTextNode(aDoc, aContainerID) + { + var hyperTextNode = aDoc.getElementById(aContainerID); + var textNode = hyperTextNode.firstChild; + + var [x, y, width, height] = getBounds(textNode); + testTextBounds(hyperTextNode, 0, -1, [x, y, width, height], + COORDTYPE_SCREEN_RELATIVE); + } + + function doTest() + { + var tabDocument = currentTabDocument(); + testTextNode(tabDocument, "p1"); + testTextNode(tabDocument, "p2"); + + zoomDocument(tabDocument, 2.0); + + testTextNode(tabDocument, "p1"); + + zoomDocument(tabDocument, 1.0); + + closeBrowserWindow(); + SimpleTest.finish(); + } + + var url = "data:text/html,<html>" + + "<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>" + + "</meta><body>" + + "<p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>" + + "<p id='p2'>ل</p>" + "</body></html>"; + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTest, + url, + { left: 0, top: 0, width: 600, height: 600 }); + + </script> + +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942" + title="Text range boundaries are incorrect when page is 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/browser.js b/accessible/tests/mochitest/browser.js new file mode 100644 index 000000000..f84f545d8 --- /dev/null +++ b/accessible/tests/mochitest/browser.js @@ -0,0 +1,153 @@ +/** + * 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 + +Components.utils.import("resource://gre/modules/Services.jsm"); + +var gBrowserContext = +{ + browserWnd: null, + testFunc: null, + startURL: "" +}; + +function openBrowserWindowIntl() +{ + var params = "chrome,all,dialog=no"; + 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.openDialog(Services.prefs.getCharPref("browser.chromeURL"), + "_blank", params, + gBrowserContext.startURL); + + whenDelayedStartupFinished(browserWindow(), function () { + addA11yLoadEvent(startBrowserTests, browserWindow()); + }); +} + +function startBrowserTests() +{ + if (gBrowserContext.startURL) // 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", false); +} diff --git a/accessible/tests/mochitest/common.js b/accessible/tests/mochitest/common.js new file mode 100644 index 000000000..1e48fa067 --- /dev/null +++ b/accessible/tests/mochitest/common.js @@ -0,0 +1,952 @@ +//////////////////////////////////////////////////////////////////////////////// +// Interfaces + +const nsIAccessibilityService = Components.interfaces.nsIAccessibilityService; + +const nsIAccessibleEvent = Components.interfaces.nsIAccessibleEvent; +const nsIAccessibleStateChangeEvent = + Components.interfaces.nsIAccessibleStateChangeEvent; +const nsIAccessibleCaretMoveEvent = + Components.interfaces.nsIAccessibleCaretMoveEvent; +const nsIAccessibleTextChangeEvent = + Components.interfaces.nsIAccessibleTextChangeEvent; +const nsIAccessibleVirtualCursorChangeEvent = + Components.interfaces.nsIAccessibleVirtualCursorChangeEvent; +const nsIAccessibleObjectAttributeChangedEvent = + Components.interfaces.nsIAccessibleObjectAttributeChangedEvent; + +const nsIAccessibleStates = Components.interfaces.nsIAccessibleStates; +const nsIAccessibleRole = Components.interfaces.nsIAccessibleRole; +const nsIAccessibleScrollType = Components.interfaces.nsIAccessibleScrollType; +const nsIAccessibleCoordinateType = Components.interfaces.nsIAccessibleCoordinateType; + +const nsIAccessibleRelation = Components.interfaces.nsIAccessibleRelation; +const nsIAccessibleTextRange = Components.interfaces.nsIAccessibleTextRange; + +const nsIAccessible = Components.interfaces.nsIAccessible; + +const nsIAccessibleDocument = Components.interfaces.nsIAccessibleDocument; +const nsIAccessibleApplication = Components.interfaces.nsIAccessibleApplication; + +const nsIAccessibleText = Components.interfaces.nsIAccessibleText; +const nsIAccessibleEditableText = Components.interfaces.nsIAccessibleEditableText; + +const nsIAccessibleHyperLink = Components.interfaces.nsIAccessibleHyperLink; +const nsIAccessibleHyperText = Components.interfaces.nsIAccessibleHyperText; + +const nsIAccessibleImage = Components.interfaces.nsIAccessibleImage; +const nsIAccessiblePivot = Components.interfaces.nsIAccessiblePivot; +const nsIAccessibleSelectable = Components.interfaces.nsIAccessibleSelectable; +const nsIAccessibleTable = Components.interfaces.nsIAccessibleTable; +const nsIAccessibleTableCell = Components.interfaces.nsIAccessibleTableCell; +const nsIAccessibleTraversalRule = Components.interfaces.nsIAccessibleTraversalRule; +const nsIAccessibleValue = Components.interfaces.nsIAccessibleValue; + +const nsIObserverService = Components.interfaces.nsIObserverService; + +const nsIDOMDocument = Components.interfaces.nsIDOMDocument; +const nsIDOMEvent = Components.interfaces.nsIDOMEvent; +const nsIDOMHTMLDocument = Components.interfaces.nsIDOMHTMLDocument; +const nsIDOMNode = Components.interfaces.nsIDOMNode; +const nsIDOMHTMLElement = Components.interfaces.nsIDOMHTMLElement; +const nsIDOMWindow = Components.interfaces.nsIDOMWindow; +const nsIDOMXULElement = Components.interfaces.nsIDOMXULElement; + +const nsIPropertyElement = Components.interfaces.nsIPropertyElement; + +//////////////////////////////////////////////////////////////////////////////// +// OS detect + +const MAC = (navigator.platform.indexOf("Mac") != -1); +const LINUX = (navigator.platform.indexOf("Linux") != -1); +const SOLARIS = (navigator.platform.indexOf("SunOS") != -1); +const WIN = (navigator.platform.indexOf("Win") != -1); + +//////////////////////////////////////////////////////////////////////////////// +// Application detect +// Firefox is assumed by default. + +const SEAMONKEY = navigator.userAgent.match(/ SeaMonkey\//); + +//////////////////////////////////////////////////////////////////////////////// +// Accessible general + +const STATE_BUSY = nsIAccessibleStates.STATE_BUSY; + +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(0x25fe) + " "; + +const MAX_TRIM_LENGTH = 100; + +/** + * Services to determine if e10s is enabled. + */ +Components.utils.import('resource://gre/modules/Services.jsm'); + +/** + * nsIAccessibilityService service. + */ +var gAccService = Components.classes["@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) + return waitForDocLoad(); + + window.setTimeout(aFunc, 0); + }, + 0 + ); + } + + 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) + "'"); +} + +//////////////////////////////////////////////////////////////////////////////// +// 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 (aAccOrNodeOrID instanceof nsIDOMNode) + 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 (aAccOrElmOrID instanceof nsIDOMNode) { + 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) ? + true : false; +} + +/** + * Return an accessible that contains the DOM node for the given identifier. + */ +function getContainerAccessible(aAccOrElmOrID) +{ + var node = getNode(aAccOrElmOrID); + if (!node) + return null; + + 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 + */ +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 (var 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) { + var attrs = accTree[prop][prevOffset]; + testTextAttrs(acc, prevOffset, attrs, { }, prevOffset, +offset, true); + } + prevOffset = +offset; + } + + if (prevOffset != -1) { + var charCount = getAccessible(acc, [nsIAccessibleText]).characterCount; + var 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 (var 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 (var i = 0; i < childCount; i++) { + var 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++) { + var 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) ? true : false; +} + +/** + * 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 == Components.results.NS_ERROR_FAILURE) + } + ok(success, "firstChild" + msg); + + // lastChild + success = false; + try { + aAcc.lastChild; + } catch (e) { + success = (e.result == Components.results.NS_ERROR_FAILURE) + } + ok(success, "lastChild" + msg); + + // childCount + success = false; + try { + aAcc.childCount; + } catch (e) { + success = (e.result == Components.results.NS_ERROR_FAILURE) + } + ok(success, "childCount" + msg); + + // children + success = false; + try { + aAcc.children; + } catch (e) { + success = (e.result == Components.results.NS_ERROR_FAILURE) + } + ok(success, "children" + msg); + + // nextSibling + success = false; + try { + aAcc.nextSibling; + } catch (e) { + success = (e.result == Components.results.NS_ERROR_FAILURE); + } + ok(success, "nextSibling" + msg); + + // previousSibling + success = false; + try { + aAcc.previousSibling; + } catch (e) { + success = (e.result == Components.results.NS_ERROR_FAILURE); + } + ok(success, "previousSibling" + msg); + + // parent + success = false; + try { + aAcc.parent; + } catch (e) { + success = (e.result == Components.results.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 != 0) + 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() { + const Ci = Components.interfaces; + return window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsILoadContext); +} + +/** + * Return text from clipboard. + */ +function getTextFromClipboard() +{ + var clip = Components.classes["@mozilla.org/widget/clipboard;1"]. + getService(Components.interfaces.nsIClipboard); + if (!clip) + return ""; + + var trans = Components.classes["@mozilla.org/widget/transferable;1"]. + createInstance(Components.interfaces.nsITransferable); + trans.init(getLoadContext()); + if (!trans) + return ""; + + trans.addDataFlavor("text/unicode"); + clip.getData(trans, clip.kGlobalClipboard); + + var str = new Object(); + var strLength = new Object(); + trans.getTransferData("text/unicode", str, strLength); + + if (str) + str = str.value.QueryInterface(Components.interfaces.nsISupportsString); + if (str) + return str.data.substring(0, strLength.value / 2); + + 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. + */ +function prettyName(aIdentifier) +{ + if (aIdentifier instanceof Array) { + var 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); + var 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 (aIdentifier instanceof nsIDOMNode) + 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.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebNavigation) + .QueryInterface(Components.interfaces.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindow); +} + +/** Sets the test plugin(s) initially expected enabled state. + * It will automatically be reset to it's previous value after the test + * ends. + * @param aNewEnabledState [in] the enabled state, e.g. SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED + * @param aPluginName [in, optional] The name of the plugin, defaults to "Test Plug-in" + */ +function setTestPluginEnabledState(aNewEnabledState, aPluginName) +{ + var plugin = getTestPluginTag(aPluginName); + var oldEnabledState = plugin.enabledState; + plugin.enabledState = aNewEnabledState; + SimpleTest.registerCleanupFunction(function() { + getTestPluginTag(aPluginName).enabledState = oldEnabledState; + }); +} + +//////////////////////////////////////////////////////////////////////////////// +// Private +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// Accessible general + +function getNodePrettyName(aNode) +{ + try { + var tag = ""; + if (aNode.nodeType == nsIDOMNode.DOCUMENT_NODE) { + tag = "document"; + } else { + tag = aNode.localName; + if (aNode.nodeType == nsIDOMNode.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 getTestPluginTag(aPluginName) +{ + var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"] + .getService(SpecialPowers.Ci.nsIPluginHost); + var tags = ph.getPluginTags(); + var name = aPluginName || "Test Plug-in"; + for (var tag of tags) { + if (tag.name == name) { + return tag; + } + } + + ok(false, "Could not find plugin tag with plugin name '" + name + "'"); + return null; +} + +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 000000000..15cb0ecb3 --- /dev/null +++ b/accessible/tests/mochitest/dumbfile.zip diff --git a/accessible/tests/mochitest/editabletext/a11y.ini b/accessible/tests/mochitest/editabletext/a11y.ini new file mode 100644 index 000000000..68466fdf2 --- /dev/null +++ b/accessible/tests/mochitest/editabletext/a11y.ini @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = + editabletext.js + !/accessible/tests/mochitest/*.js + +[test_1.html] +[test_2.html] diff --git a/accessible/tests/mochitest/editabletext/editabletext.js b/accessible/tests/mochitest/editabletext/editabletext.js new file mode 100644 index 000000000..2fb1851bf --- /dev/null +++ b/accessible/tests/mochitest/editabletext/editabletext.js @@ -0,0 +1,353 @@ +/** + * Perform all editable text tests. + */ +function editableTextTestRun() +{ + this.add = function add(aTest) + { + this.seq.push(aTest); + } + + this.run = function run() + { + this.iterate(); + } + + this.index = 0; + this.seq = new Array(); + + this.iterate = function iterate() + { + if (this.index < this.seq.length) { + this.seq[this.index++].startTest(this); + return; + } + + this.seq = null; + SimpleTest.finish(); + } +} + +/** + * Used to test nsIEditableTextAccessible methods. + */ +function editableTextTest(aID) +{ + /** + * Schedule a test, the given function with its arguments will be executed + * when preceding test is complete. + */ + this.scheduleTest = function scheduleTest(aFunc) + { + // A data container acts like a dummy invoker, it's never invoked but + // it's used to generate real invoker when previous invoker was handled. + var dataContainer = { + func: aFunc, + funcArgs: Array.slice(arguments, 1) + }; + this.mEventQueue.push(dataContainer); + + if (!this.mEventQueueReady) { + this.unwrapNextTest(); + this.mEventQueueReady = true; + } + } + + /** + * setTextContents test. + */ + this.setTextContents = function setTextContents(aValue, aSkipStartOffset) + { + var testID = "setTextContents '" + aValue + "' for " + prettyName(aID); + + function setTextContentsInvoke() + { + dump(`\nsetTextContents '${aValue}'\n`); + var acc = getAccessible(aID, nsIAccessibleEditableText); + acc.setTextContents(aValue); + } + + aSkipStartOffset = aSkipStartOffset || 0; + var insertTripple = aValue ? + [ aSkipStartOffset, aSkipStartOffset + aValue.length, aValue ] : null; + var oldValue = getValue(aID); + var removeTripple = oldValue ? + [ aSkipStartOffset, aSkipStartOffset + oldValue.length, oldValue ] : null; + + this.generateTest(aID, removeTripple, insertTripple, setTextContentsInvoke, + getValueChecker(aID, aValue), testID); + } + + /** + * insertText test. + */ + this.insertText = function insertText(aStr, aPos, aResStr, aResPos) + { + var testID = "insertText '" + aStr + "' at " + aPos + " for " + + prettyName(aID); + + function insertTextInvoke() + { + dump(`\ninsertText '${aStr}' at ${aPos} pos\n`); + var acc = getAccessible(aID, nsIAccessibleEditableText); + acc.insertText(aStr, aPos); + } + + var resPos = (aResPos != undefined) ? aResPos : aPos; + this.generateTest(aID, null, [resPos, resPos + aStr.length, aStr], + insertTextInvoke, getValueChecker(aID, aResStr), testID); + } + + /** + * copyText test. + */ + this.copyText = function copyText(aStartPos, aEndPos, aClipboardStr) + { + var testID = "copyText from " + aStartPos + " to " + aEndPos + " for " + + prettyName(aID); + + function copyTextInvoke() + { + var acc = getAccessible(aID, nsIAccessibleEditableText); + acc.copyText(aStartPos, aEndPos); + } + + this.generateTest(aID, null, null, copyTextInvoke, + getClipboardChecker(aID, aClipboardStr), testID); + } + + /** + * copyText and pasteText test. + */ + this.copyNPasteText = function copyNPasteText(aStartPos, aEndPos, + aPos, aResStr) + { + var testID = "copyText from " + aStartPos + " to " + aEndPos + + "and pasteText at " + aPos + " for " + prettyName(aID); + + function copyNPasteTextInvoke() + { + var acc = getAccessible(aID, nsIAccessibleEditableText); + acc.copyText(aStartPos, aEndPos); + acc.pasteText(aPos); + } + + this.generateTest(aID, null, [aStartPos, aEndPos, getTextFromClipboard], + copyNPasteInvoke, getValueChecker(aID, aResStr), testID); + } + + /** + * cutText test. + */ + this.cutText = function cutText(aStartPos, aEndPos, aResStr, + aResStartPos, aResEndPos) + { + var testID = "cutText from " + aStartPos + " to " + aEndPos + " for " + + prettyName(aID); + + function cutTextInvoke() + { + var acc = getAccessible(aID, nsIAccessibleEditableText); + acc.cutText(aStartPos, aEndPos); + } + + var resStartPos = (aResStartPos != undefined) ? aResStartPos : aStartPos; + var resEndPos = (aResEndPos != undefined) ? aResEndPos : aEndPos; + this.generateTest(aID, [resStartPos, resEndPos, getTextFromClipboard], null, + cutTextInvoke, getValueChecker(aID, aResStr), testID); + } + + /** + * cutText and pasteText test. + */ + this.cutNPasteText = function copyNPasteText(aStartPos, aEndPos, + aPos, aResStr) + { + var testID = "cutText from " + aStartPos + " to " + aEndPos + + " and pasteText at " + aPos + " for " + prettyName(aID); + + function cutNPasteTextInvoke() + { + var acc = getAccessible(aID, nsIAccessibleEditableText); + acc.cutText(aStartPos, aEndPos); + acc.pasteText(aPos); + } + + this.generateTest(aID, [aStartPos, aEndPos, getTextFromClipboard], + [aPos, -1, getTextFromClipboard], + cutNPasteTextInvoke, getValueChecker(aID, aResStr), + testID); + } + + /** + * pasteText test. + */ + this.pasteText = function pasteText(aPos, aResStr) + { + var testID = "pasteText at " + aPos + " for " + prettyName(aID); + + function pasteTextInvoke() + { + var acc = getAccessible(aID, nsIAccessibleEditableText); + acc.pasteText(aPos); + } + + this.generateTest(aID, null, [aPos, -1, getTextFromClipboard], + pasteTextInvoke, getValueChecker(aID, aResStr), testID); + } + + /** + * deleteText test. + */ + this.deleteText = function deleteText(aStartPos, aEndPos, aResStr) + { + var testID = "deleteText from " + aStartPos + " to " + aEndPos + + " for " + prettyName(aID); + + var oldValue = getValue(aID).substring(aStartPos, aEndPos); + var removeTripple = oldValue ? [aStartPos, aEndPos, oldValue] : null; + + function deleteTextInvoke() + { + var acc = getAccessible(aID, [nsIAccessibleEditableText]); + acc.deleteText(aStartPos, aEndPos); + } + + this.generateTest(aID, removeTripple, null, deleteTextInvoke, + getValueChecker(aID, aResStr), testID); + } + + ////////////////////////////////////////////////////////////////////////////// + // Implementation details. + + function getValue(aID) + { + var value = ""; + var elm = getNode(aID); + if (elm instanceof Components.interfaces.nsIDOMNSEditableElement) + return elm.value; + + if (elm instanceof Components.interfaces.nsIDOMHTMLDocument) + return elm.body.textContent; + + return elm.textContent; + } + + /** + * Common checkers. + */ + function getValueChecker(aID, aValue) + { + var checker = { + check: function valueChecker_check() + { + is(getValue(aID), aValue, "Wrong value " + aValue); + } + }; + return checker; + } + + function getClipboardChecker(aID, aText) + { + var checker = { + check: function clipboardChecker_check() + { + is(getTextFromClipboard(), aText, "Wrong text in clipboard."); + } + }; + return checker; + } + + function getValueNClipboardChecker(aID, aValue, aText) + { + var valueChecker = getValueChecker(aID, aValue); + var clipboardChecker = getClipboardChecker(aID, aText); + + var checker = { + check: function() + { + valueChecker.check(); + clipboardChecker.check(); + } + }; + return checker; + } + + /** + * Process next scheduled test. + */ + this.unwrapNextTest = function unwrapNextTest() + { + var data = this.mEventQueue.mInvokers[this.mEventQueue.mIndex + 1]; + if (data) + data.func.apply(this, data.funcArgs); + } + + /** + * Used to generate an invoker object for the sheduled test. + */ + this.generateTest = function generateTest(aID, aRemoveTriple, aInsertTriple, + aInvokeFunc, aChecker, aInvokerID) + { + var et = this; + var invoker = { + eventSeq: [], + + invoke: aInvokeFunc, + finalCheck: function finalCheck() + { + //dumpTree(aID, `'${aID}' tree:`); + + aChecker.check(); + et.unwrapNextTest(); // replace dummy invoker on real invoker object. + }, + getID: function getID() { return aInvokerID; } + }; + + if (aRemoveTriple) { + var checker = new textChangeChecker(aID, aRemoveTriple[0], + aRemoveTriple[1], aRemoveTriple[2], + false); + invoker.eventSeq.push(checker); + } + + if (aInsertTriple) { + var checker = new textChangeChecker(aID, aInsertTriple[0], + aInsertTriple[1], aInsertTriple[2], + true); + invoker.eventSeq.push(checker); + } + + // Claim that we don't want to fail when no events are expected. + if (!aRemoveTriple && !aInsertTriple) + invoker.noEventsOnAction = true; + + this.mEventQueue.mInvokers[this.mEventQueue.mIndex + 1] = invoker; + } + + /** + * Run the tests. + */ + this.startTest = function startTest(aTestRun) + { + var testRunObj = aTestRun; + var thisObj = this; + this.mEventQueue.onFinish = function finishCallback() + { + // Notify textRun object that all tests were finished. + testRunObj.iterate(); + + // Help GC to avoid leaks (refer to aTestRun from local variable, drop + // onFinish function). + thisObj.mEventQueue.onFinish = null; + + return DO_NOT_FINISH_TEST; + } + + this.mEventQueue.invoke(); + } + + this.mEventQueue = new eventQueue(); + this.mEventQueueReady = false; +} + diff --git a/accessible/tests/mochitest/editabletext/test_1.html b/accessible/tests/mochitest/editabletext/test_1.html new file mode 100644 index 000000000..0c0b19696 --- /dev/null +++ b/accessible/tests/mochitest/editabletext/test_1.html @@ -0,0 +1,144 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=452161 +--> +<head> + <title>nsIAccessibleEditableText chrome tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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="editabletext.js"></script> + + <script type="application/javascript"> + //gA11yEventDumpToConsole = true; + //enableLogging("tree,verbose"); // debug + + function addTestEditable(aID, aTestRun, aBeforeContent, aAfterContent) + { + var et = new editableTextTest(aID); + var startOffset = aBeforeContent ? aBeforeContent.length : 0; + // XXX afterContent currently is not used + + ////////////////////////////////////////////////////////////////////////// + // setTextContents + et.scheduleTest(et.setTextContents, "hello", startOffset); + et.scheduleTest(et.setTextContents, "olleh", startOffset); + et.scheduleTest(et.setTextContents, "", startOffset); + + ////////////////////////////////////////////////////////////////////////// + // insertText + et.scheduleTest(et.insertText, "hello", startOffset, "hello"); + et.scheduleTest(et.insertText, "ma ", startOffset, "ma hello"); + et.scheduleTest(et.insertText, "ma", startOffset + 2, "mama hello"); + et.scheduleTest(et.insertText, " hello", startOffset + 10, "mama hello hello"); + + // XXX: bug 452584 + + ////////////////////////////////////////////////////////////////////////// + // deleteText +// et.deleteText(0, 5, "hello hello"); +// et.deleteText(5, 6, "hellohello"); +// et.deleteText(5, 10, "hello"); +// et.deleteText(0, 5, ""); + + ////////////////////////////////////////////////////////////////////////// + // copyNPasteText +// et.copyNPasteText(0, 0, 0, ""); +// et.insertText("hello", 0, "hello"); +// et.copyNPasteText(0, 1, 0, "hhello"); +// et.copyNPasteText(5, 6, 6, "hhelloo"); +// et.copyNPasteText(3, 4, 1, "hehelloo"); + + ////////////////////////////////////////////////////////////////////////// +// // cutNPasteText +// et.cutNPasteText(0, 1, 1, "ehhelloo"); +// et.cutNPasteText(1, 2, 0, "hehelloo"); +// et.cutNPasteText(7, 8, 8, "hehelloo"); + + aTestRun.add(et); + } + + //gA11yEventDumpToConsole = true; // debug stuff + + function runTest() + { + var testRun = new editableTextTestRun(); + + addTestEditable("input", testRun); + addTestEditable("div", testRun); + addTestEditable("divb", testRun, "pseudo element", ""); + addTestEditable("diva", testRun, "", "pseudo element"); + addTestEditable("divba", testRun, "before", "after"); + addTestEditable(getNode("frame").contentDocument, testRun); + + testRun.run(); // Will call SimpleTest.finish(); + } + + function doTest() + { + // Prepare tested elements. + + // Design mode on/off triggers an editable state change event on + // the document accessible. + var frame = getNode("frame"); + waitForEvent(EVENT_STATE_CHANGE, frame.contentDocument, runTest); + frame.contentDocument.designMode = "on"; + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + <style> + #divb::before, + #diva::after { + content: "pseudo element"; + } + #divba::before { + content: "before"; + } + #divba::after { + content: "after"; + } + </style> +</head> +<body> + + <a target="_blank" + title="nsIAccessibleEditableText chrome tests" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=452161"> + Bug 452161 + </a> + <a target="_blank" + title="Cache rendered text on a11y side" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660"> + Bug 626660 + </a> + <a target="_blank" + title="Pseudo element support test" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1105611"> + Bug 1105611 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="input"/> + + <div id="div" contenteditable="true"></div> + <div id="divb" contenteditable="true"></div> + <div id="diva" contenteditable="true"></div> + <div id="divba" contenteditable="true"></div> + + <iframe id="frame"/> +</body> +</html> diff --git a/accessible/tests/mochitest/editabletext/test_2.html b/accessible/tests/mochitest/editabletext/test_2.html new file mode 100644 index 000000000..5d96ebf35 --- /dev/null +++ b/accessible/tests/mochitest/editabletext/test_2.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<html> +<head> + <title>nsIAccessibleEditableText chrome tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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="editabletext.js"></script> + + <script type="application/javascript"> + function doTest() + { + var et = new editableTextTest("input"); + + // 'ee' insertion/removal at 1 or 2 offset of 'hello'/'heeello' string + // reports 'ee' text was inserted/removed at 2 offset. + et.scheduleTest(et.insertText, "ee", 1, "heeello", 2); + et.scheduleTest(et.copyText, 1, 3, "ee"); + et.scheduleTest(et.cutText, 1, 3, "hello", 2, 4); + et.scheduleTest(et.insertText, "ee", 2, "heeello", 2); + et.scheduleTest(et.cutText, 2, 4, "hello", 2, 4); + + et.scheduleTest(et.deleteText, 1, 3, "hlo"); + et.scheduleTest(et.pasteText, 1, "heelo"); + + var testRun = new editableTextTestRun(); + testRun.add(et); + testRun.run(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="HyperText accessible should get focus when the caret is positioned inside of it, text is changed or copied into clipboard by ATs" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=524115"> + Mozilla Bug 524115 + </a> + <a target="_blank" + title="Cache rendered text on a11y side" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660"> + Mozilla Bug 626660 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="input" value="hello"/> + +</body> +</html> diff --git a/accessible/tests/mochitest/elm/a11y.ini b/accessible/tests/mochitest/elm/a11y.ini new file mode 100644 index 000000000..76fb4402b --- /dev/null +++ b/accessible/tests/mochitest/elm/a11y.ini @@ -0,0 +1,16 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/moz.png + !/dom/media/test/bug461281.ogg + +[test_HTMLSpec.html] +skip-if = buildapp == 'mulet' +[test_figure.html] +[test_listbox.xul] +[test_MathMLSpec.html] +[test_nsApplicationAcc.html] +[test_plugin.html] +skip-if = buildapp == 'mulet' +[test_canvas.html] +[test_shadowroot.html] diff --git a/accessible/tests/mochitest/elm/test_HTMLSpec.html b/accessible/tests/mochitest/elm/test_HTMLSpec.html new file mode 100644 index 000000000..f5f48ec56 --- /dev/null +++ b/accessible/tests/mochitest/elm/test_HTMLSpec.html @@ -0,0 +1,1671 @@ +<!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 type="application/javascript" + 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() + { + ////////////////////////////////////////////////////////////////////////// + // HTML:a@href + + var obj = { + role: ROLE_LINK, + states: STATE_LINKED, + actions: "jump", + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText, nsIAccessibleHyperLink ], + children: [ // all kids inherits linked state and jump action + { + role: ROLE_TEXT_LEAF, + states: STATE_LINKED, + actions: "jump" + } + ] + }; + 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_TEXT_CONTAINER, + 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_DOCUMENT, + states: STATE_READONLY, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + }; + testElm("article", obj); + + ////////////////////////////////////////////////////////////////////////// + // HTML:aside + obj = { + role: ROLE_NOTE, + 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_SECTION, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ], + children: [ { role: ROLE_PARAGRAPH } ] + }; + testElm("blockquote", obj); + + ////////////////////////////////////////////////////////////////////////// + // HTML:br contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + children: [ { 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: nsIAccessibleTable, + children: [ + { + role: ROLE_CAPTION, + relations: { + RELATION_LABEL_FOR: "table" + }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }, + { // td inside thead + role: ROLE_ROW, + 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, + textAttrs: { + 0: { }, + 6: { "text-line-through-style": "solid" } + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF } // HTML:del text + ] + }; + 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 + + todo(isAccessible("dialog"), "dialog element is not accessible"); + + ////////////////////////////////////////////////////////////////////////// + // 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 (windowless and windowed plugins) + + if (WIN) { + obj = { + role: ROLE_EMBEDDED_OBJECT, + states: STATE_UNAVAILABLE + }; + + testElm("embed_plugin_windowless", obj); + + obj = { + role: ROLE_EMBEDDED_OBJECT, + absentStates: STATE_UNAVAILABLE + }; + testElm("embed_plugin_windowed", obj); + } + + ////////////////////////////////////////////////////////////////////////// + // HTML:fieldset and HTML:legend + + obj = { + role: ROLE_GROUPING, + relations: { + RELATION_LABELLED_BY: "legend" + }, + children: [ + { + role: ROLE_LABEL, + 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_FOOTER, + attributes: { "xml-roles": "contentinfo" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("footer", obj); + + obj = { + role: ROLE_FOOTER, + absentAttributes: { "xml-roles": "contentinfo" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("footer_in_article", obj); + testElm("footer_in_section", obj); + + ////////////////////////////////////////////////////////////////////////// + // HTML:form + + obj = { + role: ROLE_FORM + }; + testElm("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 + + obj = { + role: ROLE_HEADING, + attributes: { "level": "1" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("h1", obj); + + obj = { + role: ROLE_HEADING, + attributes: { "level": "2" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("h2", obj); + + obj = { + role: ROLE_HEADING, + attributes: { "level": "3" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("h3", obj); + + obj = { + role: ROLE_HEADING, + attributes: { "level": "4" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("h4", obj); + + obj = { + role: ROLE_HEADING, + attributes: { "level": "5" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("h5", obj); + + obj = { + role: ROLE_HEADING, + attributes: { "level": "6" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("h6", obj); + + ////////////////////////////////////////////////////////////////////////// + // HTML:header + + obj = { + role: ROLE_HEADER, + attributes: { "xml-roles": "banner" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("header", obj); + + obj = { + role: ROLE_HEADER, + absentAttributes: { "xml-roles": "banner" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("header_in_article", obj); + testElm("header_in_section", 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 = { + TEXT_CONTAINER: [ + { 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_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 ], + children: [ + { + role: ROLE_ENTRY, + extraStates: EXT_STATE_EDITABLE | EXT_STATE_SINGLE_LINE, + actions: "activate", + interfaces: [ nsIAccessibleText, nsIAccessibleEditableText ], + children: [ + { role: ROLE_TEXT_LEAF } + ] + }, + { + role: ROLE_PUSHBUTTON, + actions: "press" + }, + { + role: ROLE_PUSHBUTTON, + actions: "press" + } + ] + }; + 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); + + ////////////////////////////////////////////////////////////////////////// + // 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:ins 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:ins text + ] + }; + 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:keygen + + obj = { + role: ROLE_COMBOBOX, + states: STATE_COLLAPSED | STATE_HASPOPUP, + extraStates: EXT_STATE_EXPANDABLE, + actions: "open", + children: [ + { COMBOBOX_LIST: [ + { role: ROLE_COMBOBOX_OPTION }, // high grade + { role: ROLE_COMBOBOX_OPTION } // medium grade + ] } + ] + }; + testElm("keygen", 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 = { + todo_role: ROLE_GROUPING, + 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, + textAttrs: { + 0: { }, + 6: { "background-color": "rgb(255, 255, 0)" } + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF } // HTML:mark text + ] + }; + testElm("mark_container", obj); + + ////////////////////////////////////////////////////////////////////////// + // HTML:math + + obj = { + role: ROLE_MATHML_MATH + }; + testElm("math", obj); + + ////////////////////////////////////////////////////////////////////////// + // HTML:menu + + obj = { + todo_role: ROLE_MENUPOPUP + }; + testElm("menu", obj); + + ////////////////////////////////////////////////////////////////////////// + // HTML:meter + + todo(isAccessible("meter"), "meter element is not accessible"); + + ////////////////////////////////////////////////////////////////////////// + // HTML:nav + + obj = { + role: ROLE_SECTION, + attributes: { "xml-roles": "navigation" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("nav", obj); + + ////////////////////////////////////////////////////////////////////////// + // HTML:object (windowless and windowed plugins) and HTML:param + + if (WIN) { + obj = { + role: ROLE_EMBEDDED_OBJECT, + states: STATE_UNAVAILABLE, + children: [ ] // no child for HTML:param + }; + testElm("object_plugin_windowless", obj); + + obj = { + role: ROLE_EMBEDDED_OBJECT, + absentStates: STATE_UNAVAILABLE + }; + testElm("object_plugin_windowed", 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_SECTION, + 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 + + obj = { + role: ROLE_SECTION, + attributes: { "xml-roles": "region" }, + interfaces: [ nsIAccessibleText, nsIAccessibleHyperText ] + }; + testElm("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: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 contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { }, + 6: { "text-position": "sub" } + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { role: ROLE_TEXT_LEAF } // HTML:sub text + ] + }; + testElm("sub_container", obj); + + ////////////////////////////////////////////////////////////////////////// + // HTML:sup contained by paragraph + + obj = { + role: ROLE_PARAGRAPH, + textAttrs: { + 0: { }, + 6: { "text-position": "super" } + }, + children: [ + { role: ROLE_TEXT_LEAF }, // plain text + { 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); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + </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, one will be eaten --> + <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_plugin_windowless" type="application/x-test" + width="300" height="300"></embed> + <embed id="embed_plugin_windowed" type="application/x-test" wmode="window" + width="300" height="300"></embed> + + <fieldset id="fieldset"> + <legend id="legend">legend</legend> + <input /> + </fieldset> + + <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> + <section> + <footer id="footer_in_section">Some copyright info</footer> + </section> + + <form id="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> + </article> + <section> + <header id="header_in_section">Not logo</header> + </section> + + <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"> + <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"> + <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> + + <p id="ins_container">normal<ins>Inserted</ins></p> + <p id="kbd_container">normal<kbd>cmd</kbd></p> + <keygen id="keygen" name="RSA public key" challenge="123456789" keytype="RSA"> + + <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" type="toolbar"> + <li> + <menu label="File"> + <button type="button" onclick="new()">New...</button> + <button type="button" onclick="save()">Save...</button> + </menu> + </li> + <li> + <menu label="Edit"> + <button type="button" onclick="cut()">Cut...</button> + <button type="button" onclick="copy()">Copy...</button> + <button type="button" onclick="paste()">Paste...</button> + </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_plugin_windowless" type="application/x-test" + width="300" height="300"> + <param name="foo" value="bar"> + </object> + <object id="object_plugin_windowed" type="application/x-test" wmode="window" + 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> + <p id="small_container">normal<small>small</small></p> + <span id="span"></span> + <p id="strong_container">normal<strong>strong</strong></p> + <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 000000000..80f1e9e70 --- /dev/null +++ b/accessible/tests/mochitest/elm/test_MathMLSpec.html @@ -0,0 +1,620 @@ +<!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 type="application/javascript" + 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 + + 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); + + ////////////////////////////////////////////////////////////////////////// + // mfenced + + obj = { + role: ROLE_MATHML_FENCED, + attributes: { open: "]", close: "[", separators: "." } + }; + 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); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + + </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_canvas.html b/accessible/tests/mochitest/elm/test_canvas.html new file mode 100644 index 000000000..b4b743800 --- /dev/null +++ b/accessible/tests/mochitest/elm/test_canvas.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<html> +<head> + <title>Accessible boundaries for hit regions</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../layout.js"></script> + + <script type="application/javascript"> + var kX = 10, kY = 10, kWidth = 150, kHeight = 100; + function doTest() + { + var canv = document.getElementById("c"); + var context = canv.getContext('2d'); + var element = document.getElementById("showA"); + context.beginPath(); + context.rect(kX, kY, kWidth, kHeight); + context.addHitRegion({control: element}); + + var input = getAccessible("showA"); + var [cnvX, cnvY, cnvWidth, cnvHeight] = getBoundsForDOMElm(canv); + var [accX, accY, accWidth, accHeight] = getBounds(input); + + var [x, y, w, h] = CSSToDevicePixels(window, kX, kY, kWidth, kHeight); + is(accX, cnvX + x, "wrong accX"); + is(accY, cnvY + y, "wrong accY"); + is(accWidth, w, "wrong accWidth"); + is(accHeight, h, "wrong accHeight"); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(function() { + SpecialPowers.pushPrefEnv({"set": [['canvas.hitregions.enabled', true]]}, doTest); + }); + + </script> +</head> +<body> + + <canvas id="c"> + <input id="showA" type="checkbox"><label for="showA"> Show As </label> + </canvas> + +</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 000000000..ba1bb489f --- /dev/null +++ b/accessible/tests/mochitest/elm/test_figure.html @@ -0,0 +1,62 @@ +<!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 type="application/javascript" + 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.xul b/accessible/tests/mochitest/elm/test_listbox.xul new file mode 100644 index 000000000..e284b3e5c --- /dev/null +++ b/accessible/tests/mochitest/elm/test_listbox.xul @@ -0,0 +1,74 @@ +<?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 type="application/javascript" + 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 listbox = null, 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: "/> + <listbox id="listbox1"> + <listitem label="item1" id="item1"/> + <listitem label="item2" id="item2"/> + </listbox> + </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 000000000..58763e437 --- /dev/null +++ b/accessible/tests/mochitest/elm/test_nsApplicationAcc.html @@ -0,0 +1,75 @@ +<html> + +<head> + <title>application accessible name</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + 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 bundleServ = + Components.classes["@mozilla.org/intl/stringbundle;1"]. + getService(Components.interfaces.nsIStringBundleService); + var brandBundle = + bundleServ.createBundle("chrome://branding/locale/brand.properties"); + + var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]. + getService(Components.interfaces.nsIXULAppInfo); + + // nsIAccessible::name + var applicationName = ""; + if (LINUX || SOLARIS) { + applicationName = 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, appInfo.name, "Wrong application name"); + is(accessible.appVersion, appInfo.version, "Wrong application version"); + is(accessible.platformName, "Gecko", "Wrong platform name"); + is(accessible.platformVersion, 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_plugin.html b/accessible/tests/mochitest/elm/test_plugin.html new file mode 100644 index 000000000..3350e6ccc --- /dev/null +++ b/accessible/tests/mochitest/elm/test_plugin.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<html> +<head> + <title>Plugin tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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() + { + if (!WIN) { + ok(true, + "It's Windows specific test. Feel free to extend the test."); + + SimpleTest.finish(); + return; + } + + testStates("plugin-windowless", STATE_UNAVAILABLE); + testAccessibleTree("plugin-windowless", { EMBEDDED_OBJECT: [ ] }); + + testStates("plugin-windowless-fallback", STATE_UNAVAILABLE); + testAccessibleTree("plugin-windowless-fallback", { EMBEDDED_OBJECT: [ ] }); + + testStates("plugin-windowed", 0, 0, STATE_UNAVAILABLE); + testAccessibleTree("plugin-windowed", { EMBEDDED_OBJECT: [ { NOTHING: [] } ] }); + + testStates("plugin-windowed-fallback", 0, 0, STATE_UNAVAILABLE); + testAccessibleTree("plugin-windowed-fallback", + { EMBEDDED_OBJECT: [ { NOTHING: [] } ] }); + + // make sure we handle content changes under the plugin. + getNode("fallback1").setAttribute("href", "5"); + getNode("fallback2").setAttribute("href", "5"); + SimpleTest.executeSoon(function () { SimpleTest.finish(); }); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + </script> +</head> +<body> + + <a target="_blank" + title="Embed and object HTML tags should be given an accessible role of embedded object" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=485270">Bug 485270</a> + <a target="_blank" + title="Embedded object accessibles for inaccessible/windowless plugins should not expose a NULL child" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=816856">Bug 816856</a> + <a target="_blank" + title="Updating accessible tree for plugin with fallback shouldn't crash" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=881636">Bug 881636</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <embed id="plugin-windowless" type="application/x-test" + width="300" height="300"></embed> + <embed id="plugin-windowed" type="application/x-test" wmode="window" + width="300" height="300"></embed> + <embed id="plugin-windowless-fallback" type="application/x-test" + width="300" height="300"><a id="fallback1">foo</a></embed> + <embed id="plugin-windowed-fallback" type="application/x-test" wmode="window" + width="300" height="300"><a id="fallback2">foo</a></embed> +</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 000000000..e4c39b8d6 --- /dev/null +++ b/accessible/tests/mochitest/elm/test_shadowroot.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<html> +<head> + <title>ShadowRoot tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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() + { + testElm("component", { + role: ROLE_GROUPING, + children: [ + { + role: ROLE_PUSHBUTTON, + }, + { + role: ROLE_LINK, + }, + ] + }); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </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> + + <div role="group" id="component"></div> + <script> + var component = document.getElementById('component'); + var shadow = component.createShadowRoot(); + + shadow.innerHTML = '<button>Hello</button>' + + '<a href="#"> World</a>'; + </script> + +</body> +</html> diff --git a/accessible/tests/mochitest/events.js b/accessible/tests/mochitest/events.js new file mode 100644 index 000000000..d1e5ec8a0 --- /dev/null +++ b/accessible/tests/mochitest/events.js @@ -0,0 +1,2329 @@ +//////////////////////////////////////////////////////////////////////////////// +// Constants + +const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT; +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 + +Components.utils.import("resource://gre/modules/Services.jsm"); + +/** + * 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 = ""; + +/** + * 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 (target instanceof nsIDOMNode && + 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.ownerDocument.defaultView); +} + +/** + * 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) + return aTestFunc(); + + 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. + */ + 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 > 0 && + this.mScenarios[matchIdx].length > 0) { + ok(false, + "We have a matched scenario at index " + matchIdx + " already."); + } + + if (matchIdx == -1 || eventSeq.length > 0) + 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. + */ + 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 hanlded not expected event of unique type + // (i.e. event types are matched, targets differs). + if (!checker.unexpected && checker.unique && + eventQueue.compareEventTypes(checker, aEvent)) { + var isExppected = 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 == 0) { + 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 == 0) { + 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 = eventSeq[idx].target; + if (!target) { + ok(false, "no target for DOM event!"); + return false; + } + var phase = eventQueue.getEventPhase(eventSeq[idx]); + target.ownerDocument.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 = eventSeq[idx].target; + var phase = eventQueue.getEventPhase(eventSeq[idx]); + target.ownerDocument.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 = new Array(); + 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 (aEventOrChecker instanceof nsIDOMEvent) + 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 (aEventOrChecker instanceof nsIDOMEvent) + return prettyName(aEventOrChecker.originalTarget); + + if (aEventOrChecker instanceof nsIDOMEvent) + 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.compareEventTypes = + function eventQueue_compareEventTypes(aChecker, aEvent) +{ + var eventType = (aEvent instanceof nsIDOMEvent) ? + 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 = (aEvent instanceof nsIDOMEvent) ? + 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 = (aEvent instanceof nsIDOMEvent) ? + 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 (aOrigEvent instanceof nsIDOMEvent) { + 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 = new Array(); + 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 = new Array(); + + // 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 instanceof nsIDOMDocument) { + targetNode = + this.DOMNode.body ? this.DOMNode.body : this.DOMNode.documentElement; + } + + // Scroll the node into view, otherwise synth click may fail. + if (targetNode instanceof nsIDOMHTMLElement) { + targetNode.scrollIntoView(true); + } else if (targetNode instanceof nsIDOMXULElement) { + var targetAcc = getAccessible(targetNode); + targetAcc.scrollTo(SCROLL_TYPE_ANYWHERE); + } + + var x = 1, y = 1; + if (aArgs && ("where" in aArgs) && aArgs.where == "right") { + if (targetNode instanceof nsIDOMHTMLElement) + x = targetNode.offsetWidth - 1; + else if (targetNode instanceof nsIDOMXULElement) + x = targetNode.boxObject.width - 1; + } + 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"; + } +} + +/** + * Mouse move invoker. + */ +function synthMouseMove(aID, aCheckerOrEventSeq) +{ + this.__proto__ = new synthAction(aID, aCheckerOrEventSeq); + + this.invoke = function synthMouseMove_invoke() + { + synthesizeMouse(this.DOMNode, 1, 1, { type: "mousemove" }); + synthesizeMouse(this.DOMNode, 2, 2, { 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 (atl + 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 instanceof Components.interfaces.nsIDOMNSEditableElement && + this.DOMNode.editor || + this.DOMNode instanceof Components.interfaces.nsIDOMXULTextBoxElement) { + 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("VK_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 (this.DOMNode instanceof Components.interfaces.nsIDOMHTMLInputElement || + this.DOMNode instanceof Components.interfaces.nsIDOMXULTextBoxElement) { + 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, aID)); + } else { + this.__proto__ = new synthEndKey(aID, + new caretMoveChecker(aCaretOffset, 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, aID)); + + this.invoke = function moveToPrevLineEnd_invoke() + { + synthesizeKey("VK_UP", { }); + + if (MAC) + synthesizeKey("VK_RIGHT", { metaKey: true }); + else + synthesizeKey("VK_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, aID)); + } else { + this.__proto__ = new synthHomeKey(aID, + new caretMoveChecker(aCaretOffset, 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, aID)); + } else { + this.__proto__ = new synthKey(aID, "VK_HOME", { ctrlKey: true }, + new caretMoveChecker(0, 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.ownerDocument.defaultView.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, 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, 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, aTargetOrFunc, aTargetFuncArg, + aIsAsync) +{ + this.__proto__ = new invokerChecker(EVENT_TEXT_CARET_MOVED, + aTargetOrFunc, aTargetFuncArg, aIsAsync); + + this.check = function caretMoveChecker_check(aEvent) + { + is(aEvent.QueryInterface(nsIAccessibleCaretMoveEvent).caretOffset, + aCaretOffset, + "Wrong caret offset for " + prettyName(aEvent.accessible)); + } +} + +function asyncCaretMoveChecker(aCaretOffset, aTargetOrFunc, aTargetFuncArg) +{ + this.__proto__ = new caretMoveChecker(aCaretOffset, aTargetOrFunc, + aTargetFuncArg, true); +} + +/** + * Text selection change checker. + */ +function textSelectionChecker(aID, aStartOffset, aEndOffset) +{ + 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); + } + } +} + +/** + * 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.toString(), 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.toString() == 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 = +{ + 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 "[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", false); + } else { + // Remove observer when there are no more applicants only. + // '< 0' case should not happen, but just in case: removeObserver() will throw. + if (--gA11yEventApplicantsCount <= 0) + Services.obs.removeObserver(gA11yEventObserver, "accessible-event"); + } +} + +function addA11yEventListener(aEventType, aEventHandler) +{ + if (!(aEventType in gA11yEventListeners)) + gA11yEventListeners[aEventType] = new Array(); + + 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 = document instanceof nsIDOMHTMLDocument ? + "div" : "description"; + + var container = document.createElement(containerTagName); + if (aHasIndent) + container.setAttribute("style", "padding-left: 10px;"); + + if (aPreEmphText) { + var inlineTagName = document instanceof nsIDOMHTMLDocument ? + "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(); + } + + var item = this; + + 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 000000000..4ea7c9d10 --- /dev/null +++ b/accessible/tests/mochitest/events/a11y.ini @@ -0,0 +1,67 @@ +[DEFAULT] +support-files = + docload_wnd.html + focus.html + scroll.html + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + +[test_aria_alert.html] +[test_aria_menu.html] +[test_aria_objattr.html] +[test_aria_owns.html] +[test_aria_statechange.html] +[test_attrs.html] +[test_bug1322593.html] +[test_bug1322593-2.html] +[test_caretmove.html] +[test_caretmove.xul] +[test_coalescence.html] +[test_contextmenu.html] +[test_descrchange.html] +[test_docload.html] +[test_docload.xul] +skip-if = buildapp == 'mulet' +[test_docload_aria.html] +[test_dragndrop.html] +[test_flush.html] +[test_focus_aria_activedescendant.html] +[test_focus_autocomplete.xul] +# Disabled on Linux and Windows due to frequent failures - bug 695019, bug 890795 +skip-if = os == 'win' || os == 'linux' +[test_focus_browserui.xul] +[test_focus_canvas.html] +[test_focus_contextmenu.xul] +[test_focus_controls.html] +[test_focus_dialog.html] +[test_focus_doc.html] +[test_focus_general.html] +[test_focus_general.xul] +[test_focus_listcontrols.xul] +[test_focus_menu.xul] +[test_focus_name.html] +[test_focus_selects.html] +[test_focus_tabbox.xul] +[test_focus_tree.xul] +[test_fromUserInput.html] +[test_label.xul] +[test_menu.xul] +[test_mutation.html] +[test_mutation.xhtml] +[test_namechange.xul] +[test_namechange.html] +[test_scroll.xul] +[test_scroll_caret.xul] +[test_selection.html] +skip-if = buildapp == 'mulet' || os == 'mac' +[test_selection.xul] +skip-if = os == 'mac' +[test_selection_aria.html] +[test_statechange.html] +[test_text.html] +[test_text_alg.html] +[test_textattrchange.html] +[test_textselchange.html] +[test_tree.xul] +[test_valuechange.html] +skip-if = os == 'mac' diff --git a/accessible/tests/mochitest/events/docload_wnd.html b/accessible/tests/mochitest/events/docload_wnd.html new file mode 100644 index 000000000..86ddfac5e --- /dev/null +++ b/accessible/tests/mochitest/events/docload_wnd.html @@ -0,0 +1,39 @@ +<html> +<head> + <title>Accessible events testing for document</title> + <script> + const STATE_BUSY = Components.interfaces.nsIAccessibleStates.STATE_BUSY; + + var gService = null; + function waitForDocLoad() + { + if (!gService) { + gService = Components.classes["@mozilla.org/accessibilityService;1"]. + getService(Components.interfaces.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/focus.html b/accessible/tests/mochitest/events/focus.html new file mode 100644 index 000000000..ab055df82 --- /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 000000000..562e0a382 --- /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/test_aria_alert.html b/accessible/tests/mochitest/events/test_aria_alert.html new file mode 100644 index 000000000..2dab35723 --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_alert.html @@ -0,0 +1,92 @@ +<html> + +<head> + <title>ARIA alert event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 000000000..5ac595ebf --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_menu.html @@ -0,0 +1,285 @@ +<html> + +<head> + <title>ARIA menu events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 000000000..5f16ba794 --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_objattr.html @@ -0,0 +1,118 @@ +<html> + +<head> + <title>Accessible ARIA object attribute changes</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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; + }; + } + + function updateARIAHidden(aID, aIsDefined, aChildId) + { + this.__proto__ = new updateAttribute(aID, "aria-hidden", + aIsDefined ? "true" : "false"); + + this.finalCheck = function updateARIAHidden() + { + if (aIsDefined) { + testAttrs(aID, {"hidden" : "true"}, true); + testAttrs(aChildId, {"hidden" : "true"}, true); + } else { + testAbsentAttrs(aID, { "hidden": "true"}); + testAbsentAttrs(aChildId, { "hidden": "true"}); + } + } + } + + // Debug stuff. + // gA11yEventDumpID = "eventdump"; + //gA11yEventDumpToConsole = true; + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new updateARIAHidden("hideable", true, "hideable_child")); + gQueue.push(new updateARIAHidden("hideable", false, "hideable_child")); + + 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> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=581096" + title="Add support for aria-hidden"> + Mozilla Bug 581096 + </a> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=640707" + title="Add event support for aria-sort"> + Mozilla Bug 640707 + </a> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=640707" + title="Expand support for aria attribute change events"> + Mozilla Bug 563862 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <div id="hideable"><div id="hideable_child">Hi</div><div>there</div></div> + + <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 000000000..a415a6d8d --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_owns.html @@ -0,0 +1,129 @@ +<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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 000000000..d8c833157 --- /dev/null +++ b/accessible/tests/mochitest/events/test_aria_statechange.html @@ -0,0 +1,208 @@ +<html> + +<head> + <title>ARIA state change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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"> + + + /** + * 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 setAttrOfMixedType(aID, aAttr, aState, aValue) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new stateChangeChecker(aState, kOrdinalState, + aValue == "true", this.DOMNode) + ]; + + if (hasState(aID, STATE_MIXED) || aValue == "mixed") { + this.eventSeq.push( + new stateChangeChecker(STATE_MIXED, kOrdinalState, + aValue == "mixed", this.DOMNode) + ); + } + + this.invoke = function setAttrOfMixedType_invoke() + { + this.DOMNode.setAttribute(aAttr, aValue); + }; + + this.getID = function setAttrOfMixedType_getID() + { + return prettyName(aID) + " " + aAttr + " changed to '" + aValue + "'"; + }; + } + + function setPressed(aID, aValue) + { + this.__proto__ = + new setAttrOfMixedType(aID, "aria-pressed", STATE_PRESSED, aValue); + } + + function setChecked(aID, aValue) + { + this.__proto__ = + new setAttrOfMixedType(aID, "aria-checked", STATE_CHECKED, aValue); + } + + function buildQueueForAttr(aList, aQueue, aID, aInvokerFunc) + { + for (var i = 0; i < aList.length; i++) { + for (var j = i + 1; j < aList.length; j++) { + // XXX: changes from/to "undefined"/"" shouldn't fire state change + // events, bug 472142. + aQueue.push(new aInvokerFunc(aID, aList[i])); + aQueue.push(new aInvokerFunc(aID, aList[j])); + } + } + } + + function buildQueueForAttrOfMixedType(aQueue, aID, aInvokerFunc) + { + var list = [ "", "undefined", "false", "true", "mixed" ]; + buildQueueForAttr(list, aQueue, aID, aInvokerFunc); + } + + function buildQueueForAttrOfBoolType(aQueue, aID, aInvokerFunc) + { + var list = [ "", "undefined", "false", "true" ]; + buildQueueForAttr(list, aQueue, aID, aInvokerFunc); + } + + function doTests() + { + gQueue = new eventQueue(); + + 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)); + + buildQueueForAttrOfMixedType(gQueue, "pressable", setPressed); + buildQueueForAttrOfMixedType(gQueue, "pressable_native", setPressed); + buildQueueForAttrOfMixedType(gQueue, "checkable", setChecked); + buildQueueForAttrOfBoolType(gQueue, "checkableBool", setChecked); + + 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=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> + + <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> +</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 000000000..1d5577572 --- /dev/null +++ b/accessible/tests/mochitest/events/test_attrs.html @@ -0,0 +1,90 @@ +<html> + +<head> + <title>Event object attributes tests</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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"; + var checker = + new eventFromInputChecker(EVENT_FOCUS, id, "false", noTargetId); + gQueue.push(new synthFocus(id, checker)); + + if (!MAC) { // Mac failure is bug 541093 + var 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 000000000..20136d393 --- /dev/null +++ b/accessible/tests/mochitest/events/test_bug1322593-2.html @@ -0,0 +1,83 @@ +<html> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 000000000..38786d0b9 --- /dev/null +++ b/accessible/tests/mochitest/events/test_bug1322593.html @@ -0,0 +1,80 @@ +<html> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 000000000..6d39c4ef6 --- /dev/null +++ b/accessible/tests/mochitest/events/test_caretmove.html @@ -0,0 +1,140 @@ +<html> + +<head> + <title>Accessible caret move events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + /** + * Click checker. + */ + function clickChecker(aCaretOffset, aID, aExtraNodeOrID, aExtraCaretOffset) + { + this.__proto__ = new caretMoveChecker(aCaretOffset, 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; + + // gA11yEventDumpID = "eventdump"; // debug stuff + //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, id))); + gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, id))); + gQueue.push(new synthClick(id, new caretMoveChecker(0, id))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id))); + + id = "textarea"; + gQueue.push(new synthClick(id, new caretMoveChecker(0, id))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id))); + gQueue.push(new synthDownKey(id, new caretMoveChecker(12, id))); + + id = "textarea_wrapped"; + gQueue.push(new setCaretOffset(id, 4, id)); + gQueue.push(new synthLeftKey(id, new caretMoveChecker(4, id))); + + id = "p"; + gQueue.push(new synthClick(id, new caretMoveChecker(0, id))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id))); + gQueue.push(new synthDownKey(id, new caretMoveChecker(6, id))); + + id = "p1_in_div"; + gQueue.push(new synthClick(id, new clickChecker(0, id, "p2_in_div", -1))); + + id = "p"; + gQueue.push(new synthShiftTab(id, new caretMoveChecker(0, id))); + id = "textarea"; + gQueue.push(new synthShiftTab(id, new caretMoveChecker(12, id))); + id = "p"; + gQueue.push(new synthTab(id, new caretMoveChecker(0, 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> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_caretmove.xul b/accessible/tests/mochitest/events/test_caretmove.xul new file mode 100644 index 000000000..cf4dcd483 --- /dev/null +++ b/accessible/tests/mochitest/events/test_caretmove.xul @@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Caret move event testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript" + src="../common.js" /> + <script type="application/javascript" + src="../events.js" /> + + <script type="application/javascript"> + /** + * Do tests. + */ + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTests() + { + if (MAC) { + todo(false, "Make these tests pass on OSX (bug 650294)"); + SimpleTest.finish(); + return; + } + + gQueue = new eventQueue(EVENT_TEXT_CARET_MOVED); + + var id = "textbox"; + var input = getNode(id).inputField; + gQueue.push(new synthFocus(id, new caretMoveChecker(5, input))); + gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, input))); + gQueue.push(new synthHomeKey(id, new caretMoveChecker(0, input))); + gQueue.push(new synthRightKey(id, new caretMoveChecker(1, input))); + + 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=634240" + title="No caret move events are fired for XUL textbox accessible"> + Mozilla Bug 634240 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <textbox id="textbox" value="hello"/> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_coalescence.html b/accessible/tests/mochitest/events/test_coalescence.html new file mode 100644 index 000000000..d95ef99b0 --- /dev/null +++ b/accessible/tests/mochitest/events/test_coalescence.html @@ -0,0 +1,864 @@ +<html> + +<head> + <title>Accessible mutation events coalescence testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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.parentNode.removeChild(aNode); + 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; + } + } + + this.getEventType = function coalescenceBase_getEventType(aAction) + { + switch (aAction) { + case kRemoveElm: case kHideElm: + return EVENT_HIDE; + case kAddElm: case kShowElm: + return EVENT_SHOW; + } + } + + 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.parentNode.removeChild(this.child1); + this.child2.parentNode.removeChild(this.child2); + 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 000000000..065e1b50e --- /dev/null +++ b/accessible/tests/mochitest/events/test_contextmenu.html @@ -0,0 +1,139 @@ +<html> + +<head> + <title>Context menu tests</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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("VK_DOWN", { }); + } + + 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("VK_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(); + } + + 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 000000000..d51318532 --- /dev/null +++ b/accessible/tests/mochitest/events/test_descrchange.html @@ -0,0 +1,85 @@ +<html> + +<head> + <title>Accessible description change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 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; + function doTests() + { + gQueue = new eventQueue(); + + 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"))); + + gQueue.push(new setAttr("tst2", "title", "title", + new invokerChecker(EVENT_NAME_CHANGE, "tst2"))); + + 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=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="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_docload.html b/accessible/tests/mochitest/events/test_docload.html new file mode 100644 index 000000000..a530592ee --- /dev/null +++ b/accessible/tests/mochitest/events/test_docload.html @@ -0,0 +1,360 @@ +<html> + +<head> + <title>Accessible events testing for document</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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"> + // Front end stuff sometimes likes to stuff things in the hidden window(s) + // in which case there's accessibles for that content. + Components.utils.import("resource://gre/modules/Services.jsm"); + + // Force the creation of an accessible for the hidden window's document. + var doc = Services.appShell.hiddenDOMWindow.document; + gAccService.getAccessibleFor(doc); + + // The private hidden window will be lazily created that's why we need to do + // it here *before* loading '../events.js' or else we'll have a duplicate + // reorder event. + var privateDoc = Services.appShell.hiddenPrivateDOMWindow.document; + + // Force the creation of an accessible for the private hidden window's doc. + gAccService.getAccessibleFor(privateDoc); + </script> + + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function changeIframeSrc(aIdentifier, aURL) + { + this.DOMNode = getNode(aIdentifier); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getAccessible(this.DOMNode)), + new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getIframeDoc) + ]; + + this.invoke = function changeIframeSrc_invoke() + { + this.DOMNode.src = aURL; + } + + this.finalCheck = function changeIframeSrc_finalCheck() + { + var accTree = { + role: ROLE_INTERNAL_FRAME, + children: [ + { + role: ROLE_DOCUMENT, + name: aURL == "about:" ? "About:" : aURL + } + ] + }; + + testAccessibleTree(this.DOMNode, accTree); + } + + this.getID = function changeIframeSrc_getID() + { + return "change iframe src on " + aURL; + } + + function getIframeDoc() + { + return getAccessible(getNode(aIdentifier).contentDocument); + } + } + + const kHide = 1; + const kShow = 2; + const kRemove = 3; + function morphIFrame(aIdentifier, aAction) + { + this.DOMNode = getNode(aIdentifier); + this.IFrameContainerDOMNode = this.DOMNode.parentNode; + + this.eventSeq = []; + + var checker = null; + if (aAction == kShow) + checker = new invokerChecker(EVENT_SHOW, this.DOMNode); + else + checker = new invokerChecker(EVENT_HIDE, this.DOMNode); + this.eventSeq.push(checker); + + var reorderChecker = + new invokerChecker(EVENT_REORDER, this.IFrameContainerDOMNode); + this.eventSeq.push(reorderChecker); + + this.invoke = function morphIFrame_invoke() + { + if (aAction == kHide) { + this.DOMNode.style.display = "none"; + } else if (aAction == kShow) { + this.DOMNode.style.display = "block"; + } else { + this.IFrameContainerDOMNode.removeChild(this.DOMNode); + } + } + + this.finalCheck = function morphIFrame_finalCheck() + { + var accTree = { + role: ROLE_SECTION, + children: (aAction == kHide || aAction == kRemove) ? [ ] : + [ + { + role: ROLE_INTERNAL_FRAME, + children: [ + { role: ROLE_DOCUMENT } + ] + } + ] + }; + + testAccessibleTree(this.IFrameContainerDOMNode, accTree); + } + + this.getID = function morphIFrame_getID() + { + if (aAction == kRemove) + return "remove iframe"; + + return "change display style of iframe to " + + ((aAction == kHide) ? "none" : "block"); + } + } + + function makeIFrameVisible(aID) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.DOMNode.parentNode) + ]; + + this.invoke = function makeIFrameVisible_invoke() + { + this.DOMNode.style.visibility = "visible"; + } + + this.getID = function makeIFrameVisible_getID() + { + return "The accessible for DOM document loaded before it's shown shouldn't have busy state."; + } + } + + function openDialogWnd(aURL) + { + // Get application root accessible. + var docAcc = getAccessible(document); + while (docAcc) { + this.mRootAcc = docAcc; + try { + docAcc = docAcc.parent; + } catch (e) { + ok(false, "Can't get parent for " + prettyName(docAcc)); + throw e; + } + } + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.mRootAcc) + ]; + + this.invoke = function openDialogWnd_invoke() + { + this.mDialog = window.openDialog(aURL); + } + + this.finalCheck = function openDialogWnd_finalCheck() + { + this.finalCheckImpl(); + } + + this.finalCheckImpl = function openDialogWnd_finalCheckImpl() + { + var accTree = { + role: ROLE_APP_ROOT, + children: [ + { + role: ROLE_CHROME_WINDOW + }, + { + role: ROLE_CHROME_WINDOW + }, + { + role: ROLE_CHROME_WINDOW + }, + { + role: ROLE_CHROME_WINDOW + } + ] + }; + + testAccessibleTree(this.mRootAcc, accTree); + + var dlgDoc = this.mDialog.document; + ok(isAccessibleInCache(dlgDoc), + "The document accessible for '" + aURL + "' is not in cache!"); + + this.mDialog.close(); + + // close() is asynchronous. + SimpleTest.executeSoon(function() { + ok(!isAccessibleInCache(dlgDoc), + "The document accessible for '" + aURL + "' is in cache still!"); + }); + } + + this.getID = function openDialogWnd_getID() + { + return "open dialog '" + aURL + "'"; + } + } + + function openWndShutdownDoc() + { + this.__proto__ = + new openDialogWnd("../events/docload_wnd.html"); + + var thisObj = this; + var docChecker = { + type: EVENT_HIDE, + get target() + { + var iframe = this.invoker.mDialog.document.getElementById("iframe"); + this.invoker.iframeDoc = iframe.contentDocument; + return iframe; + }, + get targetDescr() + { + return "inner iframe of docload_wnd.html document"; + }, + invoker: thisObj + }; + + this.eventSeq.push(docChecker); + + this.finalCheck = function openWndShutdownDoc_finalCheck() + { + // After timeout after event hide for iframe was handled the document + // accessible for iframe's document is in cache still. + ok(!isAccessibleInCache(this.iframeDoc), + "The document accessible for iframe is in cache still after iframe hide!"); + + this.finalCheckImpl(); + + // After the window is closed all alive subdocument accessibles should + // be shut down. + ok(!isAccessibleInCache(this.iframeDoc), + "The document accessible for iframe is in cache still!"); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + + // Debug stuff. + // gA11yEventDumpID = "eventdump"; + //gA11yEventDumpToConsole = true; + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new changeIframeSrc("iframe", "about:")); + gQueue.push(new changeIframeSrc("iframe", "about:buildconfig")); + gQueue.push(new morphIFrame("iframe", kHide)); + gQueue.push(new morphIFrame("iframe", kShow)); + gQueue.push(new morphIFrame("iframe", kRemove)); + gQueue.push(new makeIFrameVisible("iframe2")); + gQueue.push(new openDialogWnd("about:")); + gQueue.push(new openWndShutdownDoc()); + + gQueue.onFinish = doLastCallTests; + + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + function doLastCallTests() + { + ////////////////////////////////////////////////////////////////////////// + // makeIFrameVisible() test, part2 + + // 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. + var iframeDoc = getAccessible("iframe2").firstChild; + testStates(iframeDoc, 0, 0, STATE_BUSY); + } + + 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><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=506206" + title="Fire event_reorder application root accessible"> + Mozilla Bug 506206 + </a><br> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=566103" + title="Reorganize accessible document handling"> + Mozilla Bug 566103 + </a><br> + <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> + <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> + <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> + <div id="testContainer2"><iframe id="iframe2" src="about:" style="visibility: hidden;"></iframe></div> + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_docload.xul b/accessible/tests/mochitest/events/test_docload.xul new file mode 100644 index 000000000..4b07b0e72 --- /dev/null +++ b/accessible/tests/mochitest/events/test_docload.xul @@ -0,0 +1,243 @@ +<?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 Loading Document Events Test."> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <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="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Invoker checkers. + function stateBusyChecker(aIsEnabled) + { + this.type = EVENT_STATE_CHANGE; + this.__defineGetter__("target", currentTabDocument); + + this.check = function stateBusyChecker_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_BUSY, "Wrong state of statechange event."); + is(event.isEnabled, aIsEnabled, + "Wrong value of state of statechange event"); + + testStates(event.accessible, (aIsEnabled ? STATE_BUSY : 0), 0, + (aIsEnabled ? 0 : STATE_BUSY), 0); + } + } + + function documentReloadChecker(aIsFromUserInput) + { + this.type = EVENT_DOCUMENT_RELOAD; + this.__defineGetter__("target", currentTabDocument); + + this.check = function documentReloadChecker_check(aEvent) + { + is(aEvent.isFromUserInput, aIsFromUserInput, + "Wrong value of isFromUserInput"); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Invokers. + + /** + * Load URI. + */ + function loadURIInvoker(aURI) + { + this.invoke = function loadURIInvoker_invoke() + { + tabBrowser().loadURI(aURI); + } + + this.eventSeq = [ + // We don't expect state change event for busy true since things happen + // quickly and it's coalesced. + new asyncInvokerChecker(EVENT_REORDER, currentBrowser), + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new stateBusyChecker(false) + ]; + + this.getID = function loadURIInvoker_getID() + { + return "load uri " + aURI; + } + } + + /** + * Load the document having sub document. No document loading events for + * nested document. + */ + function loadNestedDocURIInvoker(aNestedDocURI) + { + this.__proto__ = new loadURIInvoker(aNestedDocURI); + + // Remove reorder event checker since the event is likely coalesced by + // reorder event on Firefox UI (refer to bug 759670 for details). + this.eventSeq.shift(); + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getNestedDoc), + new invokerChecker(EVENT_STATE_CHANGE, getNestedDoc) + ]; + + function getNestedDoc() + { + var iframeNodes = currentTabDocument().getElementsByTagName("iframe"); + return iframeNodes && iframeNodes.length > 0 ? + iframeNodes[0].firstChild : null; + } + } + + /** + * Reload the page by F5 (isFromUserInput flag is true). + */ + function userReloadInvoker() + { + this.invoke = function userReloadInvoker_invoke() + { + synthesizeKey("VK_F5", {}, browserWindow()); + } + + this.eventSeq = [ + new documentReloadChecker(true), + new asyncInvokerChecker(EVENT_REORDER, currentBrowser), + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new stateBusyChecker(false) + ]; + + this.getID = function userReloadInvoker_getID() + { + return "user reload page"; + } + } + + /** + * Reload the page (isFromUserInput flag is false). + */ + function reloadInvoker() + { + this.invoke = function reloadInvoker_invoke() + { + tabBrowser().reload(); + } + + this.eventSeq = [ + new documentReloadChecker(false), + new asyncInvokerChecker(EVENT_REORDER, currentBrowser), + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new stateBusyChecker(false) + ]; + + this.getID = function reloadInvoker_getID() + { + return "reload page"; + } + } + + /** + * Load wrong URI what results in error page loading. + */ + function loadErrorPageInvoker(aURL, aURLDescr) + { + this.invoke = function loadErrorPageInvoker_invoke() + { + tabBrowser().loadURI(aURL); + } + + this.eventSeq = [ + // We don't expect state change for busy true, load stopped events since + // things happen quickly and it's coalesced. + new asyncInvokerChecker(EVENT_REORDER, currentBrowser), + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new stateBusyChecker(false) + ]; + + this.getID = function loadErrorPageInvoker_getID() + { + return "load error page: '" + aURLDescr + "'"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Tests + + //gA11yEventDumpToConsole = true; // debug + //gA11yEventDumpFeature = "parentchain:reorder"; + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + var dataURL = + "data:text/html,<html><body><iframe src='http://example.com'></iframe></body></html>"; + gQueue.push(new loadNestedDocURIInvoker(dataURL)); + + gQueue.push(new loadURIInvoker("about:")); + gQueue.push(new userReloadInvoker()); + gQueue.push(new loadURIInvoker("about:mozilla")); + gQueue.push(new reloadInvoker()); + gQueue.push(new loadErrorPageInvoker("www.wronguri.wronguri", + "Server not found")); + gQueue.push(new loadErrorPageInvoker("https://nocert.example.com:443", + "Untrusted Connection")); + + gQueue.onFinish = function() { closeBrowserWindow(); } + gQueue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(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=566103" + title=" reorganize accessible document handling"> + Mozilla Bug 566103 + </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> + </body> + + <vbox id="eventdump"></vbox> + </vbox> +</window> diff --git a/accessible/tests/mochitest/events/test_docload_aria.html b/accessible/tests/mochitest/events/test_docload_aria.html new file mode 100644 index 000000000..c5f470aee --- /dev/null +++ b/accessible/tests/mochitest/events/test_docload_aria.html @@ -0,0 +1,83 @@ +<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 type="application/javascript" + 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; + + // Debug stuff. + //gA11yEventDumpToConsole = true; + + 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/test_dragndrop.html b/accessible/tests/mochitest/events/test_dragndrop.html new file mode 100644 index 000000000..cfba80a46 --- /dev/null +++ b/accessible/tests/mochitest/events/test_dragndrop.html @@ -0,0 +1,110 @@ +<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 type="application/javascript" + 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); + + var 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")); + + var 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 000000000..44a9afd94 --- /dev/null +++ b/accessible/tests/mochitest/events/test_flush.html @@ -0,0 +1,77 @@ +<html> + +<head> + <title>Flush delayed events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + SimpleTest.expectAssertions(0, 1); + + var gFocusHandler = { + handleEvent: function(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 000000000..4cd57fe3b --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_aria_activedescendant.html @@ -0,0 +1,120 @@ +<!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 type="application/javascript" + 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"> + //gA11yEventDumpToConsole = true; // debugging + + function changeARIAActiveDescendant(aID, aItemID) + { + this.eventSeq = [ + new focusChecker(aItemID) + ]; + + this.invoke = function changeARIAActiveDescendant_invoke() + { + getNode(aID).setAttribute("aria-activedescendant", aItemID); + } + + this.getID = function changeARIAActiveDescendant_getID() + { + return "change aria-activedescendant on " + aItemID; + } + } + + function insertItemNFocus(aID, aNewItemID) + { + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, aNewItemID), + new focusChecker(aNewItemID) + ]; + + this.invoke = function insertItemNFocus_invoke() + { + var container = getNode(aID); + var itemNode = document.createElement("div"); + itemNode.setAttribute("id", aNewItemID); + 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; + } + } + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new synthFocus("listbox", new focusChecker("item1"))); + gQueue.push(new changeARIAActiveDescendant("listbox", "item2")); + gQueue.push(new changeARIAActiveDescendant("listbox", "item3")); + + gQueue.push(new synthFocus("combobox_entry", new focusChecker("combobox_entry"))); + gQueue.push(new changeARIAActiveDescendant("combobox", "combobox_option2")); + + todo(false, "No focus for inserted element, bug 687011"); + //gQueue.push(new insertItemNFocus("listbox", "item4")); + + 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=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> + <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> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_autocomplete.xul b/accessible/tests/mochitest/events/test_focus_autocomplete.xul new file mode 100644 index 000000000..2a32a6587 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_autocomplete.xul @@ -0,0 +1,518 @@ +<?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" + title="Accessible focus event testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script 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. + function getBrowser() + { + return { + mCurrentBrowser: { engines: new Array() } + }; + } + + var BrowserSearch = { + updateOpenSearchBadge: function() {} + }; + + //////////////////////////////////////////////////////////////////////////// + // 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("VK_LEFT", { shiftKey: true }); + synthesizeKey("VK_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(); + synthesizeKey(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.call(null, aIDFuncArg); + this.node = getNode(this.id); + this.window = this.node.ownerDocument.defaultView; + + 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.call(null, 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.call(null, aIDFuncArg); + var node = getNode(id); + var targetWindow = node.ownerDocument.defaultView; + + var x = 0, y = 0; + 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, {}, {}); + x = itemX.value - treeX.value; + y = itemY.value - treeY.value; + } + } + + synthesizeMouseAtCenter(node, {}, targetWindow); + } + + this.getID = function selectByClick_getID() + { + return "select by click " + prettyName(aIDFunc.call(null, aIDFuncArg)); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Target getters + + function getItem(aItemObj) + { + var autocomplete = aItemObj.autocomplete; + var autocompleteNode = aItemObj.autocompleteNode; + + // XUL searchbar + if (autocompleteNode.localName == "searchbar") { + var popupNode = autocompleteNode._popup; + if (popupNode) { + var list = getAccessible(popupNode); + return list.getChildAt(aItemObj.index); + } + } + + // XUL autocomplete + var popupNode = autocompleteNode.popup; + if (!popupNode) { + // HTML form autocomplete + var controller = Components.classes["@mozilla.org/autocomplete/controller;1"]. + getService(Components.interfaces.nsIAutoCompleteController); + popupNode = controller.input.popup.QueryInterface(nsIDOMNode); + } + + if (popupNode) { + if ("richlistbox" in popupNode) { + var list = getAccessible(popupNode.richlistbox); + return list.getChildAt(aItemObj.index); + } + + var list = getAccessible(popupNode.tree); + return list.getChildAt(aItemObj.index + 1); + } + } + + 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; + + // XUL autocomplete + if (localName == "textbox") + return getAccessible(aID).firstChild; + + // HTML form autocomplete + if (localName == "input") + return getAccessible(aID); + + // XUL searchbar + if (localName == "searchbar") + return getAccessible(getNode(aID).textbox.inputField); + + 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 + + //gA11yEventDumpID = "eventdump"; // debug stuff + //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"> + <textbox id="autocomplete" type="autocomplete" + autocompletesearch="test-a11y-search"/> + + <textbox id="richautocomplete" type="autocomplete" + autocompletesearch="test-a11y-search" + autocompletepopup="richpopup"/> + <panel id="richpopup" type="autocomplete-richlistbox" noautofocus="true"/> + + <iframe id="iframe"/> + + <iframe id="iframe2"/> + + <searchbar id="searchbar"/> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_browserui.xul b/accessible/tests/mochitest/events/test_focus_browserui.xul new file mode 100644 index 000000000..bd621ebf2 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_browserui.xul @@ -0,0 +1,149 @@ +<?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 Loading Document Events Test."> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <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="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Helpers + + function inputInDocument() + { + var tabdoc = currentTabDocument(); + return tabdoc.getElementById("input"); + } + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function loadURI(aURI) + { + this.invoke = function loadURI_invoke() + { + tabBrowser().loadURI(aURI); + } + + this.eventSeq = [ + new focusChecker(currentTabDocument) + ]; + + this.getID = function loadURI_getID() + { + return "load uri " + aURI; + } + } + + function goBack() + { + this.invoke = function goBack_invoke() + { + tabBrowser().goBack(); + } + + this.eventSeq = [ + new focusChecker(inputInDocument) + ]; + + this.getID = function goBack_getID() + { + return "go back one page in history "; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Testing + + var gInputDocURI = "data:text/html,<html><input id='input'></html>"; + var gButtonDocURI = "data:text/html,<html><input id='input' type='button' value='button'></html>"; + + //gA11yEventDumpToConsole = true; // debug + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + var tabDocument = currentTabDocument(); + var input = inputInDocument(); + + // move focus to input inside tab document + gQueue.push(new synthTab(tabDocument, new focusChecker(input), + browserWindow())); + + // open new url, focus moves to new document + gQueue.push(new loadURI(gButtonDocURI)); + + // back one page in history, moves moves on input of tab document + gQueue.push(new goBack()); + + // open new tab, focus moves to urlbar + gQueue.push(new synthKey(tabDocument, "t", { accelKey: true, window: browserWindow() }, + new focusChecker(urlbarInput))); + + // close open tab, focus goes on input of tab document + gQueue.push(new synthKey(tabDocument, "w", { accelKey: true, window: browserWindow() }, + new focusChecker(inputInDocument))); + + gQueue.onFinish = function() + { + closeBrowserWindow(); + } + gQueue.invoke(); + } + + if (navigator.oscpu.startsWith("Windows NT 6.1") || navigator.oscpu.startsWith("Windows NT 6.2")) { + todo(false, "fix the leak!"); + } else { + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTests, gInputDocURI); + } + ]]> + </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=644452" + title="Focus not set when switching to cached document with back or forward if anything other than the document was last focused"> + Mozilla Bug 644452 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=665412" + title="Broken focus when returning to editable text field after closing a tab while focused in the Navigation toolbar"> + Mozilla Bug 665412 + </a> + <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 id="eventdump"></vbox> + </vbox> +</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 000000000..dcccb08e0 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_canvas.html @@ -0,0 +1,61 @@ +<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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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.xul b/accessible/tests/mochitest/events/test_focus_contextmenu.xul new file mode 100644 index 000000000..0cf62b912 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_contextmenu.xul @@ -0,0 +1,99 @@ +<?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 nenu focus testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script 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 = Components.classes["@mozilla.org/system-info;1"] + .getService(Components.interfaces.nsIPropertyBag2) + .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"))); + 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 000000000..a18832a8f --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_controls.html @@ -0,0 +1,75 @@ +<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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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")); + + 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"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_focus_dialog.html b/accessible/tests/mochitest/events/test_focus_dialog.html new file mode 100644 index 000000000..9d88b0cb4 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_dialog.html @@ -0,0 +1,164 @@ +<html> + +<head> + <title>Accessible focus testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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"> + function openCloseDialog(aID) + { + this.eventSeq = [ + new focusChecker(getNode(aID)) + ]; + + this.invoke = function openCloseDialog_invoke() + { + var wnd = window.open("focus.html"); + wnd.close(); + } + + this.getID = function openCloseDialog_getID() + { + return "Open close dialog while focus on " + prettyName(aID); + } + } + + var gDialogWnd = null; + function getDialogDocument() + { + return gDialogWnd.document; + } + + function openDialog(aID) + { + this.eventSeq = [ + new focusChecker(getDialogDocument) + ]; + + this.invoke = function openDialog_invoke() + { + gDialogWnd = window.open("focus.html"); + } + + this.getID = function openDialog_getID() + { + return "Open dialog while focus on " + prettyName(aID); + } + } + + function closeDialog(aID) + { + this.eventSeq = [ + new focusChecker(aID) + ]; + + this.invoke = function closeDialog_invoke() + { + gDialogWnd.close(); + } + + this.getID = function closeDialog_getID() + { + return "Close dialog while focus on " + prettyName(aID); + } + } + + function showNFocusAlertDialog() + { + this.ID = "alertdialog"; + this.DOMNode = getNode(this.ID); + + this.invoke = function showNFocusAlertDialog_invoke() + { + document.getElementById(this.ID).style.display = 'block'; + document.getElementById(this.ID).focus(); + } + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.DOMNode), + new focusChecker(this.DOMNode) + ]; + + this.getID = function showNFocusAlertDialog_getID() + { + return "Show and focus alert dialog " + prettyName(this.ID); + } + } + + /** + * Do tests. + */ + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTests() + { + gQueue = new eventQueue(EVENT_FOCUS); + + gQueue.push(new synthFocus("button")); + gQueue.push(new openDialog("button")); + gQueue.push(new closeDialog("button")); + + var frameNode = getNode("editabledoc"); + gQueue.push(new synthFocusOnFrame(frameNode)); + gQueue.push(new openCloseDialog(frameNode.contentDocument)); + + gQueue.push(new showNFocusAlertDialog()); + + 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=551679" + title="focus is not fired for focused document when switching between windows"> + Mozilla Bug 551679 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=580464" + title="Accessible focus incorrect after JS focus() but correct after switching apps or using menu bar"> + Mozilla Bug 580464 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <button id="button">button</button> + <iframe id="editabledoc" src="focus.html"></iframe> + + <div id="alertdialog" style="display: none" tabindex="-1" role="alertdialog" aria-labelledby="title2" aria-describedby="desc2"> + <div id="title2">Blah blah</div> + <div id="desc2">Woof woof woof.</div> + <button>Close</button> + </div> + + + <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 000000000..bd4934d84 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_doc.html @@ -0,0 +1,95 @@ +<html> + +<head> + <title>Accessible document focus event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 000000000..e881a5a4f --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_general.html @@ -0,0 +1,179 @@ +<html> + +<head> + <title>Accessible focus testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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))); + } + gQueue.push(new synthContextMenu(editableDoc, new contextMenuChecker())); + gQueue.push(new synthDownKey(editableDoc, new focusContextMenuItemChecker())); + gQueue.push(new synthEscapeKey(editableDoc, new focusChecker(editableDoc))); + 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.xul b/accessible/tests/mochitest/events/test_focus_general.xul new file mode 100644 index 000000000..f72834f39 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_general.xul @@ -0,0 +1,179 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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 getColorBtn(aBtnObj) + { + var colorpicker = aBtnObj.colorpicker; + var container = colorpicker.firstChild; + var btn = container.getChildAt(aBtnObj.btnIndex); + return btn; + } + + //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").inputField))); + gQueue.push(new synthFocus("textbox_multiline", + new focusChecker(getNode("textbox_multiline").inputField))); + gQueue.push(new synthFocus("scale")); + 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"))); + + // clicking on button having associated popup doesn't change a focus + gQueue.push(new synthClick("popupbutton", new nofocusChecker())); + // 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())); + +if (!MAC) { + // click menubutton of the 'menubutton' button while popup of button open. + gQueue.push(new synthClick("mbb", new focusChecker("mbb"), { where: "right" })); + // close popup, focus stays on menubutton, fire focus event + gQueue.push(new synthEscapeKey("mbb", new focusChecker("mbb"))); + // click menubutton, open popup, focus stays on menubutton + gQueue.push(new synthClick("mbb", new nofocusChecker(), { where: "right" })); + // select first menu item ("item 1"), focus on menu item + gQueue.push(new synthDownKey("mbb", new focusChecker("mbb_item1"))); + // choose select menu item, focus gets back to menubutton + gQueue.push(new synthEnterKey("mbb_item1", new focusChecker("mbb"))); + // open popup, focus stays on menubutton + gQueue.push(new synthOpenComboboxKey("mbb", new nofocusChecker())); + // select second menu item ("item 2"), focus on menu item + gQueue.push(new synthUpKey("menubutton", new focusChecker("mbb_item2"))); + // click on menu item of menubutton menu, focus menubutton + gQueue.push(new synthClick("mbb_item2", new focusChecker("mbb"))); +} else { + todo(false, "mbb tests time out on OS X, fix bug 746970 and reenable!"); +} + + // focus colorpicker button + gQueue.push(new synthFocus("colorpicker")); + // click on button, open popup, focus goes to current color button + var btnObj = { colorpicker: getAccessible("colorpicker"), btnIndex: 0 }; + var checker = new focusChecker(getColorBtn, btnObj); + gQueue.push(new synthClick("colorpicker", checker)); + // select sibling color button, focus on it + btnObj = { colorpicker: getAccessible("colorpicker"), btnIndex: 1 }; + var checker = new focusChecker(getColorBtn, btnObj); + gQueue.push(new synthRightKey("colorpicker", checker)); + // choose selected color button, close popup, focus on colorpicker button + gQueue.push(new synthEnterKey("colorpicker", new focusChecker("colorpicker"))); + + 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=492518" + title="xul:slider accessible of xul:scale is accessible illegally"> + Mozilla Bug 492518 + </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"> + <textbox id="textbox" value="hello"/> + <textbox id="textbox_multiline" multiline="true" value="hello"/> + <scale id="scale" min="0" max="9" value="5"/> + <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="checkbutton" type="checkbox" label="checkbutton"/> + <button id="radiobutton" type="radio" group="rbgroup" label="radio1"/> + + <button id="menubutton" type="menu" label="menubutton"> + <menupopup> + <menuitem id="mb_item1" label="item1"/> + <menuitem id="mb_item2" label="item2"/> + </menupopup> + </button> + <button id="mbb" type="menu-button" label="menubutton button"> + <menupopup> + <menuitem id="mbb_item1" label="item1"/> + <menuitem id="mbb_item2" label="item2"/> + </menupopup> + </button> + + <colorpicker id="colorpicker" type="button" label="color picker" + color="#FFFFFF"/> + + <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.xul b/accessible/tests/mochitest/events/test_focus_listcontrols.xul new file mode 100644 index 000000000..db45048e2 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_listcontrols.xul @@ -0,0 +1,189 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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("listbox", new focusChecker("lb_item1"))); + gQueue.push(new synthDownKey("lb_item1", new focusChecker("lb_item2"))); + gQueue.push(new synthTab("lb_item2", new focusChecker("mslb_item1"))); + gQueue.push(new synthDownKey("mslb_item1", new focusChecker("mslb_item2"), { shiftKey: true })); + gQueue.push(new synthTab("mslb_item2", new focusChecker("emptylistbox"))); + gQueue.push(new synthFocus("mcolumnlistbox", new focusChecker("mclb_item1"))); + gQueue.push(new synthDownKey("mclb_item1", new focusChecker("mclb_item2"))); + gQueue.push(new synthFocus("headerlistbox", new focusChecker("hlb_item1"))); + gQueue.push(new synthDownKey("hlb_item1", new focusChecker("hlb_item2"))); + + 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"))); + gQueue.push(new synthDownKey("ml_tangerine", new focusChecker("ml_marmalade"))); + gQueue.push(new synthEscapeKey("ml_marmalade", new focusChecker("menulist"))); +if (!MAC) { + // On Windows, items get selected during navigation. + let expectedItem = WIN ? "ml_tangerine" : "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"))); +} else { + todo(false, "Bug 746531 - timeouts of last three menulist tests on OS X"); +} + + var textentry = getAccessible("emenulist").firstChild; + gQueue.push(new synthFocus("emenulist", new focusChecker(textentry))); + gQueue.push(new synthDownKey(textentry, new nofocusChecker("eml_tangerine"))); + gQueue.push(new synthUpKey(textentry, new focusChecker("eml_marmalade"))); + gQueue.push(new synthEnterKey("eml_marmalade", new focusChecker(textentry))); + gQueue.push(new synthOpenComboboxKey("emenulist", new focusChecker("eml_marmalade"))); + gQueue.push(new synthEscapeKey("eml_marmalade", new focusChecker(textentry))); + + // no focus events for unfocused list controls when current item is + // changed. + gQueue.push(new synthFocus("emptylistbox")); + + gQueue.push(new changeCurrentItem("listbox", "lb_item1")); + gQueue.push(new changeCurrentItem("richlistbox", "rlb_item1")); +if (!MAC) { + gQueue.push(new changeCurrentItem("menulist", WIN ? "ml_marmalade" : "ml_tangerine")); +} + gQueue.push(new changeCurrentItem("emenulist", "eml_tangerine")); + + 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=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"> + <listbox id="listbox" rows="3"> + <listitem id="lb_item1" label="item1"/> + <listitem id="lb_item2" label="item1"/> + </listbox> + <listbox id="multisellistbox" rows="3" seltype="multiple"> + <listitem id="mslb_item1" label="item1"/> + <listitem id="mslb_item2" label="item1"/> + </listbox> + <listbox id="emptylistbox" rows="3"/> + <listbox id="mcolumnlistbox" rows="3"> + <listcols> + <listcol/> + <listcol/> + </listcols> + <listitem id="mclb_item1"> + <listcell label="George"/> + <listcell label="House Painter"/> + </listitem> + <listitem id="mclb_item2"> + <listcell label="Mary Ellen"/> + <listcell label="Candle Maker"/> + </listitem> + </listbox> + <listbox id="headerlistbox" rows="3"> + <listhead> + <listheader label="Name"/> + <listheader label="Occupation"/> + </listhead> + <listcols> + <listcol/> + <listcol flex="1"/> + </listcols> + <listitem id="hlb_item1"> + <listcell label="George"/> + <listcell label="House Painter"/> + </listitem> + <listitem id="hlb_item2"> + <listcell label="Mary Ellen"/> + <listcell label="Candle Maker"/> + </listitem> + </listbox> + + <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"/> + </menupopup> + </menulist> + <menulist id="emenulist" editable="true"> + <menupopup> + <menuitem id="eml_tangerine" label="tangerine trees"/> + <menuitem id="eml_marmalade" label="marmalade skies"/> + </menupopup> + </menulist> + + <vbox id="eventdump"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/events/test_focus_menu.xul b/accessible/tests/mochitest/events/test_focus_menu.xul new file mode 100644 index 000000000..f205e8d25 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_menu.xul @@ -0,0 +1,119 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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"))); + + // 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 synthClick("vehicle", new focusChecker("vehicle"))); + gQueue.push(new synthDownKey("cycle", 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))); + + //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> + <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 000000000..f0db26427 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_name.html @@ -0,0 +1,122 @@ +<html> + +<head> + <title>Accessible name testing on focus</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 != nsIDOMNode.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 == nsIDOMNode.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, false); + gButtonElm.addEventListener("blur", gBlurHandler, false); + gTextboxElm.addEventListener("focus", gFocusHandler, false); + gTextboxElm.addEventListener("blur", gBlurHandler, false); + + // 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, false); + gButtonElm.removeEventListener("blur", gBlurHandler, false); + gTextboxElm.removeEventListener("focus", gFocusHandler, false); + gTextboxElm.removeEventListener("blur", gBlurHandler, false); + } + + 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_selects.html b/accessible/tests/mochitest/events/test_focus_selects.html new file mode 100644 index 000000000..ef742c4b2 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_selects.html @@ -0,0 +1,118 @@ +<html> + +<head> + <title>Accessible focus testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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"> + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + 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; + } + + gQueue = new eventQueue(); + + // first item is focused until there's selection + gQueue.push(new synthFocus("list", new focusChecker("orange"))); + + // item is selected and stays focused + gQueue.push(new synthDownKey("list", new nofocusChecker())); + + // last selected item is focused + gQueue.push(new synthDownKey("list", new focusChecker("apple"), { shiftKey: true })); + + // no focus event if nothing is changed + gQueue.push(new synthDownKey("list", new nofocusChecker("apple"))); + + // current item is focused + gQueue.push(new synthUpKey("list", new focusChecker("orange"), { ctrlKey: true })); + + // focus on empty list (no items to be focused) + gQueue.push(new synthTab("list", new focusChecker("emptylist"))); + + // current item is focused + gQueue.push(new synthShiftTab("emptylist", new focusChecker("orange"))); + + // collapsed combobox keeps a focus + gQueue.push(new synthFocus("combobox", new focusChecker("combobox"))); + gQueue.push(new synthDownKey("combobox", new nofocusChecker("combobox"))); + + // selected item is focused for expanded combobox + gQueue.push(new synthOpenComboboxKey("combobox", new focusChecker("cb_apple"))); + gQueue.push(new synthUpKey("combobox", new focusChecker("cb_orange"))); + + // collapsed combobx keeps a focus + gQueue.push(new synthEscapeKey("combobox", new focusChecker("combobox"))); + + // no focus events for unfocused list controls when current item is + // changed + gQueue.push(new synthFocus("emptylist")); + + gQueue.push(new changeCurrentItem("list", "orange")); + gQueue.push(new changeCurrentItem("combobox", "cb_apple")); + + 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=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.xul b/accessible/tests/mochitest/events/test_focus_tabbox.xul new file mode 100644 index 000000000..c51546405 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_tabbox.xul @@ -0,0 +1,103 @@ +<?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="Tabbox focus testing"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script 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 textbox = getNode("textbox").inputField; + 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(textbox))); + 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"> + <textbox id="textbox" /> + </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.xul b/accessible/tests/mochitest/events/test_focus_tree.xul new file mode 100644 index 000000000..995b08e25 --- /dev/null +++ b/accessible/tests/mochitest/events/test_focus_tree.xul @@ -0,0 +1,122 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script 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_fromUserInput.html b/accessible/tests/mochitest/events/test_fromUserInput.html new file mode 100644 index 000000000..1cfeedf0b --- /dev/null +++ b/accessible/tests/mochitest/events/test_fromUserInput.html @@ -0,0 +1,127 @@ +<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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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() + { + const nsIDOMNSEditableElement = + Components.interfaces.nsIDOMNSEditableElement; + + this.DOMNode.focus(); + this.DOMNode.setSelectionRange(aStart, aEnd); + + synthesizeKey("VK_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() + { + const nsIDOMNSEditableElement = + Components.interfaces.nsIDOMNSEditableElement; + + 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("VK_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.xul b/accessible/tests/mochitest/events/test_label.xul new file mode 100644 index 000000000..e82763a7c --- /dev/null +++ b/accessible/tests/mochitest/events/test_label.xul @@ -0,0 +1,177 @@ +<?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 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="../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() + { + var tree = + { LABEL: [ + { TEXT_LEAF: [ ] } + ] }; + 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.boxObject.width; + this.charWidth = this.width / this.labelNode.value.length; + + this.eventSeq = [ + new textChangeChecker(this.labelNode, 0, -1, aRemovedText, false), + new textChangeChecker(this.labelNode, 0, -1, aInsertedText, true) + ]; + + this.invoke = function setCrop_invoke() + { + if (!this.labelNode.hasAttribute("crop")) + this.labelNode.width = Math.floor(this.width - 2 * this.charWidth); + + 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", "", kTextRemoved, "?<>!+_")); + 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)); + + if (MAC) { + // "valuetocro" -> "…etocro" + gQueue.push(new setCrop("croplabel", "left", "valu", "…")); + // "…etocro", "val…cro" + gQueue.push(new setCrop("croplabel", "center", "…eto", "val…")); + } else { + // "valuetocro" -> "…uetocro" + gQueue.push(new setCrop("croplabel", "left", "val", "…")); + // "…uetocro" -> "valu…cro" + gQueue.push(new setCrop("croplabel", "center", "…ueto", "valu…")); + } + + 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">hello</label> + <description id="descr">hello</description> + + <hbox> + <label id="croplabel" value="valuetocro" + style="font-family: monospace;"/> + </hbox> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/events/test_menu.xul b/accessible/tests/mochitest/events/test_menu.xul new file mode 100644 index 000000000..bae36fb71 --- /dev/null +++ b/accessible/tests/mochitest/events/test_menu.xul @@ -0,0 +1,202 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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"> + function openFileMenu() + { + this.eventSeq = [ + new invokerChecker(EVENT_MENU_START, getNode("menubar")), + new invokerChecker(EVENT_MENUPOPUP_START, getNode("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(EVENT_MENUPOPUP_END, getNode("menupopup-file")), + new invokerChecker(EVENT_MENUPOPUP_START, getNode("menupopup-edit")) + // new invokerChecker(EVENT_FOCUS, getNode("menuitem-undo")) intermitent failure + ]; + + this.invoke = function openEditMenu_invoke() + { + synthesizeKey("VK_RIGHT", { }); + } + + 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(EVENT_MENUPOPUP_END, getNode("menupopup-edit")), + new invokerChecker(EVENT_MENU_END, getNode("menubar")) + ]; + + this.invoke = function closeEditMenu_invoke() + { + synthesizeKey("VK_ESCAPE", { }); + } + + this.getID = function closeEditMenu_getID() + { + return "close edit menu, leave menubar"; + } + } + + 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("VK_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("VK_RIGHT", { }); + } + + 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, getNode("menubar")) + ]; + + this.invoke = function leaveMenubar_invoke() + { + synthesizeKey("VK_ESCAPE", { }); + } + + this.getID = function leaveMenubar_getID() + { + return "leave menubar"; + } + } + + /** + * Do tests. + */ + + //gA11yEventDumpID = "eventdump"; + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTests() + { + if (!WIN) { + 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()); + + // 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 000000000..232a09727 --- /dev/null +++ b/accessible/tests/mochitest/events/test_mutation.html @@ -0,0 +1,632 @@ +<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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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++) { + var 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; + } + + this.getEventSeq = function mutateA11yTree_getEventSeq() + { + return this.doNotExpectEvents ? this.unexpectedEventSeq : this.eventSeq; + } + + if (aEventTypes & kHideEvent) { + var checker = new invokerChecker(this.getA11yEventType(kHideEvent), + this.DOMNode); + this.getEventSeq().push(checker); + } + + if (aEventTypes & kShowEvent) { + var checker = new invokerChecker(this.getA11yEventType(kShowEvent), + this.DOMNode); + this.getEventSeq().push(checker); + } + + if (aEventTypes & kReorderEvent) { + var 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.call(null, newElm) : [newElm]; + this.setTargets(kShowEvent, targets); + + if (aReorderTargetFunc) { + var reorderTarget = aReorderTargetFunc.call(null, 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.parentNode.removeChild(this.DOMNode); + } + + this.getID = function removeFromDOM_getID() + { + return prettyName(aNodeOrID) + " remove from DOM."; + } + + if (aTargetsFunc && (eventTypes & kHideEvent)) + this.setTargets(kHideEvent, aTargetsFunc.call(null, this.DOMNode)); + + if (aReorderTargetFunc && (eventTypes & kReorderEvent)) + this.setTarget(kReorderEvent, + aReorderTargetFunc.call(null, 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() + { + this.containerNode.innerHTML = + "<span id='insertReferredElms_span'></span><input aria-labelledby='insertReferredElms_span'>"; + } + + 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, function() { return this.txt; }.bind(this)) + ]; + + this.invoke = function hideNDestroyDoc_invoke() + { + this.txt = getAccessible('c5').firstChild.firstChild; + this.txt.DOMNode.parentNode.removeChild(this.txt.DOMNode); + } + + this.check = function hideNDestroyDoc_check() + { + getNode('c5').parentNode.removeChild(getNode('c5')); + } + + 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, function() { return this.target; }.bind(this)) + ]; + + this.invoke = function hideHideNDestroyDoc_invoke() + { + var doc = getAccessible('c6').firstChild; + var l1 = doc.firstChild; + this.target = l1.firstChild; + var l2 = doc.lastChild; + l1.DOMNode.removeChild(l1.DOMNode.firstChild); + l2.DOMNode.removeChild(l2.DOMNode.firstChild); + } + + this.check = function hideHideNDestroyDoc_check() + { + getNode('c6').parentNode.removeChild(getNode('c6')); + } + + 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'. + var 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'. + var 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 display style of accessible DOM node + // from 'inline' to 'block', 'block' to 'inline'. + var id = "link3"; + getAccessible(id); // ensure accessible is created + gQueue.push(new changeStyle(id, "display", "block", kHideAndShowEvents)); + gQueue.push(new changeStyle(id, "display", "inline", kHideAndShowEvents)); + + // Show/hide events by changing of visibility style of accessible DOM node + // from 'collapse' to 'visible', 'visible' to 'collapse'. + var 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. + var 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. + var 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. + var 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. + var 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_mutation.xhtml b/accessible/tests/mochitest/events/test_mutation.xhtml new file mode 100644 index 000000000..e1aabe612 --- /dev/null +++ b/accessible/tests/mochitest/events/test_mutation.xhtml @@ -0,0 +1,97 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <bindings xmlns="http://www.mozilla.org/xbl" > + <binding id="button"> + <content> + <button xmlns="http://www.w3.org/1999/xhtml">a button</button> + </content> + </binding> + </bindings> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + /** + * Insert a not accessible bound element containing an accessible element + * in anonymous content. + */ + function insertBinding(aContainerID) + { + this.containerNode = getNode(aContainerID); + + function getButtonFromBinding(aNode) + { + try { return document.getAnonymousNodes(aNode.firstChild)[0]; } + catch (e) { return null; } + } + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getButtonFromBinding, this.containerNode), + new invokerChecker(EVENT_REORDER, this.containerNode) + ]; + + this.invoke = function insertBinding_invoke() + { + var span = document.createElement("span"); + span.setAttribute("style", "-moz-binding:url(#button)"); + this.containerNode.appendChild(span); + } + + this.getID = function insertBinding_getID() + { + return "insert button binding"; + } + } + + /** + * Do tests. + */ + var gQueue = null; + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new insertBinding("testContainer")); + + 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=646369" + title="UpdateTree should rely on accessible tree walker rather than DOM tree traversal"> + Mozilla Bug 646369</a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <div id="testContainer"></div> +</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 000000000..935d865e9 --- /dev/null +++ b/accessible/tests/mochitest/events/test_namechange.html @@ -0,0 +1,123 @@ +<html> + +<head> + <title>Accessible name change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 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; + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new setAttr("tst1", "aria-label", "hi", + new invokerChecker(EVENT_NAME_CHANGE, "tst1"))); + gQueue.push(new setAttr("tst1", "aria-labelledby", "display", + new unexpectedInvokerChecker(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("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_recreate("tst3", "alt", "alt")); + 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(); // Will call 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"> + <img id="tst2"> + <img id="tst3"> + <img id="tst4"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_namechange.xul b/accessible/tests/mochitest/events/test_namechange.xul new file mode 100644 index 000000000..9d688585c --- /dev/null +++ b/accessible/tests/mochitest/events/test_namechange.xul @@ -0,0 +1,92 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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.xul b/accessible/tests/mochitest/events/test_scroll.xul new file mode 100644 index 000000000..e33161376 --- /dev/null +++ b/accessible/tests/mochitest/events/test_scroll.xul @@ -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"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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("a[name='link1']"); + } + + function loadTab(aURL) + { + this.eventSeq = [ + new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument), + new asyncInvokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument) + ]; + + this.invoke = function loadTab_invoke() + { + tabBrowser().loadURI(aURL); + } + + this.getID = function loadTab_getID() + { + return "load tab: " + aURL; + } + } + + function loadTabInBackground(aURL) + { + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1) + ]; + + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument, 1) + ]; + + this.invoke = function loadTabInBackground_invoke() + { + tabBrowser().loadOneTab(aURL, null, "", null, true); + } + + this.getID = function loadTabInBackground_getID() + { + return "load tab in background: " + aURL; + } + } + + function switchToBackgroundTab() + { + this.eventSeq = [ + new invokerChecker(EVENT_SCROLLING_START, getAnchorJumpInTabDocument) + ]; + + this.invoke = function switchToBackgroundTab_invoke() + { + tabBrowser().selectTabAtIndex(1); + } + + this.getID = function switchToBackgroundTab_getID() + { + return "switch to background tab"; + } + } + + //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#link1"; + gQueue.push(new loadTab(url)); + gQueue.push(new loadTabInBackground(url)); + gQueue.push(new switchToBackgroundTab()); + 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=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.xul b/accessible/tests/mochitest/events/test_scroll_caret.xul new file mode 100644 index 000000000..57e27747f --- /dev/null +++ b/accessible/tests/mochitest/events/test_scroll_caret.xul @@ -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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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(aURL); + } + + 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 000000000..de25fedc3 --- /dev/null +++ b/accessible/tests/mochitest/events/test_selection.html @@ -0,0 +1,118 @@ +<html> + +<head> + <title>Accessible selection event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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(); + + // open combobox + gQueue.push(new synthClick("combobox", + new invokerChecker(EVENT_FOCUS, "cb1_item1"))); + gQueue.push(new synthDownKey("cb1_item1", + selChangeSeq("cb1_item1", "cb1_item2"))); + + // closed combobox + gQueue.push(new synthEscapeKey("combobox", + new invokerChecker(EVENT_FOCUS, "combobox"))); + gQueue.push(new synthDownKey("cb1_item2", + selChangeSeq("cb1_item2", "cb1_item3"))); + + // 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.xul b/accessible/tests/mochitest/events/test_selection.xul new file mode 100644 index 000000000..2b0c388ff --- /dev/null +++ b/accessible/tests/mochitest/events/test_selection.xul @@ -0,0 +1,255 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script 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")); + + ////////////////////////////////////////////////////////////////////////// + // listbox + gQueue.push(new synthClick("lb1_item1", + 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> + + <listbox id="listbox"> + <listitem id="lb1_item1" label="item1"/> + <listitem id="lb1_item2" label="item2"/> + </listbox> + + <listbox id="listbox2" seltype="multiple"> + <listitem id="lb2_item1" label="item1"/> + <listitem id="lb2_item2" label="item2"/> + <listitem id="lb2_item3" label="item3"/> + <listitem id="lb2_item4" label="item4"/> + <listitem id="lb2_item5" label="item5"/> + <listitem id="lb2_item6" label="item6"/> + <listitem id="lb2_item7" label="item7"/> + </listbox> + + </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 000000000..aabee46fd --- /dev/null +++ b/accessible/tests/mochitest/events/test_selection_aria.html @@ -0,0 +1,127 @@ +<html> + +<head> + <title>ARIA selection event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 000000000..f4557376b --- /dev/null +++ b/accessible/tests/mochitest/events/test_statechange.html @@ -0,0 +1,287 @@ +<html> + +<head> + <title>Accessible state change event testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function makeEditableDoc(aDocNode, aIsEnabled) + { + this.DOMNode = aDocNode; + + this.invoke = function editabledoc_invoke() { + // Note: this should fire an EVENT_STATE_CHANGE + this.DOMNode.designMode = 'on'; + }; + + this.check = function editabledoc_check(aEvent) { + + testStates(aDocNode, 0, EXT_STATE_EDITABLE); + + var event = null; + try { + var event = aEvent.QueryInterface(nsIAccessibleStateChangeEvent); + } catch (e) { + ok(false, "State change event was expected"); + } + + if (!event) { return; } + + ok(event.isExtraState, "Extra state change was expected"); + is(event.state, EXT_STATE_EDITABLE, "Wrong state of statechange event"); + ok(event.isEnabled, "Expected editable state to be enabled"); + } + + this.getID = function editabledoc_getID() { + return prettyName(aDocNode) + " editable state changed"; + }; + } + + function invalidInput(aNodeOrID) + { + this.DOMNode = getNode(aNodeOrID); + + this.invoke = function invalidInput_invoke() { + // Note: this should fire an EVENT_STATE_CHANGE + this.DOMNode.value = "I am not an email"; + }; + + this.check = function invalidInput_check() { + testStates(aNodeOrID, STATE_INVALID); + }; + + this.getID = function invalidInput_getID() { + return prettyName(aNodeOrID) + " became invalid"; + }; + } + + function changeCheckInput(aID, aIsChecked) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new stateChangeChecker(STATE_CHECKED, false, aIsChecked, this.DOMNode) + ]; + + this.invoke = function changeCheckInput_invoke() + { + this.DOMNode.checked = aIsChecked; + } + + this.getID = function changeCheckInput_getID() + { + return "change checked state to '" + aIsChecked + "' for " + + prettyName(aID); + } + } + + function stateChangeOnFileInput(aID, aAttr, aValue, + aState, aIsExtraState, aIsEnabled) + { + this.fileControlNode = getNode(aID); + this.fileControl = getAccessible(this.fileControlNode); + this.browseButton = this.fileControl.firstChild; + // No state change events on the label. + + this.invoke = function stateChangeOnFileInput_invoke() + { + this.fileControlNode.setAttribute(aAttr, aValue); + } + + this.eventSeq = [ + new stateChangeChecker(aState, aIsExtraState, aIsEnabled, this.fileControl), + new stateChangeChecker(aState, aIsExtraState, aIsEnabled, this.browseButton) + ]; + + this.getID = function stateChangeOnFileInput_getID() + { + return "inherited state change on file input on attribute '" + aAttr + "' change"; + } + } + + function dupeStateChange(aID, aAttr, aValue, + aState, aIsExtraState, aIsEnabled) + { + this.eventSeq = [ + new stateChangeChecker(aState, aIsExtraState, aIsEnabled, getNode(aID)) + ]; + + this.invoke = function dupeStateChange_invoke() + { + getNode(aID).setAttribute(aAttr, aValue); + getNode(aID).setAttribute(aAttr, aValue); + } + + this.getID = function dupeStateChange_getID() + { + return "duped state change events"; + } + } + + function oppositeStateChange(aID, aAttr, aState, aIsExtraState) + { + this.eventSeq = [ ]; + this.unexpectedEventSeq = [ + new stateChangeChecker(aState, aIsExtraState, false, getNode(aID)), + new stateChangeChecker(aState, aIsExtraState, true, getNode(aID)) + ]; + + this.invoke = function oppositeStateChange_invoke() + { + getNode(aID).setAttribute(aAttr, "false"); + getNode(aID).setAttribute(aAttr, "true"); + } + + this.getID = function oppositeStateChange_getID() + { + return "opposite state change events"; + } + } + + /** + * Change concomitant ARIA and native attribute at once. + */ + function echoingStateChange(aID, aARIAAttr, aAttr, aValue, + aState, aIsExtraState, aIsEnabled) + { + this.eventSeq = [ + new stateChangeChecker(aState, aIsExtraState, aIsEnabled, getNode(aID)) + ]; + + this.invoke = function echoingStateChange_invoke() + { + if (aValue == null) { + getNode(aID).removeAttribute(aARIAAttr); + getNode(aID).removeAttribute(aAttr); + + } else { + getNode(aID).setAttribute(aARIAAttr, aValue); + getNode(aID).setAttribute(aAttr, aValue); + } + } + + this.getID = function echoingStateChange_getID() + { + return "enchoing ARIA and native attributes change"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + + // var gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug stuff + + function doTests() + { + gQueue = new eventQueue(nsIAccessibleEvent.EVENT_STATE_CHANGE); + + // Test delayed editable state change + var doc = document.getElementById("iframe").contentDocument; + gQueue.push(new makeEditableDoc(doc)); + + // invalid state change + gQueue.push(new invalidInput("email")); + + // checked state change + gQueue.push(new changeCheckInput("checkbox", true)); + gQueue.push(new changeCheckInput("checkbox", false)); + gQueue.push(new changeCheckInput("radio", true)); + gQueue.push(new changeCheckInput("radio", false)); + + // file input inherited state changes + gQueue.push(new stateChangeOnFileInput("file", "aria-busy", "true", + STATE_BUSY, false, true)); + gQueue.push(new stateChangeOnFileInput("file", "aria-required", "true", + STATE_REQUIRED, false, true)); + gQueue.push(new stateChangeOnFileInput("file", "aria-invalid", "true", + STATE_INVALID, false, true)); + + gQueue.push(new dupeStateChange("div", "aria-busy", "true", + STATE_BUSY, false, true)); + gQueue.push(new oppositeStateChange("div", "aria-busy", + STATE_BUSY, false)); + + gQueue.push(new echoingStateChange("text1", "aria-disabled", "disabled", "true", + EXT_STATE_ENABLED, true, false)); + gQueue.push(new echoingStateChange("text1", "aria-disabled", "disabled", null, + EXT_STATE_ENABLED, true, 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=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> + + <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> + + <input id="text1"> + + <div id="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/events/test_text.html b/accessible/tests/mochitest/events/test_text.html new file mode 100644 index 000000000..d992d6c64 --- /dev/null +++ b/accessible/tests/mochitest/events/test_text.html @@ -0,0 +1,339 @@ +<html> + +<head> + <title>Accessible mutation events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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.removeChild(this.DOMNode.firstChild); + } + + 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) { + var topSpan = document.createElement("span"); + var fSpan = document.createElement("span"); + fSpan.textContent = "333"; + topSpan.appendChild(fSpan); + var sSpan = document.createElement("span"); + sSpan.textContent = "22"; + topSpan.appendChild(sSpan); + + this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]); + + } else { + var topSpan = document.createElement("span"); + this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]); + + var fSpan = document.createElement("span"); + fSpan.textContent = "333"; + topSpan.appendChild(fSpan); + + var 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"); + 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.removeChild(this.DOMNode.firstChild); + } + } + + 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() + { + const nsIDOMNSEditableElement = + Components.interfaces.nsIDOMNSEditableElement; + + this.DOMNode.focus(); + this.DOMNode.setSelectionRange(aStart, aEnd); + + synthesizeKey("VK_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(); + synthesizeKey("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("VK_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 000000000..97428fb3b --- /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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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> + + <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> +</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 000000000..a69a8df15 --- /dev/null +++ b/accessible/tests/mochitest/events/test_textattrchange.html @@ -0,0 +1,115 @@ +<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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 nsIDOMNSEditableElement = + Components.interfaces.nsIDOMNSEditableElement; + + Components.utils.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.QueryInterface(nsIDOMNSEditableElement).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 000000000..92ac30423 --- /dev/null +++ b/accessible/tests/mochitest/events/test_textselchange.html @@ -0,0 +1,86 @@ +<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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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, 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), { shiftKey: true })); + gQueue.push(new synthDownKey("c1", new textSelectionChecker("c1", 0, 2), { shiftKey: true })); + + gQueue.push(new synthClick("ta1", getOnclickSeq("ta1"))); + gQueue.push(new synthRightKey("ta1", + new textSelectionChecker("ta1", 0, 1), + { shiftKey: true })); + gQueue.push(new synthLeftKey("ta1", + [new textSelectionChecker("ta1", 0, 0), + new caretMoveChecker(0, "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.xul b/accessible/tests/mochitest/events/test_tree.xul new file mode 100644 index 000000000..797e7bf97 --- /dev/null +++ b/accessible/tests/mochitest/events/test_tree.xul @@ -0,0 +1,348 @@ +<?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 type="application/javascript" + 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[ + + //////////////////////////////////////////////////////////////////////////// + // 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(Components.interfaces.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(Components.interfaces.nsIPropertyBag2); + try { + var startRow = propBag.getPropertyAsInt32("startrow"); + } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') { + 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') { + 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') { + 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') { + 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() + { + gTreeBox.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"); + gTreeBox.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"); + + gTreeBox.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() + var colCount = gTree.columns.count; + var column = gTree.columns.getFirstColumn(); + while (column) { + gView.setCellText(1, column, "aloha 1x" + String(column.index)); + column = column.getNext(); + } + + gTreeBox.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 gTreeBox = null; + var gTreeView = null; + var gQueue = null; + + // gA11yEventDumpID = "debug"; + gA11yEventDumpToConsole = true; // debuggin + + function doTest() + { + // Initialize the tree + gTree = document.getElementById("tree"); + gTreeBox = gTree.treeBoxObject; + 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 000000000..3464ffdeb --- /dev/null +++ b/accessible/tests/mochitest/events/test_valuechange.html @@ -0,0 +1,255 @@ +<html> + +<head> + <title>Accessible value change events testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 changeRangeValue(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 changeSelectValue(aID, aKey, aValue) + { + this.eventSeq = + [ new invokerChecker(EVENT_TEXT_VALUE_CHANGE, getAccessible(aID)) ]; + + this.invoke = function changeSelectValue_invoke() + { + getNode(aID).focus(); + 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", 0, 0, 3, 0); + testValue("scrollbar", "5", 5, 0, 1000, 0); + testValue("progress", "22%", 22, 0, 100, 0); + testValue("range", "6", 6, 0, 10, 1); + + // 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 changeValue("combobox", "hello")); + + gQueue.push(new changeProgressValue("progress", "50")); + gQueue.push(new changeRangeValue("range")); + + gQueue.push(new changeSelectValue("select", "VK_DOWN", "2nd")); + gQueue.push(new changeSelectValue("select", "3", "3rd")); + + 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 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"> + + <select id="select"> + <option>1st</option> + <option>2nd</option> + <option>3rd</option> + </select> +</body> +</html> diff --git a/accessible/tests/mochitest/focus/a11y.ini b/accessible/tests/mochitest/focus/a11y.ini new file mode 100644 index 000000000..48d5e6654 --- /dev/null +++ b/accessible/tests/mochitest/focus/a11y.ini @@ -0,0 +1,9 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_focusedChild.html] +skip-if = (os == 'win' && (os_version == '6.2' || os_version == '6.3')) # bug 845134 +[test_takeFocus.html] +skip-if = buildapp == 'mulet' +[test_takeFocus.xul] diff --git a/accessible/tests/mochitest/focus/test_focusedChild.html b/accessible/tests/mochitest/focus/test_focusedChild.html new file mode 100644 index 000000000..e03e0469c --- /dev/null +++ b/accessible/tests/mochitest/focus/test_focusedChild.html @@ -0,0 +1,87 @@ +<html> + +<head> + <title>nsIAccessible::focusedChild testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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.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 000000000..23938c819 --- /dev/null +++ b/accessible/tests/mochitest/focus/test_takeFocus.html @@ -0,0 +1,128 @@ +<html> + +<head> + <title>nsIAccessible::takeFocus testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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("plugin")); + 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(); + } + + function waitForPlugin() + { + window.setTimeout((isAccessible("plugin") ? doTest : waitForPlugin), 0); + } + + SimpleTest.waitForExplicitFinish(); + setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED); + addA11yLoadEvent(waitForPlugin); + </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=646361" + title="No focus event fired on document when focus is set to the document while focused in a plugin"> + Mozilla Bug 646361 + </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> + + <embed id="plugin" type="application/x-test" width="200" height="200" wmode="window"></embed> + + <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.xul b/accessible/tests/mochitest/focus/test_takeFocus.xul new file mode 100644 index 000000000..11f6e2a28 --- /dev/null +++ b/accessible/tests/mochitest/focus/test_takeFocus.xul @@ -0,0 +1,106 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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> + + <listbox id="listbox"> + <listitem id="listitem1">item1</listitem> + <listitem id="listitem2">item2</listitem> + </listbox> + + <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 000000000..10e44bf92 --- /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 000000000..56f2412ff --- /dev/null +++ b/accessible/tests/mochitest/grid.js @@ -0,0 +1,149 @@ +const nsIDOMKeyEvent = Components.interfaces.nsIDOMKeyEvent; + +/** + * 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 rowCount = this.getRowCount(); + 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, false); + this.table.addEventListener("click", this, false); + } + + this.handleEvent = function handleEvent(aEvent) + { + if (aEvent instanceof nsIDOMKeyEvent) + 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 nsIDOMKeyEvent.DOM_VK_UP: + var colsCount = this.getColsCount(); + var 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 nsIDOMKeyEvent.DOM_VK_DOWN: + var colsCount = this.getColsCount(); + var 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 nsIDOMKeyEvent.DOM_VK_LEFT: + var idx = this.getIndexByCell(cell); + if (idx > 0) { + cell.removeAttribute("tabindex"); + var prevcell = this.getCellAtIndex(idx - 1); + prevcell.setAttribute("tabindex", "0"); + prevcell.focus(); + } + break; + + case nsIDOMKeyEvent.DOM_VK_RIGHT: + var 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 000000000..13bca54de --- /dev/null +++ b/accessible/tests/mochitest/hittest/a11y.ini @@ -0,0 +1,14 @@ +[DEFAULT] +support-files = zoom_tree.xul + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + +[test_browser.html] +[test_canvas_hitregion.html] +skip-if = (os == "android" || appname == "b2g") +[test_general.html] +[test_menu.xul] +[test_shadowroot.html] +[test_zoom.html] +[test_zoom_text.html] +[test_zoom_tree.xul] diff --git a/accessible/tests/mochitest/hittest/test_browser.html b/accessible/tests/mochitest/hittest/test_browser.html new file mode 100644 index 000000000..75e0b9a95 --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_browser.html @@ -0,0 +1,63 @@ +<!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 type="application/javascript" + 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_canvas_hitregion.html b/accessible/tests/mochitest/hittest/test_canvas_hitregion.html new file mode 100644 index 000000000..fc1df3d60 --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_canvas_hitregion.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<html> +<head> + <title>nsIAccessible::childAtPoint() for canvas from browser tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 redrawCheckbox(context, element, x, y) + { + context.save(); + context.font = '10px sans-serif'; + context.textAlign = 'left'; + context.textBaseline = 'middle'; + var metrics = context.measureText(element.parentNode.textContent); + context.beginPath(); + context.strokeStyle = 'black'; + context.rect(x-5, y-5, 10, 10); + context.stroke(); + if (element.checked) { + context.fillStyle = 'black'; + context.fill(); + } + context.fillText(element.parentNode.textContent, x+5, y); + + context.beginPath(); + context.rect(x-7, y-7, 12 + metrics.width+2, 14); + + if (document.activeElement == element) + context.drawFocusIfNeeded(element); + context.addHitRegion({control: element}); + context.restore(); + } + + function doTest() + { + var offsetX = 20, offsetY = 40; + getNode("hitcanvas").scrollIntoView(true); + + var context = document.getElementById("hitcanvas").getContext('2d'); + redrawCheckbox(context, document.getElementById('hitcheck'), + offsetX, offsetY); + + var hitcanvas = getAccessible("hitcanvas"); + var hitcheck = getAccessible("hitcheck"); + + var [hitX, hitY, hitWidth, hitHeight] = getBounds(hitcanvas); + var [deltaX, deltaY] = CSSToDevicePixels(window, offsetX, offsetY); + + var docAcc = getAccessible(document); + + // test if we hit the region associated with the shadow dom checkbox + var tgtX = hitX + deltaX; + var tgtY = hitY + deltaY; + hitAcc = docAcc.getDeepestChildAtPoint(tgtX, tgtY); + isObject(hitAcc, hitcheck, `Hit match at (${tgtX}, ${tgtY}`); + + // test that we don't hit the region associated with the shadow dom checkbox + tgtY = hitY + deltaY * 2; + hitAcc = docAcc.getDeepestChildAtPoint(tgtX, tgtY); + isObject(hitAcc, hitcanvas, `Hit match at (${tgtX}, ${tgtY}`); + + SimpleTest.finish(); + } + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(function() { + SpecialPowers.pushPrefEnv({"set": [['canvas.hitregions.enabled', true]]}, doTest); + }); + </script> +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=966591" + title="nsIAccessible::childAtPoint() for canvas hit regions from browser tests">Mozilla Bug 966591</a> + + <canvas id="hitcanvas"> + <input id="hitcheck" type="checkbox"><label for="showA"> Show A </label> + </canvas> +</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 000000000..74ff4fe29 --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_general.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<html> +<head> + <title>nsIAccessible::childAtPoint() tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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"); + var btnText = btn.firstChild; + 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.xul b/accessible/tests/mochitest/hittest/test_menu.xul new file mode 100644 index 000000000..30bf93acf --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_menu.xul @@ -0,0 +1,134 @@ +<?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 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" + 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 000000000..41400840d --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_shadowroot.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<html> +<head> + <title>ShadowRoot hit tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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() + { + 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> + + <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> + + <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.createShadowRoot(); + for (var child = component.firstChild; child; child = child.nextSibling) { + if (child.nodeType === 8) + 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 000000000..bc1cf9d55 --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_zoom.html @@ -0,0 +1,61 @@ +<!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 type="application/javascript" + 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 000000000..3a75296b2 --- /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 type="application/javascript" + 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; + var [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); + + var [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.xul b/accessible/tests/mochitest/hittest/test_zoom_tree.xul new file mode 100644 index 000000000..da1628df2 --- /dev/null +++ b/accessible/tests/mochitest/hittest/test_zoom_tree.xul @@ -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="nsIAccessible::getChildAtPoint and getDeepestChildAtPoint"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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 treeBoxObject = tree.treeBoxObject; + var treeBodyBoxObj = tree.treeBoxObject.treeBody.boxObject; + var rect = treeBoxObject.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 + treeBodyBoxObj.x; + var cssY = rect.y + treeBodyBoxObj.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 + var [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.xul", + { 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.xul b/accessible/tests/mochitest/hittest/zoom_tree.xul new file mode 100644 index 000000000..52ec0932a --- /dev/null +++ b/accessible/tests/mochitest/hittest/zoom_tree.xul @@ -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 000000000..f37dca8e1 --- /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.xul] diff --git a/accessible/tests/mochitest/hyperlink/hyperlink.js b/accessible/tests/mochitest/hyperlink/hyperlink.js new file mode 100644 index 000000000..4daa16275 --- /dev/null +++ b/accessible/tests/mochitest/hyperlink/hyperlink.js @@ -0,0 +1,42 @@ +/** + * 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 000000000..e6e098ecc --- /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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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); + 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, + "http://www.bbc.co.uk/radio4/atoz/index.shtml#b", "URI wrong!"); + is(imageMapHyperlinkAcc.getURI(1).spec, + "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, + "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, + "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); + 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.xul b/accessible/tests/mochitest/hyperlink/test_general.xul new file mode 100644 index 000000000..c7b838a15 --- /dev/null +++ b/accessible/tests/mochitest/hyperlink/test_general.xul @@ -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="test for nsIAccessibleHyperLink interface on XUL:label elements"> + + <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" + 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, + "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, + "Mozilla Foundation", "http://www.mozilla.org/", 2, 3, true, + false, true); + testStates(labelWithValueAcc, STATE_LINKED, EXT_STATE_HORIZONTAL); + + 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" class="text-link" href="http://www.mozilla.org/"> + Mozilla Foundation home</label> + <label id="linkLabelWithValue" value="Mozilla Foundation" class="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 000000000..27f878f74 --- /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 000000000..68018821f --- /dev/null +++ b/accessible/tests/mochitest/hypertext/test_general.html @@ -0,0 +1,156 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 000000000..23ac8b2c8 --- /dev/null +++ b/accessible/tests/mochitest/hypertext/test_update.html @@ -0,0 +1,236 @@ +<!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 type="application/javascript" + 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/jsat/a11y.ini b/accessible/tests/mochitest/jsat/a11y.ini new file mode 100644 index 000000000..5acc3df90 --- /dev/null +++ b/accessible/tests/mochitest/jsat/a11y.ini @@ -0,0 +1,30 @@ +[DEFAULT] +support-files = + dom_helper.js + gestures.json + jsatcommon.js + output.js + doc_traversal.html + doc_content_integration.html + doc_content_text.html + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/moz.png +skip-if = (os == 'win' && (os_version == '5.1' || os_version == '5.2')) + +[test_alive.html] +[test_content_integration.html] +skip-if = buildapp == 'mulet' +[test_content_text.html] +skip-if = buildapp == 'mulet' +[test_explicit_names.html] +[test_gesture_tracker.html] +[test_hints.html] +[test_landmarks.html] +[test_live_regions.html] +[test_output_mathml.html] +[test_output.html] +[test_quicknav_modes.html] +[test_tables.html] +[test_pointer_relay.html] +[test_traversal.html] +[test_traversal_helper.html] diff --git a/accessible/tests/mochitest/jsat/doc_content_integration.html b/accessible/tests/mochitest/jsat/doc_content_integration.html new file mode 100644 index 000000000..d62c000cb --- /dev/null +++ b/accessible/tests/mochitest/jsat/doc_content_integration.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<html> +<head> + <title>Traversal Rule test document</title> + <meta charset="utf-8" /> + <script> + var frameContents = '<html>' + + '<head><title>such app</title></head>' + + '<body>' + + '<h1>wow</h1>' + + '<ul>' + + '<li><label><input type="checkbox">many option</label></li>' + + '</ul>' + + '<label for="r">much range</label>' + + '<input min="0" max="10" value="5" type="range" id="r">' + + '</body>' + + '</html>'; + + function showAlert() { + document.getElementById('alert').hidden = false; + } + + function hideAlert() { + document.getElementById('alert').hidden = true; + } + + function ariaShowBack() { + document.getElementById('back').setAttribute('aria-hidden', false); + } + + function ariaHideBack() { + document.getElementById('back').setAttribute('aria-hidden', true); + } + + function ariaShowIframe() { + document.getElementById('iframe').setAttribute('aria-hidden', false); + } + + function ariaHideIframe() { + document.getElementById('iframe').setAttribute('aria-hidden', true); + } + + function renameFruit() { + document.getElementById('fruit').setAttribute('aria-label', 'banana'); + } + + function renameSlider() { + document.getElementById('slider').setAttribute( + 'aria-label', 'mover'); + } + + function changeSliderValue() { + document.getElementById('slider').setAttribute('aria-valuenow', '5'); + document.getElementById('slider').setAttribute( + 'aria-valuetext', 'medium'); + } + + function toggleLight() { + var lightSwitch = document.getElementById('light'); + lightSwitch.setAttribute('aria-checked', + lightSwitch.getAttribute('aria-checked') === 'true' ? 'false' : 'true'); + } + + </script> + <style> + #windows { + position: relative; + width: 320px; + height: 480px; + } + + #windows > iframe { + z-index: 1; + } + + #windows > div[role='dialog'] { + z-index: 2; + background-color: pink; + } + + #windows > * { + position: absolute; + width: 100%; + height: 100%; + } + + iframe { + width: 100%; + height: 100%; + } + + </style> + +</head> +<body> + <div>Phone status bar</div> + <div id="windows"> + <button id="back">Back</button> + <div role="dialog" id="alert" hidden> + <h1>This is an alert!</h1> + <p>Do you agree?</p> + <button onclick="setTimeout(hideAlert, 500)">Yes</button> + <button onclick="hideAlert()">No</button> + </div> + <div id="appframe"></div> + </div> + <button id="home">Home</button> + <button id="fruit" aria-label="apple"></button> + <span id="light" role="switch" aria-label="Light" aria-checked="false" onclick="toggleLight()"></span> + <div id="live" aria-live="polite" aria-label="live"> + <div id="slider" role="slider" aria-label="slider" aria-valuemin="0" + aria-valuemax="10" aria-valuenow="0"></div> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/doc_content_text.html b/accessible/tests/mochitest/jsat/doc_content_text.html new file mode 100644 index 000000000..4e73dc6e7 --- /dev/null +++ b/accessible/tests/mochitest/jsat/doc_content_text.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> + <head> + <title>Text content test document</title> + <meta charset="utf-8" /> + </head> + <body> + <p>These are my awards, Mother. From Army. + The seal is for marksmanship, and the gorilla is for sand racing.</p> + <p>You're a good guy, mon frere. That means brother in French. + I don't know how I know that. I took four years of Spanish.</p> + <textarea>Please refrain from Mayoneggs during this salmonella scare.</textarea> + <label>So we don't get dessert?</label><input type="text"> + </body> +</html>
\ No newline at end of file diff --git a/accessible/tests/mochitest/jsat/doc_traversal.html b/accessible/tests/mochitest/jsat/doc_traversal.html new file mode 100644 index 000000000..4c104b6b7 --- /dev/null +++ b/accessible/tests/mochitest/jsat/doc_traversal.html @@ -0,0 +1,164 @@ +<!DOCTYPE html> +<html> +<head> + <title>Traversal Rule test document</title> + <meta charset="utf-8" /> + <style> + .content:before { + content: "Content"; + } + </style> +</head> +<body> + <header id="header-1"> + <h3 id="heading-1">A small first heading</h3> + <form> + <label for="input-1-1">Name:</label> + <input id="input-1-1"> + <label id="label-1-2">Favourite Ice Cream Flavour:<input id="input-1-2"></label> + <button id="button-1-1">Magic Button</button> + <label for="radio-1-1">Radios are old: </label> + <input id="radio-1-1" type="radio"> + <label for="radio-1-2">Radios are new: </label> + <input id="radio-1-2" type="radio"> + <label for="input-1-3">Password:</label> + <input id="input-1-3" type="password"> + <label for="input-1-4">Unlucky number:</label> + <input id="input-1-4" type="tel"> + <input id="button-1-2" type="button" value="Fun"> + <label for="checkbox-1-1">Check me: </label> + <input id="checkbox-1-1" type="checkbox"> + <select id="select-1-1"> + <option>Value 1</option> + <option>Value 2</option> + <option>Value 3</option> + </select> + <select id="select-1-2" multiple="true"> + <option>Value 1</option> + <option>Value 2</option> + <option>Value 3</option> + </select> + <label for="checkbox-1-2">Check me too: </label> + <input id="checkbox-1-2" type="checkbox"> + <label for="checkbox-1-3">But not me: </label> + <input id="checkbox-1-3" type="checkbox" aria-hidden="true"> + <label for="checkbox-1-4">Or me! </label> + <input id="checkbox-1-4" type="checkbox" style="visibility:hidden"> + <select id="select-1-3" size="3"> + <option>Value 1</option> + <option>Value 2</option> + <option>Value 3</option> + </select> + <label for="input-1-5">Electronic mailing address:</label> + <input id="input-1-5" type="email"> + <input id="button-1-3" type="submit" value="Submit"> + </form> + </header> + <main id="main-1"> + <h2 id="heading-2">A larger second</h2> + <div id="heading-3" role="heading">ARIA is fun</div> + <input id="button-2-1" type="button" value="More Fun"> + <div id="button-2-2" tabindex="0" role="button">ARIA fun</div> + <div id="button-2-3" tabindex="0" role="button" aria-pressed="false">My little togglebutton</div> + <div id="button-2-4" tabindex="0" role="spinbutton">ARIA fun</div> + </main> + <h1 id="heading-4" style="display:none">Invisible header</h1> + <dl id="list-1"> + <dt id="listitem-1-1">Programming Language</dt> + <dd>A esoteric weapon wielded by only the most formidable warriors, + for its unrelenting strict power is unfathomable.</dd> + </dl> + <ul id="list-2" onclick="alert('hi');"> + <li id="listitem-2-1">Lists of Programming Languages</li> + <li id="listitem-2-2">Lisp + <ol id="list-3"> + <li id="listitem-3-1">Scheme</li> + <li id="listitem-3-2">Racket</li> + <li id="listitem-3-3">Clojure</li> + <li id="listitem-3-4"><strong>Standard</strong> Lisp</li> + <li id="listitem-3-5"><a id="link-0" href="#">Common</a> Lisp</li> + <li id="listitem-3-6"><input id="checkbox-1-5" type="checkbox"> LeLisp</li> + </ol> + </li> + <li id="listitem-2-3">JavaScript</li> + </ul> + <section> + <h6 id="heading-5">The last (visible) one!</h6> + <img id="image-1" src="http://example.com" alt=""> + <img id="image-2" src="../moz.png" alt="stuff"> + <div id="image-3" tabindex="0" role="img">Not actually an image</div> + </section> + <section> + <h4 id="heading-6" aria-hidden="true">Hidden header</h4> + <a id="link-1" href="http://www.mozilla.org">Link</a> + <a id="anchor-1">Words</a> + <a id="link-2" href="http://www.mozilla.org">Link the second</a> + <a id="anchor-2">Sentences</a> + <a id="link-3" href="http://www.example.com">Link the third</a> + </section> + <hr id="separator-1"> + <h6 id="heading-6"></h6> + <table id="table-1"> + <tr> + <td>3</td> + <td>1</td> + </tr> + <tr> + <td>4</td> + <td>1</td> + </tr> + </table> + <section id="grid" role="grid"> + <ol role="row"> + <li role="presentation"></li> + <li role="columnheader" aria-label="Sunday">S</li> + <li role="columnheader">M</li> + </ol> + <ol role="row"> + <li role="rowheader" aria-label="Week 1">1</li> + <li role="gridcell"><span>3</span><div></div></li> + <li role="gridcell"><span>4</span><div>7</div></li> + </ol> + <ol role="row"> + <li role="rowheader">2</li> + <li role="gridcell"><span>5</span><div role="presentation">8</div></li> + <li id="gridcell4" role="gridcell"> + <span>6</span><div aria-hidden="true"><div class="content"></div></div> + </li> + </ol> + </section> + <div id="separator-2" role="separator">Just an innocuous separator</div> + <table id="table-2"> + <thead> + <tr> + <th>Dirty Words</th> + <th>Meaning</th> + </tr> + </thead> + <tfoot> + <tr> + <td>Mud</td> + <td>Wet Dirt</td> + </tr> + </tfoot> + <tbody> + <tr> + <td>Dirt</td> + <td>Messy Stuff</td> + </tr> + </tbody> + </table> + <footer id="footer-1"> + <div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div> + <div aria-label="Last sync: 30min ago" id="statusbar-2" role="status"></div> + </footer> + + <span id="switch-1" role="switch" aria-checked="false" aria-label="Light switch"></span> + <p>This is a MathML formula <math id="math-1" display="block"> + <mfrac> + <mrow><mi>x</mi><mo>+</mo><mn>1</mn></mrow> + <msqrt><mn>3</mn><mo>/</mo><mn>4</mn></msqrt> + </mfrac> + </math> with some text after.</p> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/dom_helper.js b/accessible/tests/mochitest/jsat/dom_helper.js new file mode 100644 index 000000000..c95d19dc1 --- /dev/null +++ b/accessible/tests/mochitest/jsat/dom_helper.js @@ -0,0 +1,209 @@ +'use strict'; + +/* global getMainChromeWindow, AccessFuTest, GestureSettings, GestureTracker, + SimpleTest, getBoundsForDOMElm, Point, Utils */ +/* exported loadJSON, eventMap */ + +var Ci = Components.interfaces; +var Cu = Components.utils; + +Cu.import('resource://gre/modules/Geometry.jsm'); + +var win = getMainChromeWindow(window); + +/** + * Convert inch based point coordinates into pixels. + * @param {Array} aPoints Array of coordinates in inches. + * @return {Array} Array of coordinates in pixels. + */ +function convertPointCoordinates(aPoints) { + var dpi = Utils.dpi; + return aPoints.map(function convert(aPoint) { + return { + x: aPoint.x * dpi, + y: aPoint.y * dpi, + identifier: aPoint.identifier + }; + }); +} + +/** + * For a given list of points calculate their coordinates in relation to the + * document body. + * @param {Array} aTouchPoints An array of objects of the following format: { + * base: {String}, // Id of an element to server as a base for the touch. + * x: {Number}, // An optional x offset from the base element's geometric + * // centre. + * y: {Number} // An optional y offset from the base element's geometric + * // centre. + * } + * @return {JSON} An array of {x, y} coordinations. + */ +function calculateTouchListCoordinates(aTouchPoints) { + var coords = []; + for (var i = 0, target = aTouchPoints[i]; i < aTouchPoints.length; ++i) { + var bounds = getBoundsForDOMElm(target.base); + var parentBounds = getBoundsForDOMElm('root'); + var point = new Point(target.x || 0, target.y || 0); + point.scale(Utils.dpi); + point.add(bounds[0], bounds[1]); + point.add(bounds[2] / 2, bounds[3] / 2); + point.subtract(parentBounds[0], parentBounds[0]); + coords.push({ + x: point.x, + y: point.y + }); + } + return coords; +} + +/** + * Send a touch event with specified touchPoints. + * @param {Array} aTouchPoints An array of points to be associated with + * touches. + * @param {String} aName A name of the touch event. + */ +function sendTouchEvent(aTouchPoints, aName) { + var touchList = sendTouchEvent.touchList; + if (aName === 'touchend') { + sendTouchEvent.touchList = null; + } else { + var coords = calculateTouchListCoordinates(aTouchPoints); + var touches = []; + for (var i = 0; i < coords.length; ++i) { + var {x, y} = coords[i]; + var node = document.elementFromPoint(x, y); + var touch = document.createTouch(window, node, aName === 'touchstart' ? + 1 : touchList.item(i).identifier, x, y, x, y); + touches.push(touch); + } + touchList = document.createTouchList(touches); + sendTouchEvent.touchList = touchList; + } + var evt = document.createEvent('TouchEvent'); + evt.initTouchEvent(aName, true, true, window, 0, false, false, false, false, + touchList, touchList, touchList); + document.dispatchEvent(evt); +} + +sendTouchEvent.touchList = null; + +/** + * A map of event names to the functions that actually send them. + * @type {Object} + */ +var eventMap = { + touchstart: sendTouchEvent, + touchend: sendTouchEvent, + touchmove: sendTouchEvent +}; + +/** + * Attach a listener for the mozAccessFuGesture event that tests its + * type. + * @param {Array} aExpectedGestures A stack of expected event types. + * @param {String} aTitle Title of this sequence, if any. + * Note: the listener is removed once the stack reaches 0. + */ +function testMozAccessFuGesture(aExpectedGestures, aTitle) { + var types = aExpectedGestures; + function handleGesture(aEvent) { + if (aEvent.detail.type !== types[0].type) { + info('Got ' + aEvent.detail.type + ' waiting for ' + types[0].type); + // The is not the event of interest. + return; + } + is(!!aEvent.detail.edge, !!types[0].edge); + is(aEvent.detail.touches.length, types[0].fingers || 1, + 'failed to count fingers: ' + types[0].type); + ok(true, 'Received correct mozAccessFuGesture: ' + + JSON.stringify(types.shift()) + '. (' + aTitle + ')'); + if (types.length === 0) { + win.removeEventListener('mozAccessFuGesture', handleGesture); + if (AccessFuTest.sequenceCleanup) { + AccessFuTest.sequenceCleanup(); + } + AccessFuTest.nextTest(); + } + } + win.addEventListener('mozAccessFuGesture', handleGesture); +} + +/** + * Reset the thresholds and max delays that affect gesture rejection. + * @param {Number} aTimeStamp Gesture time stamp. + * @param {Boolean} aRemoveDwellThreshold An optional flag to reset dwell + * threshold. + * @param {Boolean} aRemoveSwipeMaxDuration An optional flag to reset swipe max + * duration. + */ +function setTimers(aTimeStamp, aRemoveDwellThreshold, aRemoveSwipeMaxDuration) { + if (!aRemoveDwellThreshold && !aRemoveSwipeMaxDuration) { + return; + } + if (aRemoveDwellThreshold) { + GestureSettings.dwellThreshold = 0; + } + if (aRemoveSwipeMaxDuration) { + GestureSettings.swipeMaxDuration = 0; + } + GestureTracker.current.clearTimer(); + GestureTracker.current.startTimer(aTimeStamp); +} + +function resetTimers(aRemoveGestureResolveDelay) { + GestureSettings.dwellThreshold = AccessFuTest.dwellThreshold; + GestureSettings.swipeMaxDuration = AccessFuTest.swipeMaxDuration; + GestureSettings.maxGestureResolveTimeout = aRemoveGestureResolveDelay ? + 0 : AccessFuTest.maxGestureResolveTimeout; +} + +/** + * An extention to AccessFuTest that adds an ability to test a sequence of + * pointer events and their expected mozAccessFuGesture events. + * @param {Object} aSequence An object that has a list of pointer events to be + * generated and the expected mozAccessFuGesture events. + */ +AccessFuTest.addSequence = function AccessFuTest_addSequence(aSequence) { + AccessFuTest.addFunc(function testSequence() { + testMozAccessFuGesture(aSequence.expectedGestures, aSequence.title); + var events = aSequence.events; + function fireEvent(aEvent) { + var event = { + points: convertPointCoordinates(aEvent.points), + type: aEvent.type + }; + var timeStamp = Date.now(); + resetTimers(aEvent.removeGestureResolveDelay); + GestureTracker.handle(event, timeStamp); + setTimers(timeStamp, aEvent.removeDwellThreshold, + aEvent.removeSwipeMaxDuration); + processEvents(); + } + function processEvents() { + if (events.length === 0) { + return; + } + var event = events.shift(); + SimpleTest.executeSoon(function() { + fireEvent(event); + }); + } + processEvents(); + }); +}; + +/** + * A helper function that loads JSON files. + * @param {String} aPath A path to a JSON file. + * @param {Function} aCallback A callback to be called on success. + */ +function loadJSON(aPath, aCallback) { + var request = new XMLHttpRequest(); + request.open('GET', aPath, true); + request.responseType = 'json'; + request.onload = function onload() { + aCallback(request.response); + }; + request.send(); +} diff --git a/accessible/tests/mochitest/jsat/gestures.json b/accessible/tests/mochitest/jsat/gestures.json new file mode 100644 index 000000000..111994342 --- /dev/null +++ b/accessible/tests/mochitest/jsat/gestures.json @@ -0,0 +1,352 @@ +[ + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}], + "removeGestureResolveDelay": true } + ], + "expectedGestures": [{ "type": "tap" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointermove", + "points": [{"x": 1.03, "y": 1.03, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1.03, "y": 1.03, "identifier": 1}], + "removeGestureResolveDelay": true } + ], + "expectedGestures": [{ "type": "tap" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}], + "removeDwellThreshold": true}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "dwell" }, { "type": "dwellend" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointermove", + "points": [{"x": 1.03, "y": 1.02, "identifier": 1}]}, + {"type": "pointerup", + "points": [{"x": 1.03, "y": 1.02, "identifier": 1}]}, + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointermove", + "points": [{"x": 0.97, "y": 1.01, "identifier": 1}]}, + {"type": "pointerup", + "points": [{"x": 0.97, "y": 1.01, "identifier": 1}], + "removeGestureResolveDelay": true } + ], + "expectedGestures": [{ "type": "doubletap" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}], + "removeGestureResolveDelay": true } + ], + "expectedGestures": [{ "type": "tripletap" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}], + "removeDwellThreshold": true}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "doubletaphold" }, + { "type": "doubletapholdend" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}], + "removeDwellThreshold": true}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "taphold" }, { "type": "tapholdend" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "swiperight" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointermove", "points": [{"x": 1.15, "y": 1, "identifier": 1}]}, + {"type": "pointermove", "points": [{"x": 1.3, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1.3, "y": 1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "swiperight" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1.5, "y": 1, "identifier": 1}]}, + {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "swipeleft" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointermove", "points": [{"x": 1, "y": 1.5, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1.5, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "swipedown" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1.5, "identifier": 1}]}, + {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "swipeup" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointermove", + "points": [{"x": 1.5, "y": 1.1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1.5, "y": 1.1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "swiperight" }] + }, + { + "events": [ + {"type": "pointerdown", + "points": [{"x": 1.5, "y": 1.1, "identifier": 1}]}, + {"type": "pointermove", "points": [{"x": 1, "y": 0.95, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 0.95, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "swipeleft" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointermove", + "points": [{"x": 0.9, "y": 1.5, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 0.9, "y": 1.5, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "swipedown" }] + }, + { + "events": [ + {"type": "pointerdown", + "points": [{"x": 1.1, "y": 1.5, "identifier": 1}]}, + {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "swipeup" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}, + {"x": 1, "y": 1.5, "identifier": 2}]}, + {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1}, + {"x": 1.5, "y": 1.5, "identifier": 2}]}, + {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}, + {"x": 1.5, "y": 1.5, "identifier": 2}]} + ], + "expectedGestures": [{ "type": "swiperight", "fingers": 2 }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}, + {"x": 1, "y": 1.5, "identifier": 2}]}, + {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1}, + {"x": 1.5, "y": 1.5, "identifier": 2}]}, + {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1.5, "y": 1.5, "identifier": 2}]} + ], + "expectedGestures": [{ "type": "swiperight", "fingers": 2 }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}, + {"x": 1, "y": 1.5, "identifier": 2}, + {"x": 1, "y": 2, "identifier": 3}]}, + {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1}, + {"x": 1.5, "y": 1.5, "identifier": 2}, + {"x": 1.5, "y": 2, "identifier": 3}]}, + {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}, + {"x": 1.5, "y": 1.5, "identifier": 2}, + {"x": 1.5, "y": 2, "identifier": 3}]} + ], + "expectedGestures": [{ "type": "swiperight", "fingers": 3 }] + }, + { + "events": [ + {"type": "pointerdown", + "points": [{"x": 1.6, "y": 1.5, "identifier": 1}], + "removeDwellThreshold": true}, + {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "dwell" }, { "type": "explore" }, + { "type": "exploreend" }] + }, + { + "events": [ + {"type": "pointerdown", + "points": [{"x": 1.6, "y": 1.5, "identifier": 1}], + "removeDwellThreshold": true}, + {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}]}, + {"type": "pointermove", "points": [{"x": 2, "y": 2, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 2, "y": 2, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "dwell" }, { "type": "explore" }, + { "type": "explore" }, { "type": "exploreend" }] + }, + { + "events": [ + {"type": "pointerdown", + "points": [{"x": 1.6, "y": 1.5, "identifier": 1}]}, + {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}], + "removeSwipeMaxDuration": true}, + {"type": "pointerup", "points": [{"x": 1, "y": 1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "explore" }, { "type": "exploreend" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1.5, "identifier": 1}]}, + {"type": "pointermove", "points": [{"x": 1, "y": 1, "identifier": 1}], + "removeSwipeMaxDuration": true}, + {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "explore" }, { "type": "explore" }, + { "type": "exploreend" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 1, "y": 1, "identifier": 1}], + "removeDwellThreshold": true}, + {"type": "pointermove", + "points": [{"x": 1.5, "y": 1.5, "identifier": 1}]}, + {"type": "pointermove", + "points": [{"x": 1.55, "y": 1.5, "identifier": 1}]}, + {"type": "pointermove", + "points": [{"x": 1.6, "y": 1.5, "identifier": 1}]}, + {"type": "pointermove", + "points": [{"x": 1.65, "y": 1.5, "identifier": 1}]}, + {"type": "pointermove", + "points": [{"x": 1.7, "y": 1.5, "identifier": 1}]}, + {"type": "pointermove", + "points": [{"x": 1.75, "y": 1.5, "identifier": 1}]}, + {"type": "pointerup", "points": [{"x": 1.75, "y": 1.5, "identifier": 1}]} + ], + "expectedGestures": [{ "type": "dwell" }, { "type": "explore" }, + { "type": "explore" }, { "type": "exploreend" }] + }, + { + "events": [ + {"type": "pointerdown", "points": [{"x": 0.075, "y": 1, "identifier": 1}, + {"x": 1, "y": 1.5, "identifier": 2}]}, + {"type": "pointermove", "points": [{"x": 1.5, "y": 1, "identifier": 1}, + {"x": 1.5, "y": 1.5, "identifier": 2}]}, + {"type": "pointerup", "points": [{"x": 1.5, "y": 1, "identifier": 1}, + {"x": 1.5, "y": 1.5, "identifier": 2}]} + ], + "expectedGestures": [{ "type": "swiperight", "edge": true, "fingers": 2 }] + }, + { + "title": "Bug 1182311 - 3 finger triple tap is not reliable 1/2", + "events": [ + {"points": [ + {"y": 1.88467, "x": 0.89311, "identifier": 0}, + {"y": 2.78481, "x": 0.56259, "identifier": 1}, + {"y": 1.35021, "x": 1.37834, "identifier": 2}], "type": "pointerdown"}, + {"points": [ + {"y": 1.88467, "x": 0.89311, "identifier": 0}, + {"y": 2.78481, "x": 0.56259, "identifier": 1}, + {"y": 1.35021, "x": 1.37834, "identifier": 2}], "type": "pointerup"}, + {"points": [ + {"y": 1.76512, "x": 0.98453, "identifier": 0}, + {"y": 1.1744, "x": 1.4346, "identifier": 1}, + {"y": 2.5879, "x": 0.61181, "identifier": 2}], "type": "pointerdown"}, + {"points": [ + {"y": 1.76512, "x": 0.98453, "identifier": 0}, + {"y": 1.1744, "x": 1.4346, "identifier": 1}, + {"y": 2.5879, "x": 0.61181, "identifier": 2}], "type": "pointerup"}, + {"points": [ + {"y": 1.30098, "x": 1.52602, "identifier": 0}, + {"y": 1.94093, "x": 1.02672, "identifier": 1}, + {"y": 2.67229, "x": 0.75246, "identifier": 2}], "type": "pointerdown"}, + {"points": [ + {"y": 1.30098, "x": 1.52602, "identifier": 0}, + {"y": 1.94093, "x": 1.02672, "identifier": 1}, + {"y": 2.67229, "x": 0.75246, "identifier": 2}], "type": "pointerup", + "removeGestureResolveDelay": true}], + "expectedGestures": [{ "type": "tripletap", "fingers": 3 }] + }, + { + "title": "Bug 1182311 - 3 finger triple tap is not reliable 2/2", + "events": [ + {"type": "pointerdown", + "points": [{"identifier": 0, "x": 2.21875, "y": 1.510417}]}, + {"type": "pointerdown", + "points": [{"identifier": 1, "x": 1.479167, "y": 2.53125}]}, + {"type": "pointerdown", + "points": [{"identifier": 2, "x": 1.072917, "y": 3.739583}]}, + {"type": "pointermove", + "points": [{"identifier": 1, "x": 1.46875, "y": 2.53125}]}, + {"type": "pointermove", + "points": [{"identifier": 1, "x": 1.447917, "y": 2.46875}]}, + {"type": "pointerup", + "points": [{"identifier": 0, "x": 2.21875, "y": 1.510417}]}, + {"type": "pointerup", + "points": [{"identifier": 1, "x": 1.447917, "y": 2.489583}]}, + {"type": "pointerup", + "points": [{"identifier": 2, "x": 1.072917, "y": 3.739583}]}, + {"type": "pointerdown", + "points": [{"identifier": 0, "x": 2.114583, "y": 1.572917}]}, + {"type": "pointerdown", + "points": [{"identifier": 1, "x": 1.364583, "y": 2.614583}]}, + {"type": "pointerdown", + "points": [{"identifier": 2, "x": 0.927083, "y": 3.864583}]}, + {"type": "pointermove", + "points": [{"identifier": 1, "x": 1.364583, "y": 2.614583}]}, + {"type": "pointermove", + "points": [{"identifier": 0, "x": 2.114583, "y": 1.572917}]}, + {"type": "pointerup", + "points": [{"identifier": 1, "x": 1.364583, "y": 2.614583}]}, + {"type": "pointerup", + "points": [{"identifier": 2, "x": 0.927083, "y": 3.864583}]}, + {"type": "pointerup", + "points": [{"identifier": 0, "x": 2.114583, "y": 1.572917}]}, + {"type": "pointerdown", + "points": [{"identifier": 0, "x": 1.4375, "y": 2.59375}]}, + {"type": "pointerdown", + "points": [{"identifier": 1, "x": 1.083333, "y": 3.71875}]}, + {"type": "pointerdown", + "points": [{"identifier": 2, "x": 2.15625, "y": 1.489583}]}, + {"type": "pointermove", + "points": [{"identifier": 0, "x": 1.4375, "y": 2.59375}, + {"identifier": 2, "x": 2.15625, "y": 1.489583}]}, + {"type": "pointermove", + "points": [{"identifier": 0, "x": 1.4375, "y": 2.59375}, + {"identifier": 2, "x": 2.15625, "y": 1.489583}]}, + {"type": "pointerup", + "points": [{"identifier": 1, "x": 1.083333, "y": 3.71875}], + "removeGestureResolveDelay": true} + ], + "expectedGestures": [{ "type": "tripletap", "fingers": 3 }] + } + +] diff --git a/accessible/tests/mochitest/jsat/jsatcommon.js b/accessible/tests/mochitest/jsat/jsatcommon.js new file mode 100644 index 000000000..aa7ee74e4 --- /dev/null +++ b/accessible/tests/mochitest/jsat/jsatcommon.js @@ -0,0 +1,739 @@ +// A common module to run tests on the AccessFu module + +'use strict'; + +/*global isDeeply, getMainChromeWindow, SimpleTest, SpecialPowers, Logger, + AccessFu, Utils, addMessageListener, currentTabDocument, currentBrowser*/ + +/** + * A global variable holding an array of test functions. + */ +var gTestFuncs = []; +/** + * A global Iterator for the array of test functions. + */ +var gIterator; + +Components.utils.import('resource://gre/modules/Services.jsm'); +Components.utils.import("resource://gre/modules/accessibility/Utils.jsm"); +Components.utils.import("resource://gre/modules/accessibility/EventManager.jsm"); +Components.utils.import("resource://gre/modules/accessibility/Gestures.jsm"); + +var AccessFuTest = { + + addFunc: function AccessFuTest_addFunc(aFunc) { + if (aFunc) { + gTestFuncs.push(aFunc); + } + }, + + _registerListener: function AccessFuTest__registerListener(aWaitForMessage, aListenerFunc) { + var listener = { + observe: function observe(aMessage) { + // Ignore unexpected messages. + if (!(aMessage instanceof Components.interfaces.nsIConsoleMessage)) { + return; + } + if (aMessage.message.indexOf(aWaitForMessage) < 0) { + return; + } + aListenerFunc.apply(listener); + } + }; + Services.console.registerListener(listener); + return listener; + }, + + on_log: function AccessFuTest_on_log(aWaitForMessage, aListenerFunc) { + return this._registerListener(aWaitForMessage, aListenerFunc); + }, + + off_log: function AccessFuTest_off_log(aListener) { + Services.console.unregisterListener(aListener); + }, + + once_log: function AccessFuTest_once_log(aWaitForMessage, aListenerFunc) { + return this._registerListener(aWaitForMessage, + function listenAndUnregister() { + Services.console.unregisterListener(this); + aListenerFunc(); + }); + }, + + _addObserver: function AccessFuTest__addObserver(aWaitForData, aListener) { + var listener = function listener(aSubject, aTopic, aData) { + var data = JSON.parse(aData)[1]; + // Ignore non-relevant outputs. + if (!data) { + return; + } + isDeeply(data.details, aWaitForData, "Data is correct"); + aListener.apply(listener); + }; + Services.obs.addObserver(listener, 'accessibility-output', false); + return listener; + }, + + on: function AccessFuTest_on(aWaitForData, aListener) { + return this._addObserver(aWaitForData, aListener); + }, + + off: function AccessFuTest_off(aListener) { + Services.obs.removeObserver(aListener, 'accessibility-output'); + }, + + once: function AccessFuTest_once(aWaitForData, aListener) { + return this._addObserver(aWaitForData, function observerAndRemove() { + Services.obs.removeObserver(this, 'accessibility-output'); + aListener(); + }); + }, + + _waitForExplicitFinish: false, + + waitForExplicitFinish: function AccessFuTest_waitForExplicitFinish() { + this._waitForExplicitFinish = true; + }, + + finish: function AccessFuTest_finish() { + // Disable the console service logging. + Logger.test = false; + Logger.logLevel = Logger.INFO; + // Reset Gesture Settings. + GestureSettings.dwellThreshold = this.dwellThreshold = + this.originalDwellThreshold; + GestureSettings.swipeMaxDuration = this.swipeMaxDuration = + this.originalSwipeMaxDuration; + GestureSettings.maxGestureResolveTimeout = + this.maxGestureResolveTimeout = + this.originalMaxGestureResolveTimeout; + // Finish through idle callback to let AccessFu._disable complete. + SimpleTest.executeSoon(function () { + AccessFu.detach(); + SimpleTest.finish(); + }); + }, + + nextTest: function AccessFuTest_nextTest() { + var result = gIterator.next(); + if (result.done) { + this.finish(); + return; + } + var testFunc = result.value; + testFunc(); + }, + + runTests: function AccessFuTest_runTests(aAdditionalPrefs) { + if (gTestFuncs.length === 0) { + ok(false, "No tests specified!"); + SimpleTest.finish(); + return; + } + + // Create an Iterator for gTestFuncs array. + gIterator = (function*() { + for (var testFunc of gTestFuncs) { + yield testFunc; + } + })(); + + // Start AccessFu and put it in stand-by. + Components.utils.import("resource://gre/modules/accessibility/AccessFu.jsm"); + + AccessFu.attach(getMainChromeWindow(window)); + + AccessFu.readyCallback = function readyCallback() { + // Enable logging to the console service. + Logger.test = true; + Logger.logLevel = Logger.DEBUG; + }; + + var prefs = [['accessibility.accessfu.notify_output', 1], + ['dom.mozSettings.enabled', true]]; + prefs.push.apply(prefs, aAdditionalPrefs); + + this.originalDwellThreshold = GestureSettings.dwellThreshold; + this.originalSwipeMaxDuration = GestureSettings.swipeMaxDuration; + this.originalMaxGestureResolveTimeout = + GestureSettings.maxGestureResolveTimeout; + // https://bugzilla.mozilla.org/show_bug.cgi?id=1001945 - sometimes + // SimpleTest.executeSoon timeout is bigger than the timer settings in + // GestureSettings that causes intermittents. + this.dwellThreshold = GestureSettings.dwellThreshold = + GestureSettings.dwellThreshold * 10; + this.swipeMaxDuration = GestureSettings.swipeMaxDuration = + GestureSettings.swipeMaxDuration * 10; + this.maxGestureResolveTimeout = GestureSettings.maxGestureResolveTimeout = + GestureSettings.maxGestureResolveTimeout * 10; + + SpecialPowers.pushPrefEnv({ 'set': prefs }, function () { + if (AccessFuTest._waitForExplicitFinish) { + // Run all test functions asynchronously. + AccessFuTest.nextTest(); + } else { + // Run all test functions synchronously. + gTestFuncs.forEach(testFunc => testFunc()); + AccessFuTest.finish(); + } + }); + } +}; + +function AccessFuContentTest(aFuncResultPairs) { + this.queue = aFuncResultPairs; +} + +AccessFuContentTest.prototype = { + expected: [], + currentAction: null, + actionNum: -1, + + start: function(aFinishedCallback) { + Logger.logLevel = Logger.DEBUG; + this.finishedCallback = aFinishedCallback; + var self = this; + + // Get top content message manager, and set it up. + this.mms = [Utils.getMessageManager(currentBrowser())]; + this.setupMessageManager(this.mms[0], function () { + // Get child message managers and set them up + var frames = currentTabDocument().querySelectorAll('iframe'); + if (frames.length === 0) { + self.pump(); + return; + } + + var toSetup = 0; + for (var i = 0; i < frames.length; i++ ) { + var mm = Utils.getMessageManager(frames[i]); + if (mm) { + toSetup++; + self.mms.push(mm); + self.setupMessageManager(mm, function () { + if (--toSetup === 0) { + // All message managers are loaded and ready to go. + self.pump(); + } + }); + } + } + }); + }, + + finish: function() { + Logger.logLevel = Logger.INFO; + for (var mm of this.mms) { + mm.sendAsyncMessage('AccessFu:Stop'); + mm.removeMessageListener('AccessFu:Present', this); + mm.removeMessageListener('AccessFu:Input', this); + mm.removeMessageListener('AccessFu:CursorCleared', this); + mm.removeMessageListener('AccessFu:Focused', this); + mm.removeMessageListener('AccessFu:AriaHidden', this); + mm.removeMessageListener('AccessFu:Ready', this); + mm.removeMessageListener('AccessFu:ContentStarted', this); + } + if (this.finishedCallback) { + this.finishedCallback(); + } + }, + + setupMessageManager: function (aMessageManager, aCallback) { + function contentScript() { + addMessageListener('AccessFuTest:Focus', function (aMessage) { + var elem = content.document.querySelector(aMessage.json.selector); + if (elem) { + if (aMessage.json.blur) { + elem.blur(); + } else { + elem.focus(); + } + } + }); + } + + aMessageManager.addMessageListener('AccessFu:Present', this); + aMessageManager.addMessageListener('AccessFu:Input', this); + aMessageManager.addMessageListener('AccessFu:CursorCleared', this); + aMessageManager.addMessageListener('AccessFu:Focused', this); + aMessageManager.addMessageListener('AccessFu:AriaHidden', this); + aMessageManager.addMessageListener('AccessFu:Ready', function () { + aMessageManager.addMessageListener('AccessFu:ContentStarted', aCallback); + aMessageManager.sendAsyncMessage('AccessFu:Start', + { buildApp: 'browser', + androidSdkVersion: Utils.AndroidSdkVersion, + logLevel: 'DEBUG', + inTest: true }); + }); + + aMessageManager.loadFrameScript( + 'chrome://global/content/accessibility/content-script.js', false); + aMessageManager.loadFrameScript( + 'data:,(' + contentScript.toString() + ')();', false); + }, + + pump: function() { + this.expected.shift(); + if (this.expected.length) { + return; + } + + var currentPair = this.queue.shift(); + + if (currentPair) { + this.actionNum++; + this.currentAction = currentPair[0]; + if (typeof this.currentAction === 'function') { + this.currentAction(this.mms[0]); + } else if (this.currentAction) { + this.mms[0].sendAsyncMessage(this.currentAction.name, + this.currentAction.json); + } + + this.expected = currentPair.slice(1, currentPair.length); + + if (!this.expected[0]) { + this.pump(); + } + } else { + this.finish(); + } + }, + + receiveMessage: function(aMessage) { + var expected = this.expected[0]; + + if (!expected) { + return; + } + + var actionsString = typeof this.currentAction === 'function' ? + this.currentAction.name + '()' : JSON.stringify(this.currentAction); + + if (typeof expected === 'string') { + ok(true, 'Got ' + expected + ' after ' + actionsString); + this.pump(); + } else if (expected.ignore && !expected.ignore(aMessage)) { + expected.is(aMessage.json, 'after ' + actionsString + + ' (' + this.actionNum + ')'); + expected.is_correct_focus(); + this.pump(); + } + } +}; + +// Common content messages + +var ContentMessages = { + simpleMoveFirst: { + name: 'AccessFu:MoveCursor', + json: { + action: 'moveFirst', + rule: 'Simple', + inputType: 'gesture', + origin: 'top' + } + }, + + simpleMoveLast: { + name: 'AccessFu:MoveCursor', + json: { + action: 'moveLast', + rule: 'Simple', + inputType: 'gesture', + origin: 'top' + } + }, + + simpleMoveNext: { + name: 'AccessFu:MoveCursor', + json: { + action: 'moveNext', + rule: 'Simple', + inputType: 'gesture', + origin: 'top' + } + }, + + simpleMovePrevious: { + name: 'AccessFu:MoveCursor', + json: { + action: 'movePrevious', + rule: 'Simple', + inputType: 'gesture', + origin: 'top' + } + }, + + clearCursor: { + name: 'AccessFu:ClearCursor', + json: { + origin: 'top' + } + }, + + moveOrAdjustUp: function moveOrAdjustUp(aRule) { + return { + name: 'AccessFu:MoveCursor', + json: { + origin: 'top', + action: 'movePrevious', + inputType: 'gesture', + rule: (aRule || 'Simple'), + adjustRange: true + } + } + }, + + moveOrAdjustDown: function moveOrAdjustUp(aRule) { + return { + name: 'AccessFu:MoveCursor', + json: { + origin: 'top', + action: 'moveNext', + inputType: 'gesture', + rule: (aRule || 'Simple'), + adjustRange: true + } + } + }, + + androidScrollForward: function adjustUp() { + return { + name: 'AccessFu:AndroidScroll', + json: { origin: 'top', direction: 'forward' } + }; + }, + + androidScrollBackward: function adjustDown() { + return { + name: 'AccessFu:AndroidScroll', + json: { origin: 'top', direction: 'backward' } + }; + }, + + focusSelector: function focusSelector(aSelector, aBlur) { + return { + name: 'AccessFuTest:Focus', + json: { + selector: aSelector, + blur: aBlur + } + }; + }, + + activateCurrent: function activateCurrent(aOffset) { + return { + name: 'AccessFu:Activate', + json: { + origin: 'top', + offset: aOffset + } + }; + }, + + moveNextBy: function moveNextBy(aGranularity) { + return { + name: 'AccessFu:MoveByGranularity', + json: { + direction: 'Next', + granularity: this._granularityMap[aGranularity] + } + }; + }, + + movePreviousBy: function movePreviousBy(aGranularity) { + return { + name: 'AccessFu:MoveByGranularity', + json: { + direction: 'Previous', + granularity: this._granularityMap[aGranularity] + } + }; + }, + + moveCaretNextBy: function moveCaretNextBy(aGranularity) { + return { + name: 'AccessFu:MoveCaret', + json: { + direction: 'Next', + granularity: this._granularityMap[aGranularity] + } + }; + }, + + moveCaretPreviousBy: function moveCaretPreviousBy(aGranularity) { + return { + name: 'AccessFu:MoveCaret', + json: { + direction: 'Previous', + granularity: this._granularityMap[aGranularity] + } + }; + }, + + _granularityMap: { + 'character': 1, // MOVEMENT_GRANULARITY_CHARACTER + 'word': 2, // MOVEMENT_GRANULARITY_WORD + 'paragraph': 8 // MOVEMENT_GRANULARITY_PARAGRAPH + } +}; + +function ExpectedMessage (aName, aOptions) { + this.name = aName; + this.options = aOptions || {}; + this.json = {}; +} + +ExpectedMessage.prototype.lazyCompare = function(aReceived, aExpected, aInfo) { + if (aExpected && !aReceived) { + return [false, 'Expected something but got nothing -- ' + aInfo]; + } + + var matches = true; + var delta = []; + for (var attr in aExpected) { + var expected = aExpected[attr]; + var received = aReceived[attr]; + if (typeof expected === 'object') { + var [childMatches, childDelta] = this.lazyCompare(received, expected); + if (!childMatches) { + delta.push(attr + ' [ ' + childDelta + ' ]'); + matches = false; + } + } else { + if (received !== expected) { + delta.push( + attr + ' [ expected ' + JSON.stringify(expected) + + ' got ' + JSON.stringify(received) + ' ]'); + matches = false; + } + } + } + + var msg = delta.length ? delta.join(' ') : 'Structures lazily match'; + return [matches, msg + ' -- ' + aInfo]; +}; + +ExpectedMessage.prototype.is = function(aReceived, aInfo) { + var checkFunc = this.options.todo ? 'todo' : 'ok'; + SimpleTest[checkFunc].apply( + SimpleTest, this.lazyCompare(aReceived, this.json, aInfo)); +}; + +ExpectedMessage.prototype.is_correct_focus = function(aInfo) { + if (!this.options.focused) { + return; + } + + var checkFunc = this.options.focused_todo ? 'todo_is' : 'is'; + var doc = currentTabDocument(); + SimpleTest[checkFunc].apply(SimpleTest, + [ doc.activeElement, doc.querySelector(this.options.focused), + 'Correct element is focused: ' + this.options.focused + ' -- ' + aInfo ]); +}; + +ExpectedMessage.prototype.ignore = function(aMessage) { + return aMessage.name !== this.name; +}; + +function ExpectedPresent(aB2g, aAndroid, aOptions) { + ExpectedMessage.call(this, 'AccessFu:Present', aOptions); + if (aB2g) { + this.json.b2g = aB2g; + } + + if (aAndroid) { + this.json.android = aAndroid; + } +} + +ExpectedPresent.prototype = Object.create(ExpectedMessage.prototype); + +ExpectedPresent.prototype.is = function(aReceived, aInfo) { + var received = this.extract_presenters(aReceived); + + for (var presenter of ['b2g', 'android']) { + if (!this.options['no_' + presenter]) { + var todo = this.options.todo || this.options[presenter + '_todo'] + SimpleTest[todo ? 'todo' : 'ok'].apply( + SimpleTest, this.lazyCompare(received[presenter], + this.json[presenter], aInfo + ' (' + presenter + ')')); + } + } +}; + +ExpectedPresent.prototype.extract_presenters = function(aReceived) { + var received = { count: 0 }; + for (var presenter of aReceived) { + if (presenter) { + received[presenter.type.toLowerCase()] = presenter.details; + received.count++; + } + } + + return received +}; + +ExpectedPresent.prototype.ignore = function(aMessage) { + if (ExpectedMessage.prototype.ignore.call(this, aMessage)) { + return true; + } + + var received = this.extract_presenters(aMessage.json); + return received.count === 0 || + (received.visual && received.visual.eventType === 'viewport-change') || + (received.android && + received.android[0].eventType === AndroidEvent.VIEW_SCROLLED); +}; + +function ExpectedCursorChange(aSpeech, aOptions) { + ExpectedPresent.call(this, { + eventType: 'vc-change', + data: aSpeech + }, [{ + eventType: 0x8000, // VIEW_ACCESSIBILITY_FOCUSED + }], aOptions); +} + +ExpectedCursorChange.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedCursorTextChange(aSpeech, aStartOffset, aEndOffset, aOptions) { + ExpectedPresent.call(this, { + eventType: 'vc-change', + data: aSpeech + }, [{ + eventType: AndroidEvent.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, + fromIndex: aStartOffset, + toIndex: aEndOffset + }], aOptions); + + // bug 980509 + this.options.b2g_todo = true; +} + +ExpectedCursorTextChange.prototype = + Object.create(ExpectedCursorChange.prototype); + +function ExpectedClickAction(aOptions) { + ExpectedPresent.call(this, { + eventType: 'action', + data: [{ string: 'clickAction' }] + }, [{ + eventType: AndroidEvent.VIEW_CLICKED + }], aOptions); +} + +ExpectedClickAction.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedCheckAction(aChecked, aOptions) { + ExpectedPresent.call(this, { + eventType: 'action', + data: [{ string: aChecked ? 'checkAction' : 'uncheckAction' }] + }, [{ + eventType: AndroidEvent.VIEW_CLICKED, + checked: aChecked + }], aOptions); +} + +ExpectedCheckAction.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedSwitchAction(aSwitched, aOptions) { + ExpectedPresent.call(this, { + eventType: 'action', + data: [{ string: aSwitched ? 'onAction' : 'offAction' }] + }, [{ + eventType: AndroidEvent.VIEW_CLICKED, + checked: aSwitched + }], aOptions); +} + +ExpectedSwitchAction.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedNameChange(aName, aOptions) { + ExpectedPresent.call(this, { + eventType: 'name-change', + data: aName + }, null, aOptions); +} + +ExpectedNameChange.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedValueChange(aValue, aOptions) { + ExpectedPresent.call(this, { + eventType: 'value-change', + data: aValue + }, null, aOptions); +} + +ExpectedValueChange.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedTextChanged(aValue, aOptions) { + ExpectedPresent.call(this, { + eventType: 'text-change', + data: aValue + }, null, aOptions); +} + +ExpectedTextChanged.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedEditState(aEditState, aOptions) { + ExpectedMessage.call(this, 'AccessFu:Input', aOptions); + this.json = aEditState; +} + +ExpectedEditState.prototype = Object.create(ExpectedMessage.prototype); + +function ExpectedTextSelectionChanged(aStart, aEnd, aOptions) { + ExpectedPresent.call(this, null, [{ + eventType: AndroidEvent.VIEW_TEXT_SELECTION_CHANGED, + brailleOutput: { + selectionStart: aStart, + selectionEnd: aEnd + }}], aOptions); +} + +ExpectedTextSelectionChanged.prototype = + Object.create(ExpectedPresent.prototype); + +function ExpectedTextCaretChanged(aFrom, aTo, aOptions) { + ExpectedPresent.call(this, null, [{ + eventType: AndroidEvent.VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY, + fromIndex: aFrom, + toIndex: aTo + }], aOptions); +} + +ExpectedTextCaretChanged.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedAnnouncement(aAnnouncement, aOptions) { + ExpectedPresent.call(this, null, [{ + eventType: AndroidEvent.ANNOUNCEMENT, + text: [ aAnnouncement], + addedCount: aAnnouncement.length + }], aOptions); +} + +ExpectedAnnouncement.prototype = Object.create(ExpectedPresent.prototype); + +function ExpectedNoMove(aOptions) { + ExpectedPresent.call(this, {eventType: 'no-move' }, null, aOptions); +} + +ExpectedNoMove.prototype = Object.create(ExpectedPresent.prototype); + +var AndroidEvent = { + VIEW_CLICKED: 0x01, + VIEW_LONG_CLICKED: 0x02, + VIEW_SELECTED: 0x04, + VIEW_FOCUSED: 0x08, + VIEW_TEXT_CHANGED: 0x10, + WINDOW_STATE_CHANGED: 0x20, + VIEW_HOVER_ENTER: 0x80, + VIEW_HOVER_EXIT: 0x100, + VIEW_SCROLLED: 0x1000, + VIEW_TEXT_SELECTION_CHANGED: 0x2000, + ANNOUNCEMENT: 0x4000, + VIEW_ACCESSIBILITY_FOCUSED: 0x8000, + VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY: 0x20000 +}; diff --git a/accessible/tests/mochitest/jsat/output.js b/accessible/tests/mochitest/jsat/output.js new file mode 100644 index 000000000..5afcc8a66 --- /dev/null +++ b/accessible/tests/mochitest/jsat/output.js @@ -0,0 +1,114 @@ +var Cu = Components.utils; +const PREF_UTTERANCE_ORDER = "accessibility.accessfu.utterance"; + +Cu.import('resource://gre/modules/accessibility/Utils.jsm'); +Cu.import("resource://gre/modules/accessibility/OutputGenerator.jsm", this); + +/** + * Test context output generation. + * + * @param expected {Array} expected output. + * @param aAccOrElmOrID identifier to get an accessible to test. + * @param aOldAccOrElmOrID optional identifier to get an accessible relative to + * the |aAccOrElmOrID|. + * @param aGenerator the output generator to use when generating accessible + * output + * + * Note: if |aOldAccOrElmOrID| is not provided, the |aAccOrElmOrID| must be + * scoped to the "root" element in markup. + */ +function testContextOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, aGenerator) { + var accessible = getAccessible(aAccOrElmOrID); + var oldAccessible = aOldAccOrElmOrID !== null ? + getAccessible(aOldAccOrElmOrID || 'root') : null; + var context = new PivotContext(accessible, oldAccessible); + var output = aGenerator.genForContext(context); + + // Create a version of the output that has null members where we have + // null members in the expected output. Those are indexes that are not testable + // because of the changing nature of the test (different window names), or strings + // that are inaccessible to us, like the title of parent documents. + var masked_output = []; + for (var i=0; i < output.length; i++) { + if (expected[i] === null) { + masked_output.push(null); + } else { + masked_output[i] = typeof output[i] === "string" ? output[i].trim() : + output[i]; + } + } + + isDeeply(masked_output, expected, + "Context output is correct for " + aAccOrElmOrID + + " (output: " + JSON.stringify(output) + ") ==" + + " (expected: " + JSON.stringify(expected) + ")"); +} + +/** + * Test object output generated array that includes names. + * Note: test ignores outputs without the name. + * + * @param aAccOrElmOrID identifier to get an accessible to test. + * @param aGenerator the output generator to use when generating accessible + * output + */ +function testObjectOutput(aAccOrElmOrID, aGenerator) { + var accessible = getAccessible(aAccOrElmOrID); + if (!accessible.name || !accessible.name.trim()) { + return; + } + var context = new PivotContext(accessible); + var output = aGenerator.genForObject(accessible, context); + var outputOrder; + try { + outputOrder = SpecialPowers.getIntPref(PREF_UTTERANCE_ORDER); + } catch (ex) { + // PREF_UTTERANCE_ORDER not set. + outputOrder = 0; + } + var expectedNameIndex = outputOrder === 0 ? output.length - 1 : 0; + var nameIndex = output.indexOf(accessible.name); + + if (nameIndex > -1) { + ok(output.indexOf(accessible.name) === expectedNameIndex, + "Object output is correct for " + aAccOrElmOrID); + } +} + +/** + * Test object and context output for an accessible. + * + * @param expected {Array} expected output. + * @param aAccOrElmOrID identifier to get an accessible to test. + * @param aOldAccOrElmOrID optional identifier to get an accessible relative to + * the |aAccOrElmOrID|. + * @param aOutputKind the type of output + */ +function testOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, aOutputKind) { + var generator; + if (aOutputKind === 1) { + generator = UtteranceGenerator; + } else { + generator = BrailleGenerator; + } + testContextOutput(expected, aAccOrElmOrID, aOldAccOrElmOrID, generator); + // Just need to test object output for individual + // accOrElmOrID. + if (aOldAccOrElmOrID) { + return; + } + testObjectOutput(aAccOrElmOrID, generator); +} + +function testHints(expected, aAccOrElmOrID, aOldAccOrElmOrID) { + var accessible = getAccessible(aAccOrElmOrID); + var oldAccessible = aOldAccOrElmOrID !== null ? + getAccessible(aOldAccOrElmOrID || 'root') : null; + var context = new PivotContext(accessible, oldAccessible); + var hints = context.interactionHints; + + isDeeply(hints, expected, + "Context hitns are correct for " + aAccOrElmOrID + + " (hints: " + JSON.stringify(hints) + ") ==" + + " (expected: " + JSON.stringify(expected) + ")"); +} diff --git a/accessible/tests/mochitest/jsat/test_alive.html b/accessible/tests/mochitest/jsat/test_alive.html new file mode 100644 index 000000000..cd4eef712 --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_alive.html @@ -0,0 +1,81 @@ +<html> + +<head> + <title>AccessFu test for enabling</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="./jsatcommon.js"></script> + <script type="application/javascript"> + + function prefStart() { + AccessFuTest.once_log("AccessFu:Enabled", () => + ok(AccessFu._enabled, "AccessFu was enabled again.")); + AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest); + // Start AccessFu via pref. + SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 1]]}); + } + + // Listen for 'EventManager.stop' and enable AccessFu again. + function settingsStart() { + isnot(AccessFu._enabled, true, "AccessFu was disabled."); + // XXX: Bug 978076 - test start with SettingsManager. + //navigator.mozSettings.createLock().set( + // {'accessibility.screenreader': false}); + AccessFuTest.once_log("EventManager.start", () => { + ok(AccessFu._enabled, "AccessFu was enabled again."); + AccessFuTest.nextTest(); + }); + AccessFu._enable(); + } + + // Make sure EventManager is started again. + function settingsStop() { + ok(AccessFu._enabled, "AccessFu was enabled again."); + // XXX: Bug 978076 - test stop with SettingsManager. + //navigator.mozSettings.createLock().set( + // {'accessibility.screenreader': false}); + AccessFuTest.once_log("EventManager.stop", () => { + isnot(AccessFu._enabled, "AccessFu was disabled."); + AccessFuTest.finish(); + }); + AccessFu._disable(); + } + + // Listen for initial 'EventManager.start' and disable AccessFu. + function prefStop() { + ok(AccessFu._enabled, "AccessFu was started via preference."); + AccessFuTest.once_log("AccessFu:Disabled", () => + isnot(AccessFu._enabled, true, "AccessFu was disabled.")); + AccessFuTest.once_log("EventManager.stop", AccessFuTest.nextTest); + + SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 0]]}); + } + + function doTest() { + AccessFuTest.addFunc(prefStart); + AccessFuTest.addFunc(prefStop); + AccessFuTest.addFunc(settingsStart); + AccessFuTest.addFunc(settingsStop); + AccessFuTest.waitForExplicitFinish(); + AccessFuTest.runTests(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=811307" + title="[AccessFu] Add mochitest for enabling"> + Mozilla Bug 811307 + </a> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_content_integration.html b/accessible/tests/mochitest/jsat/test_content_integration.html new file mode 100644 index 000000000..809f79726 --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_content_integration.html @@ -0,0 +1,343 @@ +<!DOCTYPE html> +<html> +<head> + <title>Tests AccessFu content integration</title> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + <script type="application/javascript" + 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="../layout.js"></script> + <script type="application/javascript" src="jsatcommon.js"></script> + + <script type="application/javascript"> + function doTest() { + var doc = currentTabDocument(); + var iframe = doc.createElement('iframe'); + iframe.id = 'iframe'; + iframe.mozbrowser = true; + iframe.addEventListener('mozbrowserloadend', function () { + var contentTest = new AccessFuContentTest( + [ + // Simple traversal forward + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange( + ['Traversal Rule test document', 'Phone status bar'], + { focused: 'body' })], + [ContentMessages.simpleMovePrevious, new ExpectedNoMove()], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], + [ContentMessages.simpleMoveNext, new ExpectedCursorChange( + ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}], + { focused: 'iframe' })], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['many option', {'string': 'stateNotChecked'}, + {'string': 'checkbutton'}, {'string': 'listStart'}, + {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])], + + // check checkbox + [ContentMessages.activateCurrent(), + new ExpectedClickAction({ no_android: true }), + new ExpectedCheckAction(true)], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['much range', {'string': 'label'}])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['much range', '5', {'string': 'slider'}])], + [ContentMessages.moveOrAdjustUp(), new ExpectedValueChange('6')], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Light', {"string": "stateOff"}, {'string': 'switch'}])], + // switch on + [ContentMessages.activateCurrent(), + new ExpectedClickAction({ no_android: true }), + new ExpectedSwitchAction(true)], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['slider', '0', {'string': 'slider'}])], + + // Simple traversal backward + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange(['Light', {"string": "stateOn"}, {'string': 'switch'}])], + // switch off + [ContentMessages.activateCurrent(), + new ExpectedClickAction({ no_android: true }), + new ExpectedSwitchAction(false)], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange(['such app', 'much range', '6', {'string': 'slider'}])], + [ContentMessages.moveOrAdjustDown(), new ExpectedValueChange('5')], + [ContentMessages.androidScrollForward(), new ExpectedValueChange('6')], + [ContentMessages.androidScrollBackward(), new ExpectedValueChange('5')], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange(['much range', {'string': 'label'}])], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange(['many option', {'string': 'stateChecked'}, + {'string': 'checkbutton'}, {'string': 'listStart'}, + {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])], + // uncheck checkbox + [ContentMessages.activateCurrent(), + new ExpectedClickAction({ no_android: true }), + new ExpectedCheckAction(false)], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange(['wow', {'string': 'headingLevel', 'args': [1]}])], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange(['Phone status bar'])], + + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], + // Moving to the absolute last item from an embedded document + // fails. Bug 972035. + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange( + ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])], + // Move from an inner frame to the last element in the parent doc + [ContentMessages.simpleMoveLast, + new ExpectedCursorChange( + ['slider', '0', {'string': 'slider'}], { b2g_todo: true })], + + [ContentMessages.clearCursor, 'AccessFu:CursorCleared'], + + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])], + [ContentMessages.moveOrAdjustDown('FormElement'), + new ExpectedCursorChange(['Back', {"string": "pushbutton"}])], + [ContentMessages.moveOrAdjustDown('FormElement'), + new ExpectedCursorChange(['such app', 'many option', {'string': 'stateNotChecked'}, + {'string': 'checkbutton'}, {'string': 'listStart'}, + {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])], + [ContentMessages.moveOrAdjustDown('FormElement'), + new ExpectedCursorChange(['much range', '5', {'string': 'slider'}])], + // Calling AdjustOrMove should adjust the range. + [ContentMessages.moveOrAdjustDown('FormElement'), + new ExpectedValueChange('4')], + [ContentMessages.moveOrAdjustUp('FormElement'), + new ExpectedValueChange('5')], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange(['much range', {'string': 'label'}])], + [ContentMessages.moveOrAdjustUp('FormElement'), + new ExpectedCursorChange(['many option', {'string': 'stateNotChecked'}, + {'string': 'checkbutton'}, {'string': 'listStart'}, + {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])], + [ContentMessages.moveOrAdjustUp('FormElement'), + new ExpectedCursorChange(['Back', {"string": "pushbutton"}])], + + [ContentMessages.clearCursor, 'AccessFu:CursorCleared'], + + // Moving to the absolute first item from an embedded document + // fails. Bug 972035. + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])], + [ContentMessages.simpleMoveNext, new ExpectedCursorChange( + ['many option', {'string': 'stateNotChecked'}, + {'string': 'checkbutton'}, {'string': 'listStart'}, + {'string': 'list'}, {'string': 'listItemsCount', 'count': 1}])], + [ContentMessages.simpleMoveFirst, + new ExpectedCursorChange(['Phone status bar'], { b2g_todo: true })], + + // Reset cursors + [ContentMessages.clearCursor, 'AccessFu:CursorCleared'], + + // Current virtual cursor's position's name changes + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])], + [ContentMessages.focusSelector('button#fruit', false), + new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])], + [doc.defaultView.renameFruit, new ExpectedNameChange('banana')], + + // Name and value changes inside a live-region (no cursor present) + [doc.defaultView.renameSlider, + new ExpectedNameChange('mover')], + [doc.defaultView.changeSliderValue, + new ExpectedValueChange('medium')], + + // Blur button and reset cursor + [ContentMessages.focusSelector('button#fruit', true), null], + [ContentMessages.clearCursor, 'AccessFu:CursorCleared'], + + // Move cursor with focus in outside document + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])], + [ContentMessages.focusSelector('button#home', false), + new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])], + + // Blur button and reset cursor + [ContentMessages.focusSelector('button#home', true), null], + [ContentMessages.clearCursor, 'AccessFu:CursorCleared'], + + // Set focus on element outside of embedded frame while + // cursor is in frame + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])], + [ContentMessages.focusSelector('button#home', false), + new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])], + + // Blur button and reset cursor + [ContentMessages.focusSelector('button#home', true), null], + [ContentMessages.clearCursor, 'AccessFu:CursorCleared'], + + // XXX: Set focus on iframe itself. + // XXX: Set focus on element in iframe when cursor is outside of it. + // XXX: Set focus on element in iframe when cursor is in iframe. + + // aria-hidden element that the virtual cursor is positioned on + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], + [doc.defaultView.ariaHideBack, + new ExpectedCursorChange( + ["such app", "wow", {"string": "headingLevel","args": [1]}])], + // Changing aria-hidden attribute twice and making sure that the event + // is fired only once when the actual change happens. + [doc.defaultView.ariaHideBack], + [doc.defaultView.ariaShowBack], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], + [ContentMessages.clearCursor, 'AccessFu:CursorCleared'], + + // aria-hidden on the iframe that has the vc. + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])], + [doc.defaultView.ariaHideIframe, + new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])], + [doc.defaultView.ariaShowIframe], + [ContentMessages.clearCursor, 'AccessFu:CursorCleared'], + + // aria-hidden element and auto Move + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])], + [doc.defaultView.ariaHideBack], + [ContentMessages.focusSelector('button#back', false), + // Must not speak Back button as it is aria-hidden + new ExpectedCursorChange( + ["such app", "wow", {"string": "headingLevel","args": [1]}])], + [doc.defaultView.ariaShowBack], + [ContentMessages.focusSelector('button#back', true), null], + [ContentMessages.clearCursor, 'AccessFu:CursorCleared'], + + // Open dialog in outer doc, while cursor is also in outer doc + [ContentMessages.simpleMoveLast, + new ExpectedCursorChange(['Traversal Rule test document', 'mover', + 'medium', {'string': 'slider'}])], + [doc.defaultView.showAlert, + new ExpectedCursorChange(['This is an alert!', + {'string': 'headingLevel', 'args': [1]}, + {'string': 'dialog'}])], + + [doc.defaultView.hideAlert, + new ExpectedCursorChange(['Traversal Rule test document', 'mover', + 'medium', {'string': 'slider'}])], + + [ContentMessages.clearCursor, 'AccessFu:CursorCleared'], + + // Open dialog in outer doc, while cursor is in inner frame + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(["Back", {"string": "pushbutton"}])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange( + ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])], + [doc.defaultView.showAlert, new ExpectedCursorChange(['This is an alert!', + {'string': 'headingLevel', 'args': [1]}, + {'string': 'dialog'}])], + + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Do you agree?'])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Yes', {'string': 'pushbutton'}])], + [ContentMessages.activateCurrent(), + new ExpectedClickAction(), + new ExpectedCursorChange( + ['such app', 'wow', {'string': 'headingLevel', 'args': [1]}])], + + [ContentMessages.clearCursor, 'AccessFu:CursorCleared'], + + // Open dialog, then focus on something when closing + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['Traversal Rule test document', 'Phone status bar'])], + [doc.defaultView.showAlert, + new ExpectedCursorChange(['This is an alert!', + {'string': 'headingLevel', 'args': [1]}, {'string': 'dialog'}])], + + [function hideAlertAndFocusHomeButton() { + doc.defaultView.hideAlert(); + doc.querySelector('button#home').focus(); + }, new ExpectedCursorChange(['Traversal Rule test document', + 'Home', {'string': 'pushbutton'}])], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['banana', {'string': 'pushbutton'}])] + [ContentMessages.simpleMoveNext, new ExpectedNoMove()] + ]); + + addA11yLoadEvent(function() { + contentTest.start(function () { + closeBrowserWindow(); + SimpleTest.finish(); + }); + }, doc.defaultView) + }); + iframe.src = 'data:text/html;charset=utf-8,' + doc.defaultView.frameContents; + doc.getElementById('appframe').appendChild(iframe); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent( + function () { + openBrowserWindow( + function () { + SpecialPowers.pushPrefEnv({ + 'set': [ + // TODO: remove this as part of bug 820712 + ['network.disable.ipc.security', true], + + + ['dom.ipc.browser_frames.oop_by_default', true], + ['dom.mozBrowserFramesEnabled', true], + ['browser.pagethumbnails.capturing_disabled', true] + ] + }, doTest) }, + getRootDirectory(window.location.href) + 'doc_content_integration.html'); + }); + </script> +</head> +<body id="body"> + + <a target="_blank" + title="Add tests for OOP message handling and general integration" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=972047">Mozilla Bug 933808</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_content_text.html b/accessible/tests/mochitest/jsat/test_content_text.html new file mode 100644 index 000000000..558b819e9 --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_content_text.html @@ -0,0 +1,292 @@ +<!DOCTYPE html> +<html> +<head> + <title>Tests AccessFu content integration</title> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"> + </script> + <script type="application/javascript" + src="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="../layout.js"></script> + <script type="application/javascript" src="jsatcommon.js"></script> + + <script type="application/javascript"> + function doTest() { + var doc = currentTabDocument(); + var textTest = new AccessFuContentTest( + [ + // Read-only text tests + [ContentMessages.simpleMoveFirst, + new ExpectedCursorChange( + ['Text content test document', 'These are my awards, Mother. ' + + 'From Army. The seal is for marksmanship, and the gorilla is ' + + 'for sand racing.'])], + [ContentMessages.moveNextBy('word'), + new ExpectedCursorTextChange('These', 0, 5)], + [ContentMessages.moveNextBy('word'), + new ExpectedCursorTextChange('are', 6, 9)], + [ContentMessages.moveNextBy('word'), + new ExpectedCursorTextChange('my', 10, 12)], + [ContentMessages.moveNextBy('word'), + new ExpectedCursorTextChange('awards,', 13, 20)], + [ContentMessages.moveNextBy('word'), + new ExpectedCursorTextChange('Mother.', 21, 28)], + [ContentMessages.movePreviousBy('word'), + new ExpectedCursorTextChange('awards,', 13, 20)], + [ContentMessages.movePreviousBy('word'), + new ExpectedCursorTextChange('my', 10, 12)], + [ContentMessages.movePreviousBy('word'), + new ExpectedCursorTextChange('are', 6, 9)], + [ContentMessages.movePreviousBy('word'), + new ExpectedCursorTextChange('These', 0, 5)], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange(['You\'re a good guy, mon frere. ' + + 'That means brother in French. ' + + 'I don\'t know how I know that. ' + + 'I took four years of Spanish.'])], + // XXX: Word boundary should be past the apostraphe. + [ContentMessages.moveNextBy('word'), + new ExpectedCursorTextChange('You\'re', 0, 6, + { android_todo: true /* Bug 980512 */})], + + // Editable text tests. + [ContentMessages.focusSelector('textarea'), + new ExpectedAnnouncement('editing'), + new ExpectedEditState({ + editing: true, + multiline: true, + atStart: true, + atEnd: false + }), + new ExpectedCursorChange( + ['Please refrain from Mayoneggs during this salmonella scare.', + {string: 'textarea'}]), + new ExpectedTextSelectionChanged(0, 0) + ], + [ContentMessages.activateCurrent(10), + new ExpectedTextCaretChanged(0, 10), + new ExpectedEditState({ editing: true, + multiline: true, + atStart: false, + atEnd: false }), + new ExpectedTextSelectionChanged(10, 10)], + [ContentMessages.activateCurrent(20), + new ExpectedTextCaretChanged(10, 20), + new ExpectedTextSelectionChanged(20, 20) + ], + [ContentMessages.moveCaretNextBy('word'), + new ExpectedTextCaretChanged(20, 29), + new ExpectedTextSelectionChanged(29, 29) + ], + [ContentMessages.moveCaretNextBy('word'), + new ExpectedTextCaretChanged(29, 36), + new ExpectedTextSelectionChanged(36, 36) + ], + [ContentMessages.moveCaretNextBy('character'), + new ExpectedTextCaretChanged(36, 37), + new ExpectedTextSelectionChanged(37, 37) + ], + [ContentMessages.moveCaretNextBy('character'), + new ExpectedTextCaretChanged(37, 38), + new ExpectedTextSelectionChanged(38, 38) + ], + [ContentMessages.moveCaretNextBy('paragraph'), + new ExpectedTextCaretChanged(38, 59), + new ExpectedTextSelectionChanged(59, 59) + ], + [ContentMessages.moveCaretPreviousBy('word'), + new ExpectedTextCaretChanged(53, 59), + new ExpectedTextSelectionChanged(53, 53) + ], + + // bug xxx + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange( + ['So we don\'t get dessert?', {string: 'label'}], + { focused: 'html'}), + new ExpectedAnnouncement('navigating'), + new ExpectedEditState({ + editing: false, + multiline: false, + atStart: true, + atEnd: false })], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange( + [{ string : 'entry' }], + { focused: 'html'})], + [ContentMessages.activateCurrent(0), + new ExpectedClickAction(), + new ExpectedAnnouncement('editing'), + new ExpectedEditState({ + editing: true, + multiline: false, + atStart: true, + atEnd: true + }, { focused: 'input[type=text]' }), + new ExpectedTextSelectionChanged(0, 0), + new ExpectedTextSelectionChanged(0, 0) + ], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange( + ['So we don\'t get dessert?', {string: 'label'}]), + new ExpectedAnnouncement('navigating'), + new ExpectedEditState({ + editing: false, + multiline: false, + atStart: true, + atEnd: false + },{ focused: 'html' }) + ], + [ContentMessages.simpleMoveNext, + new ExpectedCursorChange( + [{ string : 'entry' }], + { focused: 'html'})], + [ContentMessages.activateCurrent(0), + new ExpectedClickAction(), + new ExpectedAnnouncement('editing'), + new ExpectedEditState({ + editing: true, + multiline: false, + atStart: true, + atEnd: true + }, + { focused: 'input[type=text]' }), + new ExpectedTextSelectionChanged(0, 0)], + [ContentMessages.simpleMovePrevious, + new ExpectedCursorChange( + [ 'So we don\'t get dessert?', {string: 'label'} ]), + new ExpectedAnnouncement('navigating'), + new ExpectedEditState({ + editing: false, + multiline: false, + atStart: true, + atEnd: false + }, { focused: 'html' })], + + [ContentMessages.focusSelector('input'), + new ExpectedAnnouncement('editing'), + new ExpectedEditState({ + editing: true, + multiline: false, + atStart: true, + atEnd: true + }), + new ExpectedCursorChange([{string: 'entry'}]), + new ExpectedTextSelectionChanged(0, 0) + ], + [function() { + SpecialPowers.pushPrefEnv({"set": [[KEYBOARD_ECHO_SETTING, 3]]}, typeKey('a')()); + }, + new ExpectedTextChanged('a'), + new ExpectedTextSelectionChanged(1, 1), + ], + [typeKey('b'), + new ExpectedTextChanged('b'), + new ExpectedTextSelectionChanged(2, 2), + ], + [typeKey('c'), + new ExpectedTextChanged('c'), + new ExpectedTextSelectionChanged(3, 3), + ], + [typeKey('d'), + new ExpectedTextChanged('d'), + new ExpectedTextSelectionChanged(4, 4), + ], + [typeKey(' '), + new ExpectedTextChanged(' abcd'), + new ExpectedTextSelectionChanged(5, 5), + ], + [typeKey('e'), + new ExpectedTextChanged('e'), + new ExpectedTextSelectionChanged(6, 6), + ], + [function() { + SpecialPowers.pushPrefEnv({"set": [[KEYBOARD_ECHO_SETTING, 2]]}, typeKey('a')()); + }, + new ExpectedTextChanged(''), + new ExpectedTextSelectionChanged(7, 7), + ], + [typeKey('d'), + new ExpectedTextChanged(''), + new ExpectedTextSelectionChanged(8, 8), + ], + [typeKey(' '), + new ExpectedTextChanged(' ead'), + new ExpectedTextSelectionChanged(9, 9), + ], + [function() { + SpecialPowers.pushPrefEnv({"set": [[KEYBOARD_ECHO_SETTING, 1]]}, typeKey('f')()); + }, + new ExpectedTextChanged('f'), + new ExpectedTextSelectionChanged(10, 10), + ], + [typeKey('g'), + new ExpectedTextChanged('g'), + new ExpectedTextSelectionChanged(11, 11), + ], + [typeKey(' '), + new ExpectedTextChanged(' '), + new ExpectedTextSelectionChanged(12, 12), + ], + [function() { + SpecialPowers.pushPrefEnv({"set": [[KEYBOARD_ECHO_SETTING, 0]]}, typeKey('f')()); + }, + new ExpectedTextChanged(''), + new ExpectedTextSelectionChanged(13, 13), + ], + [typeKey('g'), + new ExpectedTextChanged(''), + new ExpectedTextSelectionChanged(14, 14), + ], + [typeKey(' '), + new ExpectedTextChanged(''), + new ExpectedTextSelectionChanged(15, 15), + ], + ]); + + const KEYBOARD_ECHO_SETTING = 'accessibility.accessfu.keyboard_echo'; + function typeKey(key) { + return function() { synthesizeKey(key, {}, currentTabWindow()); }; + } + + addA11yLoadEvent(function() { + textTest.start(function () { + closeBrowserWindow(); + SimpleTest.finish(); + }); + }, doc.defaultView); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent( + function () { + openBrowserWindow( + doTest, + getRootDirectory(window.location.href) + "doc_content_text.html"); + }); + </script> +</head> +<body id="body"> + + <a target="_blank" + title="Add tests for text editing and navigating" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=972047">Mozilla Bug 933808</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_explicit_names.html b/accessible/tests/mochitest/jsat/test_explicit_names.html new file mode 100644 index 000000000..fb7ed2022 --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_explicit_names.html @@ -0,0 +1,191 @@ +<html> +<head> + <title>[AccessFu] Trust explicitly associated names when speaking certain elements</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="output.js"></script> + <script type="application/javascript"> + + function doTest() { + // Test the following accOrElmOrID. + var tests = [{ + accOrElmOrID: "anchor1", + expected: [{"string": "link"}, "title"] + }, { + accOrElmOrID: "anchor2", + expected: [{"string": "link"}, "This is a link"] + }, { + accOrElmOrID: "button1", + expected: [{"string": "pushbutton"}, "Press me"] + }, { + accOrElmOrID: "button2", + expected: [{"string": "pushbutton"}, "Press me"] + }, { + accOrElmOrID: "textarea1", + expected: [{"string": "textarea"}, "This is the text area text.", + "Test Text Area"] + }, { + accOrElmOrID: "textarea2", + expected: [{"string": "textarea"}, "This is the text area text."] + }, { + accOrElmOrID: "heading1", + expected: [{"string": "headingLevel", "args": [1]}, "Test heading", + "This is the heading."] + }, { + accOrElmOrID: "heading1", + oldAccOrElmOrID: null, + expected: [null /* parent doc title */, document.title, + {"string": "headingLevel", "args": [1]}, "Test heading", + "This is the heading."] + }, { + accOrElmOrID: "heading2", + expected: [{"string": "headingLevel", "args": [1]}, + "This is the heading."] + }, { + accOrElmOrID: "list", + expected: [{"string": "list"}, {"string": "listItemsCount", "count": 2}, + "Test List", {"string": "listStart"}, "Top of the list", + {"string": "listEnd"}, "2.", "list two"] + }, { + accOrElmOrID: "dlist", + expected: [{"string": "definitionlist"}, + {"string": "listItemsCount", "count": 0.5}, "Test Definition List", + "dd one"] + }, { + accOrElmOrID: "li_one", + expected: [{"string": "list"}, {"string": "listItemsCount", "count": 2}, + "Test List", {"string": "listStart"}, "Top of the list"] + }, { + accOrElmOrID: "li_two", + expected: [{"string": "list"}, {"string": "listItemsCount", "count": 2}, + "Test List", {"string": "listEnd"}, "2.", "list two"] + }, { + accOrElmOrID: "cell", + expected: [{"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 1}, "Fruits and vegetables", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "List of Fruits", + {"string": "list"}, {"string": "listItemsCount", "count": 4}, + {"string": "listStart"}, {"string": "link"}, "Apples", + {"string": "link"}, "Bananas", + {"string": "link"}, "Peaches", {"string": "listEnd"}, + {"string": "link"}, "Plums"] + }, { + accOrElmOrID: "app.net", + expected: [{"string": "list"}, {"string": "listItemsCount", "count": 2}, + {"string": "listStart"}, {"string": "link"}, "star", + {"string": "listEnd"}, {"string": "link"}, "repost"] + }, { + // Test pivot to list from li_one. + accOrElmOrID: "list", + oldAccOrElmOrID: "li_one", + expected: [{"string": "list"}, {"string": "listItemsCount", "count": 2}, + "Test List", {"string": "listStart"}, "Top of the list", + {"string": "listEnd"}, "2.", "list two"] + }, { + // Test pivot to li_one from list. + accOrElmOrID: "li_one", + oldAccOrElmOrID: "list", + expected: [{"string": "listStart"}, "Top of the list"] + }, { + // Test pivot to "apples" link from the table cell. + accOrElmOrID: "apples", + oldAccOrElmOrID: "cell", + expected: [{"string": "list"}, {"string": "listItemsCount", "count": 4}, + {"string": "listStart"}, {"string": "link"}, "Apples"] + }, { + // Test pivot to the table cell from the "apples" link. + accOrElmOrID: "cell", + oldAccOrElmOrID: "apples", + expected: ["List of Fruits", {"string": "list"}, + {"string": "listItemsCount", "count": 4}, {"string": "listStart"}, + {"string": "link"}, "Apples", {"string": "link"}, "Bananas", + {"string": "link"}, "Peaches", {"string": "listEnd"}, + {"string": "link"}, "Plums"] + }]; + + SpecialPowers.pushPrefEnv({"set": [[PREF_UTTERANCE_ORDER, 0]]}, function() { + // Test various explicit names vs the utterance generated from subtrees. + tests.forEach(function run(test) { + testOutput(test.expected, test.accOrElmOrID, test.oldAccOrElmOrID, 1); + }); + SimpleTest.finish(); + }); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <div id="root"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=845870" + title="[AccessFu] Trust explicitly associated names when speaking certain elements"> + Mozilla Bug 845870 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + <button id="button1" aria-label="Press me">This is not a name</button> + <button id="button2"> + Press me + </button> + <a id="anchor1" href="#test" title="title"></a> + <a id="anchor2" href="#test">This is a link</a> + <textarea id="textarea1" title="Test Text Area" cols="80" rows="5">This is the text area text.</textarea> + <textarea id="textarea2" cols="80" rows="5"> + This is the text area text. + </textarea> + <h1 id="heading1" title="Test heading">This is the heading.</h1> + <h1 id="heading2"> + This is the heading. + </h1> + <ol id="list" title="Test List"> + <li id="li_one" aria-label="Top of the list">list one</li> + <li id="li_two">list two</li> + </ol> + <dl id="dlist" title="Test Definition List"> + <dd id="dd_one">dd one</dd> + </dl> + <table> + <caption>Fruits and vegetables</caption> + <tr> + <td id="cell" aria-label="List of Fruits"> + <ul style="list-style-type: none;"> + <li><a id="apples" href="#">Apples</a></li> + <li><a id="bananas" href="#">Bananas</a></li> + <li><a href="#">Peaches</a></li> + <li> + <a href="#"> + + Plums + </a> + </li> + </ul> + </td> + </tr> + </table> + <!-- app.net --> + <ul id="app.net" class="unstyled ul-horizontal yui3-u fixed-right ta-right" style="list-style-type: none;"> + <li class="yui3-u"> + <a href="#star" data-starred="0" data-star-button="1" data-post-id="5098826" aria-label="star"> + Garbage + </a> + </li> + <li class="yui3-u repost"> + <a href="#repost" title="repost" data-repost-button="1" data-reposted="0" data-post-id="5098826"> + <i aria-label="repost" class="icon-repost"></i> + </a> + </li> + </ul> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_gesture_tracker.html b/accessible/tests/mochitest/jsat/test_gesture_tracker.html new file mode 100644 index 000000000..af2755455 --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_gesture_tracker.html @@ -0,0 +1,51 @@ +<html> + +<head> + <title>AccessFu tests for gesture tracker.</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + 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" src="./jsatcommon.js"></script> + <script type="application/javascript" src="./dom_helper.js"></script> + <script type="application/javascript"> + + function startGestureTracker() { + GestureTracker.reset(); + AccessFuTest.nextTest(); + } + + function stopGestureTracker() { + GestureTracker.reset(); + AccessFuTest.finish(); + } + + function doTest() { + loadJSON("./gestures.json", function onSuccess(gestures) { + AccessFuTest.addFunc(startGestureTracker); + AccessFuTest.sequenceCleanup = GestureTracker.reset.bind( + GestureTracker); + gestures.forEach(AccessFuTest.addSequence); + AccessFuTest.addFunc(stopGestureTracker); + AccessFuTest.waitForExplicitFinish(); + Logger.logLevel = Logger.GESTURE; + AccessFuTest.runTests(); + }); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=981015" + title="AccessFu tests for gesture tracker."> + Mozilla Bug 981015 + </a> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_hints.html b/accessible/tests/mochitest/jsat/test_hints.html new file mode 100644 index 000000000..d5691b97a --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_hints.html @@ -0,0 +1,89 @@ +<html> +<head> + <title> [AccessFu] Interaction Hints </title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="output.js"></script> + <script type="application/javascript"> + + function doTest() { + var tests = [{ + accOrElmOrID: 'can_wheel', + expectedHints: ['Swipe with two fingers to move between pages'] + }, { + accOrElmOrID: 'nested_link', + expectedHints: [{string: 'link-hint'}, + 'Swipe with two fingers to move between pages'] + }, { + accOrElmOrID: 'nested_link', + oldAccOrElmOrID: 'can_wheel', + expectedHints: [{string: 'link-hint'}] + }, { + accOrElmOrID: 'link_with_default_hint', + expectedHints: [{string: 'link-hint'}] + }, { + accOrElmOrID: 'link_with_hint_override', + expectedHints: ['Tap and hold to get to menu'] + }, { + accOrElmOrID: 'button_with_default_hint', + expectedHints: [{string: 'pushbutton-hint'}] + }, { + accOrElmOrID: 'button_with_hint_override', + expectedHints: ['Tap and hold to activate'] + }, { + accOrElmOrID: 'nested_link2', + expectedHints: [{string: 'link-hint'}] + }, { + accOrElmOrID: 'nested_link3', + expectedHints: [{string: 'link-hint'}, {string: 'pushbutton-hint'}, + "Double tap and hold to activate"] + }, { + accOrElmOrID: 'menuitemradio', + expectedHints: [{string: 'radiomenuitem-hint'}] + }]; + + // Test hints. + tests.forEach(function run(test) { + testHints(test.expectedHints, test.accOrElmOrID, test.oldAccOrElmOrID); + }); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <div id="root"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069574" + title="[AccessFu] Interaction Hints"> + Mozilla Bug 1069574 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + <span role="region" id="can_wheel" aria-moz-hint="Swipe with two fingers to move between pages"> + <a href="#" id="nested_link">I can be clicked</a> + </span> + <span role="region" aria-moz-hint=""> + <a><a href="#" id="nested_link2">I can be clicked</a></a> + </span> + <span role="region" aria-moz-hint="Double tap and hold to activate"> + <button><a href="#" id="nested_link3">I can be clicked</a></button> + </span> + <a href="#" id="link_with_default_hint">I can be clicked</a> + <a href="#" id="link_with_hint_override" aria-moz-hint="Tap and hold to get to menu">I am a special link</a> + <button id="button_with_default_hint">Toggle</button> + <button id="button_with_hint_override" aria-moz-hint="Tap and hold to activate">Special</button> + <span id="menuitemradio" role="menuitemradio">Item 1</span> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_landmarks.html b/accessible/tests/mochitest/jsat/test_landmarks.html new file mode 100644 index 000000000..8b1a16f83 --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_landmarks.html @@ -0,0 +1,183 @@ +<html> +<head> + <title> [AccessFu] Speak landmarks</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="output.js"></script> + <script type="application/javascript" + src="jsatcommon.js"></script> + <script type="application/javascript"> + + function doTest() { + // Test the following accOrElmOrID. + var tests = [{ + accOrElmOrID: "nav", + expectedUtterance: [[{"string": "navigation"}, "a nav"], + ["a nav", {"string": "navigation"}]], + expectedBraille: [[{"string": "navigation"}, "a nav"], + ["a nav", {"string": "navigation"}]] + }, { + accOrElmOrID: "main", + expectedUtterance: [[{"string": "main"}, "a main area"], + ["a main area", {"string": "main"}]], + expectedBraille: [[{"string": "main"}, "a main area"], + ["a main area", {"string": "main"}]] + }, { + accOrElmOrID: "header", + expectedUtterance: [ + [{"string": "banner"}, {"string": "header"}, "a header"], + ["a header", {"string": "header"}, {"string": "banner"}]], + expectedBraille: [ + [{"string": "banner"}, {"string": "headerAbbr"}, "a header"], + ["a header", {"string": "headerAbbr"}, {"string": "banner"}]] + }, { + accOrElmOrID: "footer", + expectedUtterance: [ + [{"string": "contentinfo"}, {"string": "footer"}, "a footer"], + ["a footer", {"string": "footer"}, {"string": "contentinfo"}]], + expectedBraille: [ + [{"string": "contentinfo"}, {"string": "footerAbbr"}, "a footer"], + ["a footer", {"string": "footerAbbr"}, {"string": "contentinfo"}]] + }, { + accOrElmOrID: "article_header", + expectedUtterance: [ + [{"string": "header"}, "a header within an article"], + ["a header within an article", {"string": "header"}]], + expectedBraille: [ + [{"string": "headerAbbr"}, "a header within an article"], + ["a header within an article", {"string": "headerAbbr"}]], + }, { + accOrElmOrID: "article_footer", + expectedUtterance: [ + [{"string": "footer"}, "a footer within an article"], + ["a footer within an article", {"string": "footer"}]], + expectedBraille: [ + [{"string": "footerAbbr"}, "a footer within an article"], + ["a footer within an article", {"string": "footerAbbr"}]] + }, { + accOrElmOrID: "section_header", + expectedUtterance: [[{"string":"header"}, "a header within a section"], + ["a header within a section", {"string":"header"}]], + expectedBraille: [ + [{"string":"headerAbbr"}, "a header within a section"], + ["a header within a section", {"string":"headerAbbr"}]] + }, { + accOrElmOrID: "section_footer", + expectedUtterance: [ + [{"string": "footer"}, "a footer within a section"], + ["a footer within a section", {"string": "footer"}]], + expectedBraille: [ + [{"string": "footerAbbr"}, "a footer within a section"], + ["a footer within a section", {"string": "footerAbbr"}]] + }, { + accOrElmOrID: "aside", + expectedUtterance: [ + [{"string": "complementary"}, "by the way I am an aside"], + ["by the way I am an aside", {"string": "complementary"}]], + expectedBraille: [ + [{"string": "complementary"}, "by the way I am an aside"], + ["by the way I am an aside", {"string": "complementary"}]] + }, { + accOrElmOrID: "main_element", + expectedUtterance: [[{"string": "main"}, "another main area"], + ["another main area", {"string": "main"}]], + expectedBraille: [[{"string": "main"}, "another main area"], + ["another main area", {"string": "main"}]] + }, { + accOrElmOrID: "complementary", + expectedUtterance: [[{"string": "list"}, + {"string": "listItemsCount", "count": 1}, {"string": "complementary"}, + {"string": "listStart"}, "A complementary"], ["A complementary", + {"string": "listStart"}, {"string": "complementary"}, + {"string": "list"}, {"string": "listItemsCount", "count": 1}]], + expectedBraille: [["*", {"string": "complementary"}, "A complementary"], + ["*", "A complementary", {"string": "complementary"}]] + }, { + accOrElmOrID: "parent_main", + expectedUtterance: [[{"string": "main"}, "a parent main", + {"string": "complementary"}, "a child complementary"], + ["a parent main", "a child complementary", + {"string": "complementary"}, {"string": "main"}]], + expectedBraille: [[{"string": "main"}, "a parent main", + {"string": "complementary"}, "a child complementary"], + ["a parent main", "a child complementary", + {"string": "complementary"}, {"string": "main"}]] + }, { + accOrElmOrID: "child_complementary", + expectedUtterance: [[{"string": "main"}, {"string": "complementary"}, + "a child complementary"], ["a child complementary", + {"string": "complementary"}, {"string": "main"}]], + expectedBraille: [[{"string": "complementary"}, + "a child complementary"], ["a child complementary", + {"string": "complementary"}]] + }]; + + // Test outputs (utterance and braille) for landmarks. + function testOutputOrder(aOutputOrder) { + return function() { + SpecialPowers.pushPrefEnv({ + "set": [[PREF_UTTERANCE_ORDER, aOutputOrder]] + }, function() { + tests.forEach(function run(test) { + testOutput(test.expectedUtterance[aOutputOrder], test.accOrElmOrID, + test.oldAccOrElmOrID, 1); + testOutput(test.expectedBraille[aOutputOrder], test.accOrElmOrID, + test.oldAccOrElmOrID, 0); + }); + AccessFuTest.nextTest(); + }); + }; + } + + AccessFuTest.addFunc(testOutputOrder(0)); + AccessFuTest.addFunc(testOutputOrder(1)); + AccessFuTest.waitForExplicitFinish(); + AccessFuTest.runTests(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <div id="root"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=888256" + title="[AccessFu] Speak landmarks"> + Mozilla Bug 888256 + </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> + <section id="section_with_header_and_footer"> + <header id="section_header">a header within a section</header> + <footer id="section_footer">a footer within a section</footer> + </section> + <aside id="aside">by the way I am an aside</aside> + <article id="main" role="main">a main area</article> + <main id="main_element">another main area</main> + <ul style="list-style-type: none;"> + <li role="complementary" id="complementary"> + A complementary + </li> + </ul> + <main id="parent_main"> + a parent main + <p id="child_complementary" role="complementary">a child complementary</article> + </main> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_live_regions.html b/accessible/tests/mochitest/jsat/test_live_regions.html new file mode 100644 index 000000000..53828f1b1 --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_live_regions.html @@ -0,0 +1,472 @@ +<html> + +<head> + <title>AccessFu tests for live regions support</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="./jsatcommon.js"></script> + <script type="application/javascript"> + + function startAccessFu() { + SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 1]]}); + AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest); + } + + function stopAccessFu() { + SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 0]]}); + AccessFuTest.once_log("EventManager.stop", () => AccessFuTest.finish()); + } + + function hide(id) { + var element = document.getElementById(id); + element.style.display = "none"; + } + + function show(id) { + var element = document.getElementById(id); + element.style.display = "block"; + } + + function ariaHide(id) { + var element = document.getElementById(id); + element.setAttribute('aria-hidden', true); + } + + function ariaShow(id) { + var element = document.getElementById(id); + element.setAttribute('aria-hidden', false); + } + + function udpate(id, text, property) { + var element = document.getElementById(id); + element[property] = text; + } + + function updateText(id, text) { + udpate(id, text, "textContent"); + } + + function updateHTML(id, text) { + udpate(id, text, "innerHTML"); + } + + var tests = [{ + expected: { + "eventType": "liveregion-change", + "data": [{"string": "hidden"}, "I will be hidden"], + "options": { + "enqueue": true + } + }, + action: function action() { + ["to_hide1", "to_hide2", "to_hide3", "to_hide4"].forEach(id => hide(id)); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": [{"string": "hidden"}, "I will be hidden"], + "options": { + "enqueue": true + } + }, + action: function action() { + ["to_hide_descendant1", "to_hide_descendant2", + "to_hide_descendant3", "to_hide_descendant4"].forEach(id => hide(id)); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["I will be shown"], + "options": { + "enqueue": true + } + }, + action: function action() { + ["to_show1", "to_show2", "to_show3", "to_show4"].forEach(id => show(id)); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["I will be shown"], + "options": { + "enqueue": true + } + }, + action: function action() { + ["to_show_descendant1", "to_show_descendant2", + "to_show_descendant3", "to_show_descendant4"].forEach(id => show(id)); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": [{"string": "hidden"}, "I will be hidden"], + "options": { + "enqueue": true + } + }, + action: function action() { + ["to_hide5", "to_hide6", "to_hide7", "to_hide8", "to_hide9"].forEach(id => ariaHide(id)); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": [{"string": "hidden"}, "I will be hidden"], + "options": { + "enqueue": true + } + }, + action: function action() { + ["to_hide_descendant5", "to_hide_descendant6", + "to_hide_descendant7", "to_hide_descendant8"].forEach(id => ariaHide(id)); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["I will be shown"], + "options": { + "enqueue": true + } + }, + action: function action() { + ["to_show5", "to_show6", "to_show7", "to_show8", "to_show9"].forEach(id => ariaShow(id)); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["I will be shown"], + "options": { + "enqueue": true + } + }, + action: function action() { + ["to_show_descendant5", "to_show_descendant6", + "to_show_descendant7", "to_show_descendant8"].forEach(id => ariaShow(id)); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": [{"string": "hidden"}, "I will be hidden"], + "options": { + "enqueue": false + } + }, + action: function action() { + hide("to_hide_live_assertive"); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": [{"string": "hidden"}, "I will be hidden"], + "options": { + "enqueue": false + } + }, + action: function action() { + ariaHide("to_hide_live_assertive2"); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["I will be shown"], + "options": { + "enqueue": false + } + }, + action: function action() { + ["to_show_live_off", "to_show_live_assertive"].forEach(id => show(id)); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["I will be shown"], + "options": { + "enqueue": false + } + }, + action: function action() { + ["to_show_live_off2", "to_show_live_assertive2"].forEach(id => ariaShow(id)); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["Text Added"], + "options": { + "enqueue": false + } + }, + action: function action() { + updateText("text_add", "Text Added"); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["Text Added"], + "options": { + "enqueue": false + } + }, + action: function action() { + updateHTML("text_add", "Text Added"); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": [{"string": "hidden"}, "Text Removed"], + "options": { + "enqueue": true + } + }, + action: function action() { + updateText("text_remove", ""); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["Descendant Text Added"], + "options": { + "enqueue": false + } + }, + action: function action() { + updateText("text_add_descendant", "Descendant Text Added"); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["Descendant Text Added"], + "options": { + "enqueue": false + } + }, + action: function action() { + updateHTML("text_add_descendant", "Descendant Text Added"); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": [{"string": "hidden"}, "Descendant Text Removed"], + "options": { + "enqueue": true + } + }, + action: function action() { + updateText("text_remove_descendant", ""); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["Descendant Text Added"], + "options": { + "enqueue": false + } + }, + action: function action() { + updateText("text_add_descendant2", "Descendant Text Added"); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["Descendant Text Added"], + "options": { + "enqueue": false + } + }, + action: function action() { + updateHTML("text_add_descendant2", "Descendant Text Added"); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": [{"string": "hidden"}, "Descendant Text Removed"], + "options": { + "enqueue": true + } + }, + action: function action() { + updateText("text_remove_descendant2", ""); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["I am replaced", {"string": "main"}], + "options": { + "enqueue": true + } + }, + action: function action() { + var region = document.getElementById("to_replace_region"); + var child = document.getElementById("to_replace"); + child.setAttribute("role", "main"); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["I am a replaced text"], + "options": { + "enqueue": false + } + }, + action: function action() { + updateText("to_replace_text", "I am a replaced text"); + } + }, { + expected: { + "eventType": "liveregion-change", + "data": ["I am a replaced text"], + "options": { + "enqueue": false + } + }, + action: function action() { + updateHTML("to_replace_text", "I am a replaced text"); + } + }]; + + function doTest() { + AccessFuTest.addFunc(startAccessFu); + tests.forEach(function addTest(test) { + AccessFuTest.addFunc(function () { + AccessFuTest.once(test.expected, AccessFuTest.nextTest); + test.action(); + }); + }); + AccessFuTest.addFunc(stopAccessFu); + AccessFuTest.waitForExplicitFinish(); + AccessFuTest.runTests(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=795957" + title="[AccessFu] Support live regions"> + Mozilla Bug 795957 + </a> + <div id="root"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + + <p id="to_hide1">I should not be announced 1</p> + <p id="to_hide2" aria-live="polite">I should not be announced 2</p> + <p id="to_hide3" aria-live="assertive" aria-relevant="text">I should not be announced 3</p> + <p id="to_hide4" aria-live="polite" aria-relevant="all">I will be hidden</p> + + <p id="to_hide5" aria-hidden="true">I should not be announced 5</p> + <p id="to_hide6">I should not be announced 6</p> + <p id="to_hide7" aria-live="polite">I should not be announced 7</p> + <p id="to_hide8" aria-live="assertive" aria-relevant="text">I should not be announced 8</p> + <p id="to_hide9" aria-live="polite" aria-relevant="all">I will be hidden</p> + + <div> + <p id="to_hide_descendant1">I should not be announced 1</p> + </div> + <div aria-live="polite"> + <p id="to_hide_descendant2">I should not be announced 2</p> + </div> + <div aria-live="assertive" aria-relevant="text"> + <p id="to_hide_descendant3">I should not be announced 3</p> + </div> + <div aria-live="polite" aria-relevant="all"> + <p id="to_hide_descendant4">I will be hidden</p> + </div> + + <div> + <p id="to_hide_descendant5">I should not be announced 4</p> + </div> + <div aria-live="polite"> + <p id="to_hide_descendant6">I should not be announced 5</p> + </div> + <div aria-live="assertive" aria-relevant="text"> + <p id="to_hide_descendant7">I should not be announced 6</p> + </div> + <div aria-live="polite" aria-relevant="all"> + <p id="to_hide_descendant8">I will be hidden</p> + </div> + + <p id="to_show1" style="display: none">I should not be announced 1</p> + <p id="to_show2" aria-live="assertive" aria-relevant="text" style="display: none">I should not be announced 2</p> + <p id="to_show3" aria-live="polite" aria-relevant="removals" style="display: none">I should not be announced 3</p> + <p id="to_show4" aria-live="polite" aria-relevant="all" style="display: none">I will be shown</p> + + <p id="to_show5" aria-hidden="false">I should not be announced 5</p> + <p id="to_show6" aria-hidden="true">I should not be announced 6</p> + <p id="to_show7" aria-hidden="true" aria-live="assertive" aria-relevant="text">I should not be announced 7</p> + <p id="to_show8" aria-hidden="true" aria-live="polite" aria-relevant="removals">I should not be announced 8</p> + <p id="to_show9" aria-hidden="true" aria-live="polite" aria-relevant="all">I will be shown</p> + + <div> + <p id="to_show_descendant1" style="display: none">I should not be announced 1</p> + </div> + <div aria-live="polite" aria-relevant="removals"> + <p id="to_show_descendant2" style="display: none">I should not be announced 2</p> + </div> + <div aria-live="assertive" aria-relevant="text"> + <p id="to_show_descendant3" style="display: none">I should not be announced 3</p> + </div> + <div aria-live="polite" aria-relevant="all"> + <p id="to_show_descendant4" style="display: none">I will be shown</p> + </div> + + <div> + <p id="to_show_descendant5" aria-hidden="true">I should not be announced 5</p> + </div> + <div aria-live="polite" aria-relevant="removals"> + <p id="to_show_descendant6" aria-hidden="true">I should not be announced 6</p> + </div> + <div aria-live="assertive" aria-relevant="text"> + <p id="to_show_descendant7" aria-hidden="true">I should not be announced 7</p> + </div> + <div aria-live="polite" aria-relevant="all"> + <p id="to_show_descendant8" aria-hidden="true">I will be shown</p> + </div> + + <div aria-live="assertive" aria-relevant="all"> + <p id="to_hide_live_assertive">I will be hidden</p> + </div> + + <div aria-live="assertive" aria-relevant="all"> + <p id="to_hide_live_assertive2">I will be hidden</p> + </div> + <p id="to_show_live_assertive" aria-live="assertive" style="display: none">I will be shown</p> + + <p id="to_show_live_off" aria-live="off" style="display: none">I will not be shown</p> + + <p id="to_show_live_assertive2" aria-live="assertive" aria-hidden="true">I will be shown</p> + + <p id="to_show_live_off2" aria-live="off" aria-hidden="true">I will not be shown</p> + + <div id="to_replace_region" aria-live="polite" aria-relevant="all"> + <p id="to_replace">I am replaced</p> + </div> + + <p id="to_replace_text" aria-live="assertive" aria-relevant="text">I am going to be replaced</p> + + <p id="text_add" aria-live="assertive" aria-relevant="text"></p> + <p id="text_remove" aria-live="polite" aria-relevant="all">Text Removed</p> + + <div aria-live="assertive" aria-relevant="all"> + <p id="text_add_descendant"></p> + </div> + <div aria-live="polite" aria-relevant="text"> + <p id="text_remove_descendant">Descendant Text Removed</p> + </div> + <div aria-live="assertive" aria-relevant="text"> + <p id="text_add_descendant2"></p> + </div> + <div aria-live="polite" aria-relevant="all"> + <p id="text_remove_descendant2">Descendant Text Removed</p> + </div> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_output.html b/accessible/tests/mochitest/jsat/test_output.html new file mode 100644 index 000000000..ec2b289be --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_output.html @@ -0,0 +1,673 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=753984 +--> + <head> + <title>[AccessFu] utterance order test</title> + <meta charset="utf-8"> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="./output.js"></script> + <script type="application/javascript" + src="./jsatcommon.js"></script> + <script type="application/javascript"> + + function doTest() { + // Test the following accOrElmOrID (with optional old accOrElmOrID). + // Note: each accOrElmOrID entry maps to a unique object utterance + // generator function within the UtteranceGenerator. + var tests = [{ + accOrElmOrID: "anchor", + expectedUtterance: [[{"string": "link"}, "title"], + ["title", {"string": "link"}]], + expectedBraille: [[{"string": "linkAbbr"}, "title"], + ["title", {"string": "linkAbbr"}]] + }, { + accOrElmOrID: "anchor_titleandtext", + expectedUtterance: [[{"string": "link"}, "goes to the tests -", + "Tests"], ["Tests", "- goes to the tests", {"string": "link"}]], + expectedBraille: [[{"string": "linkAbbr"}, "goes to the tests -", + "Tests"], ["Tests", "- goes to the tests", {"string": "linkAbbr"}]], + }, { + accOrElmOrID: "anchor_duplicatedtitleandtext", + expectedUtterance: [[{"string": "link"}, "Tests"], + ["Tests", {"string": "link"}]], + expectedBraille: [[{"string": "linkAbbr"}, "Tests"], + ["Tests", {"string": "linkAbbr"}]] + }, { + accOrElmOrID: "anchor_arialabelandtext", + expectedUtterance: [[{"string": "link"}, "goes to the tests - Tests"], + ["Tests - goes to the tests", {"string": "link"}]], + expectedBraille: [[{"string": "linkAbbr"}, + "goes to the tests - Tests"], ["Tests - goes to the tests", + {"string": "linkAbbr"}]], + }, { + accOrElmOrID: "textarea", + expectedUtterance: [[{"string": "textarea"}, + "This is the text area text."], ["This is the text area text.", + {"string": "textarea"}]], + expectedBraille: [[{"string": "textareaAbbr"}, + "This is the text area text."], ["This is the text area text.", + {"string": "textareaAbbr"}]], + }, { + accOrElmOrID: "heading", + expectedUtterance: [[{"string": "headingLevel", "args": [1]}, + "Test heading"], ["Test heading", + {"string": "headingLevel", "args": [1]}]], + expectedBraille: [[{"string": "headingAbbr"}, "Test heading"], + ["Test heading", {"string": "headingAbbr"}]] + }, { + accOrElmOrID: "list", + expectedUtterance: [[{"string": "list"}, + {"string": "listItemsCount", "count":1}, {"string": "listStart"}, + "1.", "list one"], ["1.", "list one", {"string": "listStart"}, + {"string": "list"}, {"string": "listItemsCount", "count":1}] + ], + expectedBraille: [[{"string": "listAbbr"}, "list one"], + ["list one", {"string": "listAbbr"}]] + }, { + accOrElmOrID: "dlist", + expectedUtterance: [[{"string": "definitionlist"}, + {"string": "listItemsCount", "count": 0.5}, "dd one"], ["dd one", + {"string": "definitionlist"}, + {"string": "listItemsCount", "count": 0.5}] + ], + expectedBraille: [[{"string": "definitionlistAbbr"}, "dd one"], + ["dd one", {"string": "definitionlistAbbr"}]] + }, { + accOrElmOrID: "li_one", + expectedUtterance: [[{"string": "list"}, + {"string": "listItemsCount", "count": 1}, {"string": "listStart"}, + "1.", "list one"], ["1.", "list one", {"string": "listStart"}, + {"string": "list"}, {"string": "listItemsCount", "count": 1}] + ], + expectedBraille: [["1.", "list one"], ["1.", "list one"]] + }, + { + accOrElmOrID: "li_two", + expectedUtterance: [[{"string": "list"}, + {"string": "listItemsCount", "count": 1}, {"string": "listStart"}, + "list two"], ["list two", {"string": "listStart"}, + {"string": "list"}, {"string": "listItemsCount", "count": 1}] + ], + expectedBraille: [["*", "list two"], ["*", "list two"]] + }, { + accOrElmOrID: "cell", + expectedUtterance: [[{"string":"table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 1}, "Fruits and vegetables", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, {"string": "list"}, + {"string": "listItemsCount", "count": 4}, {"string": "listStart"}, + {"string": "link"}, "Apples", {"string": "link"}, "Bananas", + {"string": "link"}, "Peaches", {"string": "listEnd"}, + {"string": "link"}, "Plums"], ["Apples", {"string": "link"}, + {"string": "listStart"}, "Bananas", {"string": "link"}, "Peaches", + {"string": "link"}, "Plums", {"string": "link"}, + {"string": "listEnd"}, {"string": "list"}, + {"string": "listItemsCount", "count": 4}, + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "Fruits and vegetables", + {"string":"table"}, {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 1}]], + expectedBraille: [[{"string": "cellInfoAbbr", "args": [ 1, 1]}, + {"string": "listAbbr"}, {"string": "linkAbbr"}, "Apples", + {"string": "linkAbbr"}, "Bananas", {"string": "linkAbbr"}, + "Peaches", {"string": "linkAbbr"}, "Plums"], ["Apples", + {"string": "linkAbbr"}, "Bananas", {"string": "linkAbbr"}, + "Peaches", {"string": "linkAbbr"}, "Plums", {"string": "linkAbbr"}, + {"string": "listAbbr"}, + {"string": "cellInfoAbbr", "args": [ 1, 1]}]] + }, { + accOrElmOrID: "date", + expectedUtterance: [[{"string": "textInputType_date"}, + {"string": "entry"}, "2011-09-29"], ["2011-09-29", + {"string": "textInputType_date"}, {"string": "entry"}]], + expectedBraille: [[{"string": "textInputType_date"}, + {"string": "entryAbbr"}, "2011-09-29"], ["2011-09-29", + {"string": "textInputType_date"}, {"string": "entryAbbr"}]] + }, { + accOrElmOrID: "email", + expectedUtterance: [[{"string": "textInputType_email"}, + {"string": "entry"}, "test@example.com"], ["test@example.com", + {"string": "textInputType_email"}, {"string": "entry"}]], + expectedBraille: [[{"string": "textInputType_email"}, + {"string": "entryAbbr"}, "test@example.com"], ["test@example.com", + {"string": "textInputType_email"}, {"string": "entryAbbr"}]] + }, { + accOrElmOrID: "search", + expectedUtterance: [[{"string": "textInputType_search"}, + {"string": "entry"}, "This is a search"], ["This is a search", + {"string": "textInputType_search"}, {"string": "entry"}]], + expectedBraille: [[{"string": "textInputType_search"}, + {"string": "entryAbbr"}, "This is a search"], ["This is a search", + {"string": "textInputType_search"}, {"string": "entryAbbr"}]] + }, { + accOrElmOrID: "tel", + expectedUtterance: [[{"string": "textInputType_tel"}, + {"string": "entry"}, "555-5555"], ["555-5555", + {"string": "textInputType_tel"}, {"string": "entry"}]], + expectedBraille: [[{"string": "textInputType_tel"}, + {"string": "entryAbbr"}, "555-5555"], ["555-5555", + {"string": "textInputType_tel"}, {"string": "entryAbbr"}]] + }, { + accOrElmOrID: "url", + expectedUtterance: [[{"string": "textInputType_url"}, + {"string": "entry"}, "http://example.com"], ["http://example.com", + {"string": "textInputType_url"}, {"string": "entry"}]], + expectedBraille: [[{"string": "textInputType_url"}, + {"string": "entryAbbr"}, "http://example.com"], + ["http://example.com", {"string": "textInputType_url"}, + {"string": "entryAbbr"}]] + }, { + accOrElmOrID: "textInput", + expectedUtterance: [[{"string": "entry"}, "This is text."], + ["This is text.", {"string": "entry"}]], + expectedBraille: [[{"string": "entryAbbr"}, "This is text."], + ["This is text.", {"string": "entryAbbr"}]] + }, { + // Test pivot to list from li_one. + accOrElmOrID: "list", + oldAccOrElmOrID: "li_one", + expectedUtterance: [[{"string": "list"}, + {"string": "listItemsCount", "count": 1}, {"string": "listStart"}, + "1.", "list one"], ["1.", "list one", {"string": "listStart"}, + {"string": "list"}, {"string": "listItemsCount", "count": 1}] + ], + expectedBraille: [[{"string": "listAbbr"}, "list one"], + ["list one", {"string": "listAbbr"}]] + }, { + // Test pivot to "apples" link from the table cell. + accOrElmOrID: "apples", + oldAccOrElmOrID: "cell", + expectedUtterance: [[{"string": "list"}, + {"string": "listItemsCount", "count": 4}, {"string": "listStart"}, + {"string": "link"}, "Apples"], ["Apples", {"string": "link"}, + {"string": "listStart"}, {"string": "list"}, + {"string": "listItemsCount", "count": 4}] + ], + expectedBraille: [["*", {"string": "linkAbbr"}, "Apples"], + ["*", "Apples", {"string": "linkAbbr"}]] + }, { + // Test pivot to "bananas" link from "apples" link. + accOrElmOrID: "bananas", + oldAccOrElmOrID: "apples", + expectedUtterance: [[{"string": "link"}, "Bananas"], + ["Bananas", {"string": "link"}]], + expectedBraille: [["*", {"string": "linkAbbr"}, "Bananas"], + ["*", "Bananas", {"string": "linkAbbr"}]] + }, { + // test unavailable state utterance + accOrElmOrID: "unavailableButton", + expectedUtterance: [[{"string": "stateUnavailable"}, + {"string": "pushbutton"}, "I am unavailable"], ["I am unavailable", + {"string": "stateUnavailable"}, {"string": "pushbutton"}]], + expectedBraille: [[{"string": "pushbuttonAbbr"}, "I am unavailable"], + ["I am unavailable", {"string": "pushbuttonAbbr"}]] + }, { + // test expanded state utterance + accOrElmOrID: "expandedButton", + expectedUtterance: [[{"string": "stateExpanded"}, + {"string": "pushbutton"}, "I am expanded"], ["I am expanded", + {"string": "stateExpanded"}, {"string": "pushbutton"}]], + expectedBraille: [[{"string": "pushbuttonAbbr"}, "I am expanded"], + ["I am expanded", {"string": "pushbuttonAbbr"}]] + }, { + // test collapsed state utterance + accOrElmOrID: "collapsedButton", + expectedUtterance: [[{"string": "stateCollapsed"}, + {"string": "pushbutton"}, "I am collapsed"], ["I am collapsed", + {"string": "stateCollapsed"}, {"string": "pushbutton"}]], + expectedBraille: [[{"string": "pushbuttonAbbr"}, "I am collapsed"], + ["I am collapsed", {"string": "pushbuttonAbbr"}]] + }, { + // test required state utterance + accOrElmOrID: "requiredInput", + expectedUtterance: [[{"string": "stateRequired"}, {"string": "entry"}, + "I am required"], ["I am required", {"string": "stateRequired"}, + {"string": "entry"}]], + expectedBraille: [[{"string": "entryAbbr"}, "I am required"], + ["I am required", {"string": "entryAbbr"}]] + }, { + // test unavailable state utterance on inputs + accOrElmOrID: "readonlyInput", + expectedUtterance: [[{"string": "stateReadonly"}, {"string": "entry"}, + "No edits"], ["No edits", {"string": "stateReadonly"}, + {"string": "entry"}]], + expectedBraille: [[{"string": "entryAbbr"}, "No edits"], + ["No edits", {"string": "entryAbbr"}]] + }, { + // test unavailable state utterance on textareas + accOrElmOrID: "readonlyTextarea", + expectedUtterance: [[{"string": "stateReadonly"}, {"string": "textarea"}, + "No editing"], ["No editing", {"string": "stateReadonly"}, + {"string": "textarea"}]], + expectedBraille: [[{"string": "textareaAbbr"}, "No editing"], + ["No editing", {"string": "textareaAbbr"}]] + }, { + // test has popup state utterance + accOrElmOrID: "hasPopupButton", + expectedUtterance: [[{"string": "stateHasPopup"}, + {"string": "buttonmenu"}, "I have a popup"], ["I have a popup", + {"string": "stateHasPopup"}, {"string": "buttonmenu"}]], + expectedBraille: [[{"string": "buttonmenuAbbr"}, "I have a popup"], + ["I have a popup", {"string": "buttonmenuAbbr"}]] + }, { + // Test selected tab + accOrElmOrID: "tab1", + expectedUtterance: [[{"string": "pagetablist"}, + {"string": "stateSelected"}, {"string": "pagetab"}, + {"string": "objItemOfN", "args": [1, 2]}, "Account"], ["Account", + {"string": "stateSelected"}, {"string": "pagetab"}, + {"string": "objItemOfN", "args": [1, 2]}, {"string": "pagetablist"}] + ], + expectedBraille: [[{"string": "pagetabAbbr"}, + {"string": "objItemOfN", "args": [1, 2]}, "Account"], ["Account", + {"string": "pagetabAbbr"}, + {"string": "objItemOfN", "args": [1, 2]}]] + }, { + // Test unselected tab + accOrElmOrID: "tab2", + expectedUtterance: [[{"string": "pagetablist"}, {"string": "pagetab"}, + {"string": "objItemOfN", "args": [2, 2]}, "Advanced"], ["Advanced", + {"string": "pagetab"}, {"string": "objItemOfN", "args": [2, 2]}, + {"string": "pagetablist"}]], + expectedBraille: [[{"string": "pagetabAbbr"}, + {"string": "objItemOfN", "args": [2, 2]}, "Advanced"], ["Advanced", + {"string": "pagetabAbbr"}, + {"string": "objItemOfN", "args": [2, 2]}]] + }, { + // Landing on this label should mimic landing on the checkbox. + accOrElmOrID: "label1", + expectedUtterance: [[{"string": "stateNotChecked"}, + {"string": "checkbutton"}, "Orange"], ["Orange", + {"string": "stateNotChecked"}, {"string": "checkbutton"}]], + expectedBraille: [[{"string": "stateUncheckedAbbr"}, "Orange"], + ["Orange", {"string": "stateUncheckedAbbr"}]] + }, { + // Here we get a top-level view of the form. + accOrElmOrID: "form1", + expectedUtterance: [[{"string": "label"}, + {"string": "stateNotChecked"}, {"string": "checkbutton"}, "Orange", + "Orange", {"string": "stateNotChecked"}, {"string": "checkbutton"}, + "Blue", {"string": "label"}, "Blue"], ["Orange", + {"string": "stateNotChecked"}, {"string": "checkbutton"}, "Orange", + {"string": "label"}, "Blue", {"string": "stateNotChecked"}, + {"string": "checkbutton"}, "Blue", {"string": "label"}]], + expectedBraille: [[{"string": "labelAbbr"}, + {"string": "stateUncheckedAbbr"}, "Orange", "Orange", + {"string": "stateUncheckedAbbr"}, "Blue", {"string": "labelAbbr"}, + "Blue"], ["Orange", {"string": "stateUncheckedAbbr"}, "Orange", + {"string": "labelAbbr"}, "Blue", {"string": "stateUncheckedAbbr"}, + "Blue", {"string": "labelAbbr"}]] + }, { + // This is a non-nesting label. + accOrElmOrID: "label2", + expectedUtterance: [[{"string": "label"}, "Blue"], + ["Blue", {"string": "label"}]], + expectedBraille: [[{"string": "labelAbbr"}, "Blue"], + ["Blue", {"string": "labelAbbr"}]] + }, { + // This is a distinct control. + accOrElmOrID: "input2", + expectedUtterance: [[{"string": "stateNotChecked"}, + {"string": "checkbutton"}, "Blue"], ["Blue", + {"string": "stateNotChecked"}, {"string": "checkbutton"}]], + expectedBraille: [[{"string": "stateUncheckedAbbr"}, "Blue"], ["Blue", + {"string": "stateUncheckedAbbr"}]] + }, { + // This is a nested control. + accOrElmOrID: "input1", + expectedUtterance: [[{"string": "stateNotChecked"}, + {"string": "checkbutton"}, "Orange"], ["Orange", + {"string": "stateNotChecked"}, {"string": "checkbutton"}]], + expectedBraille: [[{"string": "stateUncheckedAbbr"}, "Orange"], + ["Orange", {"string": "stateUncheckedAbbr"}]] + }, { + // Landing on this label should mimic landing on the entry. + accOrElmOrID: "label3", + expectedUtterance: [[{"string": "entry"}, "Joe", "First name:"], + ["First name:", "Joe", {"string": "entry"}]], + expectedBraille: [[{"string": "entryAbbr"}, "Joe", "First name:"], + ["First name:", "Joe", {"string": "entryAbbr"}]] + }, { + // This is a nested control with a value. + accOrElmOrID: "input3", + expectedUtterance: [[{"string": "entry"}, "Joe", "First name:"], + ["First name:", "Joe", {"string": "entry"}]], + expectedBraille: [[{"string": "entryAbbr"}, "Joe", "First name:"], + ["First name:", "Joe", {"string": "entryAbbr"}]] + }, { + // This is a nested control with a value. + accOrElmOrID: "input4", + expectedUtterance: [[{"string": "slider"}, "3", "Points:"], + ["Points:", "3", {"string": "slider"}]], + expectedBraille: [[{"string": "sliderAbbr"}, "3", "Points:"], + ["Points:", "3", {"string": "sliderAbbr"}]] + }, { + accOrElmOrID: "password", + expectedUtterance: [[{"string": "passwordtext"}, "Secret Password"], + ["Secret Password", {"string": "passwordtext"}]], + expectedBraille: [[{"string": "passwordtextAbbr"}, "Secret Password"], + ["Secret Password", {"string": "passwordtextAbbr"}]] + }, { + accOrElmOrID: "input5", + expectedUtterance: [[{"string": "stateChecked"}, + {"string": "checkbutton"}, "Boring label"], ["Boring label", + {"string": "stateChecked"}, {"string": "checkbutton"}]], + expectedBraille: [[{"string": "stateCheckedAbbr"}, "Boring label"], + ["Boring label", {"string": "stateCheckedAbbr"}]] + }, { + accOrElmOrID: "radio_unselected", + expectedUtterance: [[{"string": "stateNotChecked"}, + {"string": "radiobutton"}, "any old radio button"], + ["any old radio button", {"string": "stateNotChecked"}, + {"string": "radiobutton"}] + ], + expectedBraille: [ + [{"string": "stateUncheckedAbbr"}, "any old radio button"], + ["any old radio button", {"string": "stateUncheckedAbbr"}]] + }, { + accOrElmOrID: "radio_selected", + expectedUtterance: [[{"string": "stateChecked"}, + {"string": "radiobutton"}, "a unique radio button"], + ["a unique radio button", {"string": "stateChecked"}, + {"string": "radiobutton"}]], + expectedBraille: [ + [{"string": "stateCheckedAbbr"}, "a unique radio button"], + ["a unique radio button", {"string": "stateCheckedAbbr"}]] + }, { + accOrElmOrID: "togglebutton_notpressed", + expectedUtterance: [[{"string": "togglebutton"}, "I am not pressed"], + ["I am not pressed", {"string": "togglebutton"}]], + expectedBraille: [ + [{"string": "stateUnpressedAbbr"}, "I am not pressed"], + ["I am not pressed", {"string": "stateUnpressedAbbr"}]] + }, { + accOrElmOrID: "togglebutton_pressed", + expectedUtterance: [[{"string": "statePressed"}, + {"string": "togglebutton"}, "I am pressed!"], ["I am pressed!", + {"string": "statePressed"}, {"string": "togglebutton"}]], + expectedBraille: [[{"string": "statePressedAbbr"}, "I am pressed!"], + ["I am pressed!", {"string": "statePressedAbbr"}]] + }, { + accOrElmOrID: "listbox-option", + expectedUtterance: [[{"string": "listbox"}, + {"string": "listboxoption"}, "Search suggestion"], + ["Search suggestion", {"string": "listboxoption"}, + {"string": "listbox"}] + ], + expectedBraille: [ + [{"string": "listboxoptionAbbr"}, "Search suggestion"], + ["Search suggestion", {"string": "listboxoptionAbbr"}]] + }, { + accOrElmOrID: "listbox-option2", + oldAccOrElmOrID: "listbox-option", + expectedUtterance: [[{"string": "listboxoption"}, "555-12345"], + ["555-12345", {"string": "listboxoption"}]], + expectedBraille: [[{"string": "listboxoptionAbbr"}, "555-12345"], + ["555-12345", {"string": "listboxoptionAbbr"}]] + }, { + accOrElmOrID: "columnheader", + oldAccOrElmOrID: "grid", + expectedUtterance: [[{"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args" :[1]}, "Sunday"], ["Sunday", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args" :[1]}]], + expectedBraille: [[{"string": "cellInfoAbbr", "args": [1, 1]}, + "Sunday"], ["Sunday", {"string": "cellInfoAbbr", "args": [1, 1]}]] + }, { + accOrElmOrID: "rowheader", + oldAccOrElmOrID: "grid", + expectedUtterance: [[{"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "Sunday", "Week 1"], ["Week 1", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "Sunday"]], + expectedBraille: [[{"string": "cellInfoAbbr", "args": [1, 2]}, + "Sunday", "Week 1"], ["Week 1", + {"string": "cellInfoAbbr", "args": [1, 2]}, "Sunday"]] + }, { + accOrElmOrID: "gridcell1", + oldAccOrElmOrID: "grid", + expectedUtterance: [["3"], ["3"]], + expectedBraille: [["3"], ["3"]] + }, { + accOrElmOrID: "gridcell2", + oldAccOrElmOrID: "grid", + expectedUtterance: [["4", "7"], ["4", "7"]], + expectedBraille: [["4", "7"], ["4", "7"]] + }, { + accOrElmOrID: "gridcell3", + oldAccOrElmOrID: "grid", + expectedUtterance: [[{"string": "stateSelected"}, "5"], + ["5", {"string": "stateSelected"}]], + expectedBraille: [["5"], ["5"]], + }, { + accOrElmOrID: "frequency", + expectedUtterance: [[{"string": "stateCollapsed"}, + {"string": "stateHasPopup"}, {"string": "combobox"}, "15 min"], [ + "15 min", {"string": "stateCollapsed"}, {"string": "stateHasPopup"}, + {"string": "combobox"}]], + expectedBraille: [[{"string": "comboboxAbbr"}, "15 min"], ["15 min", + {"string": "comboboxAbbr"}]] + }, { + accOrElmOrID: "selected-combobox-option", + oldAccOrElmOrID: "frequency", + expectedUtterance: [[{"string": "stateSelected"}, + {"string": "comboboxoption"}, "15 min"], ["15 min", + {"string": "stateSelected"}, {"string": "comboboxoption"}]], + expectedBraille: [[{"string": "comboboxoptionAbbr"}, "15 min"], [ + "15 min", {"string": "comboboxoptionAbbr"}]] + }, { + accOrElmOrID: "combobox-option", + oldAccOrElmOrID: "frequency", + expectedUtterance: [[{"string": "comboboxoption"}, "30 min"], [ + "30 min", {"string": "comboboxoption"}]], + expectedBraille: [[{"string": "comboboxoptionAbbr"}, "30 min"], [ + "30 min", {"string": "comboboxoptionAbbr"}]] + }, { + accOrElmOrID: "labelled-combobox", + expectedUtterance: [[{"string": "stateCollapsed"}, + {"string": "stateHasPopup"}, {"string": "combobox"}, "Never", + "Intervals"], ["Intervals", "Never", {"string": "stateCollapsed"}, + {"string": "stateHasPopup"}, {"string": "combobox"}]], + expectedBraille: [[{"string": "comboboxAbbr"}, "Never", "Intervals"], + ["Intervals", "Never", {"string": "comboboxAbbr"}]] + }, { + accOrElmOrID: "statusbar-1", + expectedUtterance: [["Last sync:", "2 days ago"], + ["Last sync:", "2 days ago"]], + expectedBraille: [["Last sync:", "2 days ago"], + ["Last sync:", "2 days ago"]] + }, { + accOrElmOrID: "statusbar-2", + expectedUtterance: [["Last sync: 30min ago"], + ["Last sync: 30min ago"]], + expectedBraille: [["Last sync: 30min ago"], ["Last sync: 30min ago"]] + }, { + accOrElmOrID: "switch-1", + expectedUtterance: [[{"string": "stateOn"}, {"string": "switch"}, + "Simple switch"], ["Simple switch", {"string": "stateOn"}, + {"string": "switch"}]], + expectedBraille: [[{"string": "stateCheckedAbbr"}, "Simple switch"], + ["Simple switch", {"string": "stateCheckedAbbr"}]] + }, { + accOrElmOrID: "switch-2", + expectedUtterance: [[{"string": "stateOff"}, + {"string": "switch"}, "Another switch"], ["Another switch", + {"string": "stateOff"}, {"string": "switch"}]], + expectedBraille: [ + [{"string": "stateUncheckedAbbr"}, "Another switch"], + ["Another switch", {"string": "stateUncheckedAbbr"}]] + }]; + + // Test all possible utterance order preference values. + function testOutputOrder(aOutputOrder) { + return function() { + SpecialPowers.pushPrefEnv({ + "set": [[PREF_UTTERANCE_ORDER, aOutputOrder]] + }, function() { + tests.forEach(function run(test) { + testOutput(test.expectedUtterance[aOutputOrder], test.accOrElmOrID, + test.oldAccOrElmOrID, 1); + testOutput(test.expectedBraille[aOutputOrder], test.accOrElmOrID, + test.oldAccOrElmOrID, 0); + }); + AccessFuTest.nextTest(); + }); + }; + } + + AccessFuTest.addFunc(testOutputOrder(0)); + AccessFuTest.addFunc(testOutputOrder(1)); + AccessFuTest.waitForExplicitFinish(); + AccessFuTest.runTests(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + + </script> + </head> + <body> + <div id="root"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=753984" + title="[AccessFu] utterance order test"> + Mozilla Bug 753984</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=758675" + title="[AccessFu] Add support for accDescription"> + Mozilla Bug 758675</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=876475" + title="[AccessFu] Make braille output less verbose"> + Mozilla Bug 876475</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=924284" + title="[AccessFu] Output accessible values"> + Mozilla Bug 924284</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=925845" + title="[AccessFu] Unify output tests"> + Mozilla Bug 925845</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + <a id="anchor" href="#test" title="title"></a> + <a id="anchor_titleandtext" href="#test" title="goes to the tests">Tests</a> + <a id="anchor_duplicatedtitleandtext" href="#test" title="Tests">Tests</a> + <a id="anchor_arialabelandtext" href="#test" aria-label="Tests" title="goes to the tests">Tests</a> + <textarea id="textarea" cols="80" rows="5"> + This is the text area text. + </textarea> + <h1 id="heading" title="Test heading"></h1> + <ol id="list"> + <li id="li_one">list one</li> + </ol> + <ul id="unorderd_list"> + <li id="li_two">list two</li> + </ul> + <dl id="dlist"> + <dd id="dd_one"> + dd one + </dd> + </dl> + <table> + <caption>Fruits and vegetables</caption> + <tr> + <td id="cell"> + <ul style="list-style-type: none;"> + <li><a id="apples" href="#">Apples</a></li> + <li><a id="bananas" href="#">Bananas</a></li> + <li><a href="#">Peaches</a></li> + <li> + <a href="#"> + Plums + </a> + </li> + </ul> + </td> + </tr> + </table> + <button id="unavailableButton" disabled>I am unavailable</button> + <button id="expandedButton" aria-expanded="true">I am expanded</button> + <button id="collapsedButton" aria-expanded="false">I am collapsed</button> + <input id="requiredInput" required placeholder="I am required" /> + <input id="readonlyInput" readonly value="No edits" /> + <textarea id="readonlyTextarea" readonly>No editing</textarea> + <button id="hasPopupButton" aria-haspopup="true">I have a popup</button> + <div role="tablist"> + <a id="tab1" href="#" role="tab" aria-selected="true">Account</a> + <a id="tab2" href="#" role="tab" aria-selected="false">Advanced</a> + </div> + <form id="form1"> + <label id="label1"><input id="input1" type="checkbox">Orange</label> + <input id="input2" type="checkbox"><label id="label2" for="input2">Blue</label> + </form> + <label id="label3">First name: <input id="input3" value="Joe"></label> + <label id="label4">Points: + <input id="input4" type="range" name="points" min="1" max="10" value="3"> + </label> + <label for="input5">Boring label</label><input id="input5" type="checkbox" checked></input> + <label for="password">Secret Password</label><input id="password" type="password"></input> + <label for="radio_unselected">any old radio button</label><input id="radio_unselected" type="radio"></input> + <label for="radio_selected">a unique radio button</label><input id="radio_selected" type="radio" checked></input> + <input id="date" type="date" value="2011-09-29" /> + <input id="email" type="email" value="test@example.com" /> + <input id="search" type="search" value="This is a search" /> + <input id="tel" type="tel" value="555-5555" /> + <input id="url" type="url" value="http://example.com" /> + <input id="textInput" type="text" value="This is text." /> + <label>Points: <input id="range" type="range" name="points" min="1" max="10" value="3"></label> + <div id="togglebutton_notpressed" aria-pressed="false" role="button" tabindex="-1">I am not pressed</div> + <div id="togglebutton_pressed" aria-pressed="true" role="button" tabindex="-1">I am pressed!</div> + <ul role="listbox" style="list-style-type: none;"> + <li role="option" id="listbox-option">Search suggestion</li> + <li role="option" id="listbox-option2"> + <label aria-hidden="true"> + <input type="checkbox" /> + </label> + 555-12345 + </li> + </ul> + <section id="grid" role="grid"> + <ol role="row"> + <li role="presentation"></li> + <li id="columnheader" role="columnheader" aria-label="Sunday">S</li> + <li role="columnheader">M</li> + </ol> + <ol role="row"> + <li id="rowheader" role="rowheader" aria-label="Week 1">1</li> + <li id="gridcell1" role="gridcell"><span>3</span><div></div></li> + <li id="gridcell2" role="gridcell"><span>4</span><div>7</div></li> + </ol> + <ol role="row"> + <li role="rowheader">2</li> + <li id="gridcell3" aria-selected="true" role="gridcell">5</li> + <li role="gridcell">6</li> + </ol> + </section> + <select id="frequency"> + <option id="selected-combobox-option" value="15">15 min</option> + <option id="combobox-option" value="30">30 min</option> + <option value="null">Manual</option> + </select> + <select id="labelled-combobox" aria-label="Intervals"> + <option value="15">Every 15 min</option> + <option value="30">Every 30 min</option> + <option value="null" selected>Never</option> + </select> + <div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div> + <div aria-label="Last sync: 30min ago" id="statusbar-2" role="status">I should be ignored</div> + <span id="switch-1" role="switch" aria-label="Simple switch" aria-checked="true"></span> + <span id="switch-2" role="switch" aria-label="Another switch" aria-checked="false"></span> + </div> + </body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_output_mathml.html b/accessible/tests/mochitest/jsat/test_output_mathml.html new file mode 100644 index 000000000..3fe4779b2 --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_output_mathml.html @@ -0,0 +1,313 @@ +<html> +<head> + <title>[AccessFu] MathML Accessibility Support</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="output.js"></script> + <script type="application/javascript" + src="jsatcommon.js"></script> + <script type="application/javascript"> + + function doTest() { + // Test the following accOrElmOrID. + var tests = [{ + accOrElmOrID: "math-1", + expectedUtterance: [ + [{"string":"open-fence"},"(","x",",","y",{"string":"close-fence"},")"], + ["(",{"string":"open-fence"},"x",",","y",")",{"string":"close-fence"}] + ], + expectedBraille: [ + [{"string":"open-fenceAbbr"},"(","x",",","y",{"string":"close-fenceAbbr"},")"], + ["(",{"string":"open-fenceAbbr"},"x",",","y",")",{"string":"close-fenceAbbr"}] + ] + }, { + accOrElmOrID: "mfrac-1", + expectedUtterance: [ + [{"string":"mathmlfraction"},{"string":"numerator"},"a",{"string":"denominator"},"b"], + ["a",{"string":"numerator"},"b",{"string":"denominator"},{"string":"mathmlfraction"}] + ], + expectedBraille: [ + [{"string":"mathmlfractionAbbr"},{"string":"numeratorAbbr"},"a",{"string":"denominatorAbbr"},"b"], + ["a",{"string":"numeratorAbbr"},"b",{"string":"denominatorAbbr"},{"string":"mathmlfractionAbbr"}] + ] + }, { + accOrElmOrID: "mfrac-2", + expectedUtterance: [ + [{"string":"mathmlfractionwithoutbar"},{"string":"numerator"},"a",{"string":"denominator"},"b"], + ["a",{"string":"numerator"},"b",{"string":"denominator"},{"string":"mathmlfractionwithoutbar"}] + ], + expectedBraille: [ + [{"string":"mathmlfractionwithoutbarAbbr"},{"string":"numeratorAbbr"},"a",{"string":"denominatorAbbr"},"b"], + ["a",{"string":"numeratorAbbr"},"b",{"string":"denominatorAbbr"},{"string":"mathmlfractionwithoutbarAbbr"}] + ] + }, { + accOrElmOrID: "msub-1", + expectedUtterance: [ + [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"subscript"},"b"], + ["a",{"string":"base"},"b",{"string":"subscript"},{"string":"mathmlscripted"}] + ], + expectedBraille: [ + [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"subscriptAbbr"},"b"], + ["a",{"string":"baseAbbr"},"b",{"string":"subscriptAbbr"},{"string":"mathmlscriptedAbbr"}] + ] + }, { + accOrElmOrID: "msup-1", + expectedUtterance: [ + [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"superscript"},"b"], + ["a",{"string":"base"},"b",{"string":"superscript"},{"string":"mathmlscripted"}] + ], + expectedBraille: [ + [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"superscriptAbbr"},"b"], + ["a",{"string":"baseAbbr"},"b",{"string":"superscriptAbbr"},{"string":"mathmlscriptedAbbr"}] + ] + }, { + accOrElmOrID: "msubsup-1", + expectedUtterance: [ + [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"subscript"},"b",{"string":"superscript"},"c"], + ["a",{"string":"base"},"b",{"string":"subscript"},"c",{"string":"superscript"},{"string":"mathmlscripted"}] + ], + expectedBraille: [ + [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"subscriptAbbr"},"b",{"string":"superscriptAbbr"},"c"], + ["a",{"string":"baseAbbr"},"b",{"string":"subscriptAbbr"},"c",{"string":"superscriptAbbr"},{"string":"mathmlscriptedAbbr"}] + ] + }, { + accOrElmOrID: "mmultiscripts-1", + expectedUtterance: [ + [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"subscript"},"b",{"string":"superscript"},"c",{"string":"superscript"},"d",{"string":"presubscript"},"e",{"string":"presubscript"},"f",{"string":"presuperscript"},"g"], + ["a",{"string":"base"},"b",{"string":"subscript"},"c",{"string":"superscript"},"d",{"string":"superscript"},"e",{"string":"presubscript"},"f",{"string":"presubscript"},"g",{"string":"presuperscript"},{"string":"mathmlscripted"}] + ], + expectedBraille: [ + [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"subscriptAbbr"},"b",{"string":"superscriptAbbr"},"c",{"string":"superscriptAbbr"},"d",{"string":"presubscriptAbbr"},"e",{"string":"presubscriptAbbr"},"f",{"string":"presuperscriptAbbr"},"g"], + ["a",{"string":"baseAbbr"},"b",{"string":"subscriptAbbr"},"c",{"string":"superscriptAbbr"},"d",{"string":"superscriptAbbr"},"e",{"string":"presubscriptAbbr"},"f",{"string":"presubscriptAbbr"},"g",{"string":"presuperscriptAbbr"},{"string":"mathmlscriptedAbbr"}] + ] + }, { + accOrElmOrID: "munder-1", + expectedUtterance: [ + [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"underscript"},"b"], + ["a",{"string":"base"},"b",{"string":"underscript"},{"string":"mathmlscripted"}] + ], + expectedBraille: [ + [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"underscriptAbbr"},"b"], + ["a",{"string":"baseAbbr"},"b",{"string":"underscriptAbbr"},{"string":"mathmlscriptedAbbr"}] + ] + }, { + accOrElmOrID: "mover-1", + expectedUtterance: [ + [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"overscript"},"b"], + ["a",{"string":"base"},"b",{"string":"overscript"},{"string":"mathmlscripted"}] + ], + expectedBraille: [ + [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"overscriptAbbr"},"b"], + ["a",{"string":"baseAbbr"},"b",{"string":"overscriptAbbr"},{"string":"mathmlscriptedAbbr"}] + ] + }, { + accOrElmOrID: "munderover-1", + expectedUtterance: [ + [{"string":"mathmlscripted"},{"string":"base"},"a",{"string":"underscript"},"b",{"string":"overscript"},"c"], + ["a",{"string":"base"},"b",{"string":"underscript"},"c",{"string":"overscript"},{"string":"mathmlscripted"}] + ], + expectedBraille: [ + [{"string":"mathmlscriptedAbbr"},{"string":"baseAbbr"},"a",{"string":"underscriptAbbr"},"b",{"string":"overscriptAbbr"},"c"], + ["a",{"string":"baseAbbr"},"b",{"string":"underscriptAbbr"},"c",{"string":"overscriptAbbr"},{"string":"mathmlscriptedAbbr"}] + ] + }, { + accOrElmOrID: "mroot-1", + expectedUtterance: [ + [{"string":"mathmlroot"},{"string":"base"},"a",{"string":"root-index"},"b"], + ["a",{"string":"base"},"b",{"string":"root-index"},{"string":"mathmlroot"}] + ], + expectedBraille: [ + [{"string":"mathmlrootAbbr"},{"string":"baseAbbr"},"a",{"string":"root-indexAbbr"},"b"], + ["a",{"string":"baseAbbr"},"b",{"string":"root-indexAbbr"},{"string":"mathmlrootAbbr"}] + ] + }, { + accOrElmOrID: "mtable-1", + expectedUtterance: [ + [{"string":"mathmltable"},{"string":"tblColumnInfo","count":3},{"string":"tblRowInfo","count":2},{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[1]},"a",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[1]},"b",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[1]},"c",{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[2]},"d",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[2]},"e",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[2]},"f"], + ["a",{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[1]},"b",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[1]},"c",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[1]},"d",{"string":"columnInfo","args":[1]},{"string":"rowInfo","args":[2]},"e",{"string":"columnInfo","args":[2]},{"string":"rowInfo","args":[2]},"f",{"string":"columnInfo","args":[3]},{"string":"rowInfo","args":[2]},{"string":"mathmltable"},{"string":"tblColumnInfo","count":3},{"string":"tblRowInfo","count":2}] + ], + expectedBraille: [ + [{"string":"mathmltableAbbr"},{"string":"tblColumnInfoAbbr","count":3},{"string":"tblRowInfoAbbr","count":2},{"string":"cellInfoAbbr","args":[1,1]},"a",{"string":"cellInfoAbbr","args":[2,1]},"b",{"string":"cellInfoAbbr","args":[3,1]},"c",{"string":"cellInfoAbbr","args":[1,2]},"d",{"string":"cellInfoAbbr","args":[2,2]},"e",{"string":"cellInfoAbbr","args":[3,2]},"f"], + ["a",{"string":"cellInfoAbbr","args":[1,1]},"b",{"string":"cellInfoAbbr","args":[2,1]},"c",{"string":"cellInfoAbbr","args":[3,1]},"d",{"string":"cellInfoAbbr","args":[1,2]},"e",{"string":"cellInfoAbbr","args":[2,2]},"f",{"string":"cellInfoAbbr","args":[3,2]},{"string":"mathmltableAbbr"},{"string":"tblColumnInfoAbbr","count":3},{"string":"tblRowInfoAbbr","count":2}] + ] + }, { + accOrElmOrID: "menclose-1", + expectedUtterance: [ + [{"string":"mathmlenclosed"},{"string":"notation-longdiv"},"a"], + ["a",{"string":"notation-longdiv"},{"string":"mathmlenclosed"}] + ], + expectedBraille: [ + [{"string":"mathmlenclosedAbbr"},{"string":"notation-longdivAbbr"},"a"], + ["a",{"string":"notation-longdivAbbr"},{"string":"mathmlenclosedAbbr"}] + ] + }, { + accOrElmOrID: "menclose-2", + expectedUtterance: [ + [{"string":"mathmlenclosed"},{"string":"notation-circle"},"a"], + ["a",{"string":"notation-circle"},{"string":"mathmlenclosed"}] + ], + expectedBraille: [ + [{"string":"mathmlenclosedAbbr"},{"string":"notation-circleAbbr"},"a"], + ["a",{"string":"notation-circleAbbr"},{"string":"mathmlenclosedAbbr"}] + ] + }, { + accOrElmOrID: "menclose-3", + expectedUtterance: [ + [{"string":"mathmlenclosed"},{"string":"notation-left"},{"string":"notation-top"},{"string":"notation-bottom"},"a"], + ["a",{"string":"notation-left"},{"string":"notation-top"},{"string":"notation-bottom"},{"string":"mathmlenclosed"}] + ], + expectedBraille: [ + [{"string":"mathmlenclosedAbbr"},{"string":"notation-leftAbbr"},{"string":"notation-topAbbr"},{"string":"notation-bottomAbbr"},"a"], + ["a",{"string":"notation-leftAbbr"},{"string":"notation-topAbbr"},{"string":"notation-bottomAbbr"},{"string":"mathmlenclosedAbbr"}] + ] + }]; + + // Test all possible utterance order preference values. + function testOutputOrder(aOutputOrder) { + return function() { + SpecialPowers.pushPrefEnv({ + "set": [[PREF_UTTERANCE_ORDER, aOutputOrder]] + }, function() { + tests.forEach(function run(test) { + testOutput(test.expectedUtterance[aOutputOrder], test.accOrElmOrID, + test.oldAccOrElmOrID, 1); + testOutput(test.expectedBraille[aOutputOrder], test.accOrElmOrID, + test.oldAccOrElmOrID, 0); + }); + AccessFuTest.nextTest(); + }); + }; + } + + AccessFuTest.addFunc(testOutputOrder(0)); + AccessFuTest.addFunc(testOutputOrder(1)); + AccessFuTest.waitForExplicitFinish(); + AccessFuTest.runTests(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <div id="root"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1163374" + title="[AccessFu] MathML Accessibility Support"> + Mozilla Bug 1163374 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + + <math id="math-1"><mo>(</mo><mi>x</mi><mo>,</mo><mi>y</mi><mo>)</mo></math> + + <math> + <mfrac id="mfrac-1"> + <mi>a</mi> + <mi>b</mi> + </mfrac> + </math> + + <math> + <mfrac id="mfrac-2" linethickness="0px"> + <mi>a</mi> + <mi>b</mi> + </mfrac> + </math> + + <math> + <msub id="msub-1"> + <mi>a</mi> + <mi>b</mi> + </msub> + </math> + <math> + <msup id="msup-1"> + <mi>a</mi> + <mi>b</mi> + </msup> + </math> + <math> + <msubsup id="msubsup-1"> + <mi>a</mi> + <mi>b</mi> + <mi>c</mi> + </msubsup> + </math> + <math> + <mmultiscripts id="mmultiscripts-1"> + <mi>a</mi> + <mi>b</mi> + <mi>c</mi> + <none/> + <mi>d</mi> + <mprescripts/> + <mi>e</mi> + <none/> + <mi>f</mi> + <mi>g</mi> + </mmultiscripts> + </math> + + <math> + <munder id="munder-1"> + <mi>a</mi> + <mi>b</mi> + </munder> + </math> + <math> + <mover id="mover-1"> + <mi>a</mi> + <mi>b</mi> + </mover> + </math> + <math> + <munderover id="munderover-1"> + <mi>a</mi> + <mi>b</mi> + <mi>c</mi> + </munderover> + </math> + + <math> + <mroot id="mroot-1"> + <mi>a</mi> + <mi>b</mi> + </mroot> + </math> + + <math> + <mtable id="mtable-1"> + <mtr> + <mtd><mi>a</mi></mtd> + <mtd><mi>b</mi></mtd> + <mtd><mi>c</mi></mtd> + </mtr> + <mtr> + <mtd><mi>d</mi></mtd> + <mtd><mi>e</mi></mtd> + <mtd><mi>f</mi></mtd> + </mtr> + </mtable> + </math> + + <math> + <menclose id="menclose-1"><mi>a</mi></menclose> + </math> + <math> + <menclose id="menclose-2" notation="circle"><mi>a</mi></menclose> + </math> + <math> + <menclose id="menclose-3" notation="left top bottom"><mi>a</mi></menclose> + </math> + + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_pointer_relay.html b/accessible/tests/mochitest/jsat/test_pointer_relay.html new file mode 100644 index 000000000..cb58fe73b --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_pointer_relay.html @@ -0,0 +1,95 @@ +<html> + +<head> + <title>AccessFu tests for pointer relay.</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + 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" src="./jsatcommon.js"></script> + <script type="application/javascript" src="./dom_helper.js"></script> + <script type="application/javascript"> + + Components.utils.import( + "resource://gre/modules/accessibility/PointerAdapter.jsm"); + + var tests = [ + { + type: 'touchstart', target: [{base: 'button'}], + expected: {type: 'pointerdown', length: 1} + }, + { + type: 'touchmove', target: [{base: 'button'}], + expected: {type: 'pointermove', length: 1} + }, + { + type: 'touchend', target: [{base: 'button'}], + expected: {type: 'pointerup'} + }, + { + type: 'touchstart', target: [{base: 'button'}, + {base: 'button', x: 0.5, y: 0.3}], + expected: {type: 'pointerdown', length: 2} + }, + { + type: 'touchend', target: [{base: 'button'}, + {base: 'button', x: 0.5, y: 0.3}], + expected: {type: 'pointerup'} + }, + { + type: 'touchstart', target: [{base: 'button'}, + {base: 'button', x: 0.5, y: 0.3}, + {base: 'button', x: 0.5, y: -0.3}], + expected: {type: 'pointerdown', length: 3} + }, + { + type: 'touchend', target: [{base: 'button'}, + {base: 'button', x: 0.5, y: 0.3}, + {base: 'button', x: 0.5, y: -0.3}], + expected: {type: 'pointerup'} + } + ]; + + function makeTestFromSpec(test) { + return function runTest() { + PointerRelay.start(function onPointerEvent(aDetail) { + is(aDetail.type, test.expected.type, + 'mozAccessFuPointerEvent is correct.'); + if (test.expected.length) { + is(aDetail.points.length, test.expected.length, + 'mozAccessFuPointerEvent points length is correct.'); + } + PointerRelay.stop(); + AccessFuTest.nextTest(); + }); + eventMap[test.type](test.target, test.type); + }; + } + + function doTest() { + tests.forEach(function addTest(test) { + AccessFuTest.addFunc(makeTestFromSpec(test)); + }); + AccessFuTest.waitForExplicitFinish(); + AccessFuTest.runTests(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> +<body id="root"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=976082" + title="[AccessFu] Provide tests for pointer relay."> + Mozilla Bug 981015 + </a> + <div> + <button id="button">I am a button</button> + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_quicknav_modes.html b/accessible/tests/mochitest/jsat/test_quicknav_modes.html new file mode 100644 index 000000000..f99b64a84 --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_quicknav_modes.html @@ -0,0 +1,107 @@ +<html> + +<head> + <title>AccessFu test for enabling</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="./jsatcommon.js"></script> + <script type="application/javascript"> + + function prefStart() { + // Start AccessFu via pref. + SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 1]]}); + AccessFuTest.once_log("EventManager.start", AccessFuTest.nextTest); + } + + function nextMode(aCurrentMode, aNextMode) { + return function() { + is(AccessFu.Input.quickNavMode.current, aCurrentMode, + 'initial current mode is correct'); + AccessFu.Input.quickNavMode.next(); + _expectMode(aNextMode, AccessFuTest.nextTest); + } + } + + function prevMode(aCurrentMode, aNextMode) { + return function() { + is(AccessFu.Input.quickNavMode.current, aCurrentMode, + 'initial current mode is correct'); + AccessFu.Input.quickNavMode.previous(); + _expectMode(aNextMode, AccessFuTest.nextTest); + } + } + + function setMode(aModeIndex, aExpectedMode) { + return function() { + SpecialPowers.pushPrefEnv( + {"set": [['accessibility.accessfu.quicknav_index', aModeIndex]]}, + function() { + _expectMode(aExpectedMode, AccessFuTest.nextTest); + }); + } + } + + function reconfigureModes() { + SpecialPowers.pushPrefEnv( + {"set": [['accessibility.accessfu.quicknav_modes', 'Landmark,Button,Entry,Graphic']]}, + function() { + // When the modes are reconfigured, the current mode should + // be set to the first in the new list. + _expectMode('Landmark', AccessFuTest.nextTest); + }); + } + + function _expectMode(aExpectedMode, aCallback) { + if (AccessFu.Input.quickNavMode.current === aExpectedMode) { + ok(true, 'correct mode'); + aCallback(); + } else { + AccessFuTest.once_log('Quicknav mode: ' + aExpectedMode, function() { + ok(true, 'correct mode'); + aCallback(); + }); + } + } + + // Listen for initial 'EventManager.start' and disable AccessFu. + function prefStop() { + ok(AccessFu._enabled, "AccessFu was started via preference."); + AccessFuTest.once_log("EventManager.stop", () => AccessFuTest.finish()); + SpecialPowers.pushPrefEnv({"set": [['accessibility.accessfu.activate', 0]]}); + } + + function doTest() { + AccessFuTest.addFunc(prefStart); + AccessFuTest.addFunc(nextMode('Link', 'Heading')); + AccessFuTest.addFunc(nextMode('Heading', 'FormElement')); + AccessFuTest.addFunc(nextMode('FormElement', 'Link')); + AccessFuTest.addFunc(nextMode('Link', 'Heading')); + AccessFuTest.addFunc(prevMode('Heading', 'Link')); + AccessFuTest.addFunc(prevMode('Link', 'FormElement')); + AccessFuTest.addFunc(setMode(1, 'Heading')); + AccessFuTest.addFunc(reconfigureModes); + AccessFuTest.addFunc(prefStop); + AccessFuTest.waitForExplicitFinish(); + AccessFuTest.runTests([ // Will call SimpleTest.finish(); + ['accessibility.accessfu.quicknav_modes', 'Link,Heading,FormElement']]); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> +<body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=811307" + title="[AccessFu] Add mochitest for enabling"> + Mozilla Bug 811307 + </a> +</body> +</html>
\ No newline at end of file diff --git a/accessible/tests/mochitest/jsat/test_tables.html b/accessible/tests/mochitest/jsat/test_tables.html new file mode 100644 index 000000000..aa7f482e9 --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_tables.html @@ -0,0 +1,579 @@ +<html> +<head> + <title>[AccessFu] Improve reading of table semantics</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="output.js"></script> + <script type="application/javascript" + src="jsatcommon.js"></script> + <script type="application/javascript"> + + function doTest() { + // Test the following accOrElmOrID. + var tests = [{ + accOrElmOrID: "table1", + expectedUtterance: [[ + {"string": "table"}, + {"string": "tblColumnInfo", "count": 2}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "col1", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "col1", "cell1", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [2]}, "col2", "cell2"], ["col1", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "col2", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "cell1", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "col1", "cell2", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [2]}, "col2", {"string": "table"}, + {"string": "tblColumnInfo", "count": 2}, + {"string": "tblRowInfo", "count": 2}]], + expectedBraille: [[ + {"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 2}, + {"string": "tblRowInfoAbbr", "count": 2}, + {"string": "cellInfoAbbr", "args": [1, 1]}, "col1", + {"string": "cellInfoAbbr", "args": [2, 1]}, "col2", + {"string": "cellInfoAbbr", "args": [1, 2]}, "col1", "cell1", + {"string": "cellInfoAbbr", "args": [2, 2]}, "col2", "cell2"], ["col1", + {"string": "cellInfoAbbr", "args": [1, 1]}, "col2", + {"string": "cellInfoAbbr", "args": [2, 1]}, "cell1", + {"string": "cellInfoAbbr", "args": [1, 2]}, "col1", "cell2", + {"string": "cellInfoAbbr", "args": [2, 2]}, "col2", + {"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 2}, + {"string": "tblRowInfoAbbr", "count": 2}]] + }, { + accOrElmOrID: "table2", + expectedUtterance: [[ + {"string": "table"}, + {"string": "tblColumnInfo", "count": 2}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "col1", "cell1", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "colheader", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", "bla", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "col1", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [2]}, "col2"], ["cell1", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "col1", "colheader", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "bla", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2", "col1", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "col2", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [2]}, {"string": "table"}, + {"string": "tblColumnInfo", "count": 2}, + {"string": "tblRowInfo", "count": 2}]], + expectedBraille: [[{"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 2}, + {"string": "tblRowInfoAbbr", "count": 2}, + {"string": "cellInfoAbbr", "args": [1, 1]}, "col1", "cell1", + {"string": "cellInfoAbbr", "args": [2, 1]}, "col2", + {"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 1}, + {"string": "tblRowInfoAbbr", "count": 2}, + {"string": "cellInfoAbbr", "args": [1, 1]}, "colheader", + {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla", + {"string": "cellInfoAbbr", "args": [1, 2]}, "col1", + {"string": "cellInfoAbbr", "args": [2, 2]}, "col2"], ["cell1", + {"string": "cellInfoAbbr", "args": [1, 1]}, "col1", "colheader", + {"string": "cellInfoAbbr", "args": [1, 1]}, "bla", + {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", + {"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 1}, + {"string": "tblRowInfoAbbr", "count": 2}, + {"string": "cellInfoAbbr", "args": [2, 1]}, "col2", "col1", + {"string": "cellInfoAbbr", "args": [1, 2]}, "col2", + {"string": "cellInfoAbbr", "args": [2, 2]}, + {"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 2}, + {"string": "tblRowInfoAbbr", "count": 2}]] + }, { + accOrElmOrID: "table3", + expectedUtterance: [[ + {"string": "table"}, + {"string": "tblColumnInfo", "count": 2}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "colheader", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", "bla"], ["colheader", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "bla", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 2}, + {"string": "tblRowInfo", "count": 2}]], + expectedBraille: [[ + {"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 1}, + {"string": "tblRowInfoAbbr", "count": 2}, + {"string": "cellInfoAbbr", "args": [1, 1]}, "colheader", + {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla"], + ["colheader", + {"string": "cellInfoAbbr", "args": [1, 1]}, "bla", + {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", + {"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 1}, + {"string": "tblRowInfoAbbr", "count": 2}]] + }, { + accOrElmOrID: "table4", + expectedUtterance: [[ + {"string": "table"}, + {"string": "tblColumnInfo", "count": 4}, + {"string": "tblRowInfo", "count": 3}, + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "col1", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2", + {"string": "columnInfo", "args": [3]}, + {"string": "rowInfo", "args": [1]}, "col3", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, + {"string": "spansColumns", "args": [2]}, "col1", "row1", + {"string": "columnInfo", "args": [3]}, + {"string": "rowInfo", "args": [2]}, "col3", "row1", "cell1", + {"string": "columnInfo", "args": [4]}, + {"string": "rowInfo", "args": [2]}, + {"string": "spansRows", "args": [2]}, "row1", "cell2", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [3]}, "col1", "row2", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [3]}, "col2", "row2", "cell3", + {"string": "columnInfo", "args": [3]}, + {"string": "rowInfo", "args": [3]}, "col3", "row2", "cell4"], ["col1", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "col2", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col3", + {"string": "columnInfo", "args": [3]}, + {"string": "rowInfo", "args": [1]}, "row1", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, + {"string": "spansColumns", "args": [2]}, "col1", "cell1", + {"string": "columnInfo", "args": [3]}, + {"string": "rowInfo", "args": [2]}, "col3", "row1", "cell2", + {"string": "columnInfo", "args": [4]}, + {"string": "rowInfo", "args": [2]}, + {"string": "spansRows", "args": [2]}, "row1", "row2", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [3]}, "col1", "cell3", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [3]}, "col2", "row2", "cell4", + {"string": "columnInfo", "args": [3]}, + {"string": "rowInfo", "args": [3]}, "col3", "row2", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 4}, + {"string": "tblRowInfo", "count": 3}]], + expectedBraille: [[ + {"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 4}, + {"string": "tblRowInfoAbbr", "count": 3}, + {"string": "cellInfoAbbr", "args": [1, 1]}, "col1", + {"string": "cellInfoAbbr", "args": [2, 1]}, "col2", + {"string": "cellInfoAbbr", "args": [3, 1]}, "col3", + {"string": "cellInfoAbbr", "args": [1, 2]}, "col1", "row1", + {"string": "cellInfoAbbr", "args": [3, 2]}, "col3", "row1", "cell1", + {"string": "cellInfoAbbr", "args": [4, 2]}, "row1", "cell2", + {"string": "cellInfoAbbr", "args": [1, 3]}, "col1", "row2", + {"string": "cellInfoAbbr", "args": [2, 3]}, "col2", "row2", "cell3", + {"string": "cellInfoAbbr", "args": [3, 3]}, "col3", "row2", "cell4"], + ["col1", + {"string": "cellInfoAbbr", "args": [1, 1]}, "col2", + {"string": "cellInfoAbbr", "args": [2, 1]}, "col3", + {"string": "cellInfoAbbr", "args": [3, 1]}, "row1", + {"string": "cellInfoAbbr", "args": [1, 2]}, "col1", "cell1", + {"string": "cellInfoAbbr", "args": [3, 2]}, "col3", "row1", "cell2", + {"string": "cellInfoAbbr", "args": [4, 2]}, "row1", "row2", + {"string": "cellInfoAbbr", "args": [1, 3]}, "col1", "cell3", + {"string": "cellInfoAbbr", "args": [2, 3]}, "col2", "row2", "cell4", + {"string": "cellInfoAbbr", "args": [3, 3]}, "col3", "row2", + {"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 4}, + {"string": "tblRowInfoAbbr", "count": 3}]] + }, { + accOrElmOrID: "table5", + expectedUtterance: [["Row1", "Row2"], ["Row1", "Row2"]], + expectedBraille: [["Row1", "Row2"], ["Row1", "Row2"]] + }, { + // Test pivot to table1_th1 from table1. + accOrElmOrID: "table1_th1", + oldAccOrElmOrID: "table1", + expectedUtterance: [[ + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "col1"], ["col1", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}]], + expectedBraille: [[ + {"string": "cellInfoAbbr", "args": [1, 1]}, "col1"], ["col1", + {"string": "cellInfoAbbr", "args": [1, 1]}]] + }, { + // Test pivot to table1_td2 from table1. + accOrElmOrID: "table1_td2", + oldAccOrElmOrID: "table1", + expectedUtterance: [[ + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [2]}, "col2", "cell2"], ["cell2", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [2]}, "col2"]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [2, 2]}, "col2", "cell2"], + ["cell2", {"string": "cellInfoAbbr", "args": [2, 2]}, "col2"]] + }, { + // Test pivot to table1_td2 from table1_th1. + accOrElmOrID: "table1_td2", + oldAccOrElmOrID: "table1_th1", + expectedUtterance: [[ + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [2]}, "col2", "cell2"], ["cell2", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [2]}, "col2"]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [2, 2]}, "col2", "cell2"], + ["cell2", {"string": "cellInfoAbbr", "args": [2, 2]}, "col2"]] + }, { + // Test pivot to table1_td2 from table1_td1. + accOrElmOrID: "table1_td2", + oldAccOrElmOrID: "table1_td1", + expectedUtterance: [[ + {"string": "columnInfo", "args": [2]}, "col2", "cell2"], ["cell2", + {"string": "columnInfo", "args": [2]}, "col2"]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [2, 2]}, "col2", "cell2"], + ["cell2", {"string": "cellInfoAbbr", "args": [2, 2]}, "col2"]] + }, { + // Test pivot to table2_cell_1 from table2. + accOrElmOrID: "table2_cell_1", + oldAccOrElmOrID: "table2", + expectedUtterance: [[ + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "col1", "cell1"], ["cell1", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "col1"]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [1, 1]}, "col1", "cell1"], + ["cell1", {"string": "cellInfoAbbr", "args": [1, 1]}, "col1"]] + }, { + // Test pivot to table2_cell_2 from table2. + accOrElmOrID: "table2_cell_2", + oldAccOrElmOrID: "table2", + expectedUtterance: [[ + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "colheader", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", "bla"], ["colheader", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [1]}, "bla", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2"]], + expectedBraille: [[ + {"string": "cellInfoAbbr", "args": [2, 1]}, "col2", + {"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 1}, + {"string": "tblRowInfoAbbr", "count": 2}, + {"string": "cellInfoAbbr", "args": [1, 1]}, "colheader", + {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla"], + ["colheader", + {"string": "cellInfoAbbr", "args": [1, 1]}, "bla", + {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", + {"string": "tableAbbr"}, + {"string": "tblColumnInfoAbbr", "count": 1}, + {"string": "tblRowInfoAbbr", "count": 2}, + {"string": "cellInfoAbbr", "args": [2, 1]}, "col2"]] + }, { + // Test pivot to table2_cell_1 from table2_cell_2. + accOrElmOrID: "table2_cell_1", + oldAccOrElmOrID: "table2_cell_2", + expectedUtterance: [[ + {"string": "columnInfo", "args": [1]}, "col1", "cell1"], ["cell1", + {"string": "columnInfo", "args": [1]}, "col1"]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [1, 1]}, "col1", "cell1"], + ["cell1", {"string": "cellInfoAbbr", "args": [1, 1]}, "col1"]] + }, { + // Test pivot to table3_cell from table2. + accOrElmOrID: "table3_cell", + oldAccOrElmOrID: "table2", + expectedUtterance: [[ + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", "bla"], ["bla", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2"]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla"], + ["bla", {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader"]] + }, { + // Test pivot to table3_cell from table2_cell_1. + accOrElmOrID: "table3_cell", + oldAccOrElmOrID: "table2_cell_1", + expectedUtterance: [[ + {"string": "columnInfo", "args": [2]}, "col2", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", "bla"], ["bla", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2"]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla"], + ["bla", {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader"]] + }, { + // Test pivot to table3_cell from table3_ch. + accOrElmOrID: "table3_cell", + oldAccOrElmOrID: "table3_ch", + expectedUtterance: [[ + {"string": "rowInfo", "args": [2]}, "bla"], ["bla", + {"string": "rowInfo", "args": [2]}]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [1, 2]}, "bla"], + ["bla", {"string": "cellInfoAbbr", "args": [1, 2]}]] + }, { + // Test pivot to table3_cell from table1_td1. + accOrElmOrID: "table3_cell", + oldAccOrElmOrID: "table1_td1", + expectedUtterance: [[ + {"string": "table"}, + {"string": "tblColumnInfo", "count": 2}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", "bla"], ["bla", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, "colheader", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 1}, + {"string": "tblRowInfo", "count": 2}, + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [1]}, "col2", + {"string": "table"}, + {"string": "tblColumnInfo", "count": 2}, + {"string": "tblRowInfo", "count": 2}]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [1, 2]}, "colheader", "bla"], + ["bla", {"string": "cellInfoAbbr", "args": [1, 2]}, "colheader"]] + }, { + // Test pivot to table4_ch_3 from table4. + accOrElmOrID: "table4_ch_3", + oldAccOrElmOrID: "table4", + expectedUtterance: [[ + {"string": "columnInfo", "args": [3]}, + {"string": "rowInfo", "args": [1]}, "col3"], ["col3", + {"string": "columnInfo", "args": [3]}, + {"string": "rowInfo", "args": [1]}]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [3, 1]}, "col3"], + ["col3", {"string": "cellInfoAbbr", "args": [3, 1]}]] + }, { + // Test pivot to table4_rh_1 from table4_ch_3. + accOrElmOrID: "table4_rh_1", + oldAccOrElmOrID: "table4_ch_3", + expectedUtterance: [[ + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, + {"string": "spansColumns", "args": [2]}, "col1", "row1"], ["row1", + {"string": "columnInfo", "args": [1]}, + {"string": "rowInfo", "args": [2]}, + {"string": "spansColumns", "args": [2]}, "col1"]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [1, 2]}, "col1", "row1"], + ["row1", {"string": "cellInfoAbbr", "args": [1, 2]}, "col1"]] + }, { + // Test pivot to table4_cell_3 from table4_rh_1. + accOrElmOrID: "table4_cell_3", + oldAccOrElmOrID: "table4_rh_1", + expectedUtterance: [[ + {"string": "columnInfo", "args": [4]}, + {"string": "spansRows", "args": [2]}, "cell2"], ["cell2", + {"string": "columnInfo", "args": [4]}, + {"string": "spansRows", "args": [2]}]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [4, 2]}, "cell2"], + ["cell2", {"string": "cellInfoAbbr", "args": [4, 2]}]] + }, { + // Test pivot to table4_cell_5 from table4_cell_3. + accOrElmOrID: "table4_cell_5", + oldAccOrElmOrID: "table4_cell_3", + expectedUtterance: [[ + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [3]}, "col2", "row2", "cell3"], + ["cell3", + {"string": "columnInfo", "args": [2]}, + {"string": "rowInfo", "args": [3]}, "col2", "row2"]], + expectedBraille: [ + [{"string": "cellInfoAbbr", "args": [2, 3]}, "col2", "row2", "cell3"], + ["cell3", {"string": "cellInfoAbbr", "args": [2, 3]}, "col2", "row2"]] + }]; + + // Test outputs (utterance and braille) for tables including their + // headers and cells. + function testOutputOrder(aOutputOrder) { + return function() { + SpecialPowers.pushPrefEnv({ + "set": [[PREF_UTTERANCE_ORDER, aOutputOrder]] + }, function() { + tests.forEach(function run(test) { + testOutput(test.expectedUtterance[aOutputOrder], test.accOrElmOrID, + test.oldAccOrElmOrID, 1); + testOutput(test.expectedBraille[aOutputOrder], test.accOrElmOrID, + test.oldAccOrElmOrID, 0); + }); + AccessFuTest.nextTest(); + }); + }; + } + + AccessFuTest.addFunc(testOutputOrder(0)); + AccessFuTest.addFunc(testOutputOrder(1)); + AccessFuTest.waitForExplicitFinish(); + AccessFuTest.runTests(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + <div id="root"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=830748" + title="[AccessFu] Improve reading of table semantics"> + Mozilla Bug 830748 + </a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + <table id="table1"> + <thead> + <tr> + <th id="table1_th1">col1</th> + <th>col2</th> + </tr> + </thead> + <tbody> + <tr> + <td id="table1_td1">cell1</td> + <td id="table1_td2">cell2</td> + </tr> + </tbody> + </table> + <table id="table2" border="1"> + <tr> + <td id="table2_cell_1" headers="table2_ch_1">cell1</td> + <td id="table2_cell_2" headers="table2_ch_2"> + <table id="table3"> + <thead> + <tr> + <th id="table3_ch">colheader</th> + </tr> + </thead> + <tbody> + <tr> + <td id="table3_cell">bla</td> + </tr> + </tbody> + </table> + </td> + </tr> + <tr> + <td id="table2_ch_1" scope="col">col1</td> + <td id="table2_ch_2" scope="col">col2</td> + </tr> + </table> + <table id="table4" border="1"> + <thead> + <tr> + <th id="table4_ch_1">col1</th> + <th id="table4_ch_2">col2</th> + <td id="table4_ch_3" scope="col">col3</td> + </tr> + </thead> + <tbody> + <tr> + <th id="table4_rh_1" colspan="2">row1</th> + <td id="table4_cell_2">cell1</td> + <td id="table4_cell_3" rowspan="2">cell2</td> + </tr> + <tr> + <td id="table4_rh_2" scope="row">row2</td> + <td id="table4_cell_5">cell3</td> + <td id="table4_cell_6">cell4</td> + </tr> + </tbody> + </table> + <table id="table5"> + <tr><td>Row1</td></tr> + <tr><td>Row2</td></tr> + </table> + </div> +</body> +</html>
\ No newline at end of file diff --git a/accessible/tests/mochitest/jsat/test_traversal.html b/accessible/tests/mochitest/jsat/test_traversal.html new file mode 100644 index 000000000..533e9d89f --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_traversal.html @@ -0,0 +1,167 @@ +<!DOCTYPE html> +<html> +<head> + <title>Tests AccessFu TraversalRules</title> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + <script type="application/javascript" + 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"> + Components.utils.import("resource://gre/modules/accessibility/Traversal.jsm"); + var gBrowserWnd = null; + var gQueue = null; + + function doTest() + { + var doc = currentTabDocument(); + var docAcc = getAccessible(doc, [nsIAccessibleDocument]); + + gQueue = new eventQueue(); + + gQueue.onFinish = function onFinish() + { + closeBrowserWindow(); + } + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Heading, null, + ['heading-1', 'heading-2', 'heading-3', 'heading-5']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Entry, null, + ['input-1-1', 'label-1-2', 'input-1-3', + 'input-1-4', 'input-1-5']); + + // move back an element to hit all the form elements, because the VC is + // currently at the first input element + gQueue.push(new setVCPosInvoker(docAcc, "movePrevious", + TraversalRules.Heading, "heading-1")); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.FormElement, null, + ['input-1-1', 'label-1-2', 'button-1-1', + 'radio-1-1', 'radio-1-2', 'input-1-3', + 'input-1-4', 'button-1-2', 'checkbox-1-1', + 'select-1-1', 'select-1-2', 'checkbox-1-2', + 'select-1-3', 'input-1-5', 'button-1-3', + 'button-2-1', 'button-2-2', 'button-2-3', + 'button-2-4', 'checkbox-1-5', 'switch-1']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Button, null, + ['button-1-1', 'button-1-2', 'button-1-3', + 'button-2-1', 'button-2-2', 'button-2-3', + 'button-2-4']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.RadioButton, null, + ['radio-1-1', 'radio-1-2']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Checkbox, null, + ['checkbox-1-1', 'checkbox-1-2', 'checkbox-1-5', + 'switch-1']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Combobox, null, + ['select-1-1', 'select-1-2', 'select-1-3']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.List, null, + ['list-1', 'list-2', 'list-3']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.ListItem, null, + ['listitem-1-1', 'listitem-2-1', 'listitem-2-2', + 'listitem-3-1', 'listitem-3-2', 'listitem-3-3', + 'listitem-3-4', 'listitem-3-5', 'listitem-3-6', + 'listitem-2-3']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Graphic, null, + ['image-2', 'image-3']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Link, null, + ['link-0', 'link-1', 'link-2', 'link-3']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Anchor, null, + ['anchor-1', 'anchor-2']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Separator, null, + ['separator-1', 'separator-2']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Table, null, + ['table-1', 'grid', 'table-2']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Simple, null, + ['heading-1', 'Name:', 'input-1-1', 'label-1-2', + 'button-1-1', 'Radios are old: ', 'radio-1-1', + 'Radios are new: ', 'radio-1-2', 'Password:', + 'input-1-3', 'Unlucky number:', 'input-1-4', + 'button-1-2', 'Check me: ', 'checkbox-1-1', + 'select-1-1', 'Value 1', 'Value 2', 'Value 3', + 'Check me too: ', 'checkbox-1-2', 'But not me: ', + 'Or me! ', 'Value 1', 'Value 2', 'Value 3', + 'Electronic mailing address:', 'input-1-5', + 'button-1-3', 'heading-2', 'heading-3', + 'button-2-1', 'button-2-2', 'button-2-3', + 'button-2-4', 'Programming Language', + 'A esoteric weapon wielded by only the most ' + + 'formidable warriors, for its unrelenting strict' + + ' power is unfathomable.', + '• Lists of Programming Languages', 'Lisp ', + '1. Scheme', '2. Racket', '3. Clojure', + '4. Standard Lisp', 'link-0', ' Lisp', + 'checkbox-1-5', ' LeLisp', '• JavaScript', + 'heading-5', 'image-2', 'image-3', + 'Not actually an image', 'link-1', 'anchor-1', + 'link-2', 'anchor-2', 'link-3', '3', '1', '4', + '1', 'Sunday', 'M', 'Week 1', '3', '4', '7', '2', + '5 8', 'gridcell4', 'Just an innocuous separator', + 'Dirty Words', 'Meaning', 'Mud', 'Wet Dirt', + 'Dirt', 'Messy Stuff', 'statusbar-1', 'statusbar-2', + 'switch-1', 'This is a MathML formula ', 'math-1', + 'with some text after.']); + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Landmark, null, + ['header-1', 'main-1', 'footer-1']); + + + queueTraversalSequence(gQueue, docAcc, TraversalRules.Control, null, + ['input-1-1', 'label-1-2', 'button-1-1', + 'radio-1-1', 'radio-1-2', 'input-1-3', + 'input-1-4', 'button-1-2', 'checkbox-1-1', + 'select-1-1', 'select-1-2', 'checkbox-1-2', + 'select-1-3', 'input-1-5', 'button-1-3', + 'button-2-1', 'button-2-2', 'button-2-3', + 'button-2-4', 'link-0', 'checkbox-1-5', + 'link-1', 'link-2', 'link-3', 'switch-1']); + + 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_traversal.html"); + }); + </script> +</head> +<body id="body"> + + <a target="_blank" + title="Add tests for AccessFu TraversalRules" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=933808">Mozilla Bug 933808</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/jsat/test_traversal_helper.html b/accessible/tests/mochitest/jsat/test_traversal_helper.html new file mode 100644 index 000000000..f6a347ed0 --- /dev/null +++ b/accessible/tests/mochitest/jsat/test_traversal_helper.html @@ -0,0 +1,113 @@ +<!DOCTYPE html> +<html> +<head> + <title>Tests AccessFu TraversalRules</title> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + <script type="application/javascript" + 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"> + Components.utils.import("resource://gre/modules/accessibility/Traversal.jsm"); + + var vc; + + function accessibleIs(aAccessible, aExpected, aMessage) { + if (!aAccessible && aAccessible == aExpected) { + ok(true, "Accessible is null. " + aMessage); + } else { + ok(aAccessible.DOMNode.id == aExpected || aAccessible.name == aExpected, + "expected '" + aExpected + "', got " + prettyName(vc.position) + + ". " + aMessage); + } + } + + function walkSequence(aMethod, aRule, aExpectedSequence) { + for (var expected of aExpectedSequence) { + ok(TraversalHelper.move(vc, aMethod, aRule), + "successfully did " + aMethod + " with " + aRule); + accessibleIs(vc.position, expected, "landed on correct accessible"); + } + } + + function testTraversalHelper(aRule, aExpectedSequence) { + vc.position = null; + + walkSequence('moveNext', aRule, aExpectedSequence); + + ok(!TraversalHelper.move(vc, 'moveNext', aRule), "reached end"); + + TraversalHelper.move(vc, 'moveLast', 'Simple'); + + walkSequence('movePrevious', aRule, + Array.from(aExpectedSequence).reverse()); + + ok(!TraversalHelper.move(vc, 'movePrevious', aRule), "reached start"); + + vc.position = null; + + ok(TraversalHelper.move(vc, 'moveFirst', aRule), "moveFirst"); + + accessibleIs(vc.position, aExpectedSequence[0], + "moveFirst to correct accessible"); + + ok(TraversalHelper.move(vc, 'moveLast', aRule), "moveLast"); + + accessibleIs(vc.position, aExpectedSequence[aExpectedSequence.length - 1], + "moveLast to correct accessible"); + } + + + function doTest() + { + var doc = currentTabDocument(); + var docAcc = getAccessible(doc, [nsIAccessibleDocument]); + vc = docAcc.virtualCursor; + + testTraversalHelper('Landmark', + ['heading-1', 'heading-2', 'statusbar-1']); + + testTraversalHelper('List', + ['Programming Language', 'listitem-2-1', 'listitem-3-1']); + + testTraversalHelper('Section', + ['heading-1', 'heading-2', 'heading-3', + 'heading-5', 'link-1', 'statusbar-1']); + + SimpleTest.finish(); + } + + 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_traversal.html"); + }); + </script> +</head> +<body id="body"> + + <a target="_blank" + title="Add tests for AccessFu TraversalRules" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=xxx">Mozilla Bug xxx</a> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> +</body> +</html> diff --git a/accessible/tests/mochitest/layout.js b/accessible/tests/mochitest/layout.js new file mode 100644 index 000000000..e1fb14670 --- /dev/null +++ b/accessible/tests/mochitest/layout.js @@ -0,0 +1,258 @@ +/** + * 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) +{ + var docShell = aDocument.defaultView. + QueryInterface(Components.interfaces.nsIInterfaceRequestor). + getInterface(Components.interfaces.nsIWebNavigation). + QueryInterface(Components.interfaces.nsIDocShell); + var docViewer = docShell.contentViewer; + + docViewer.fullZoom = 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; + + 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); + is(xObj.value, expectedX, + "Wrong x coordinate of text between offsets (" + aStartOffset + ", " + + aEndOffset + ") for " + prettyName(aID)); + is(yObj.value, expectedY, + "Wrong y coordinate of text between offsets (" + aStartOffset + ", " + + aEndOffset + ") for " + prettyName(aID)); + + var msg = "Wrong width of text between offsets (" + aStartOffset + ", " + + aEndOffset + ") for " + prettyName(aID); + if (widthObj.value == expectedWidth) + ok(true, msg); + else + todo(false, msg); // fails on some windows machines + + is(heightObj.value, expectedHeight, + "Wrong 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. + */ +function getBounds(aID) +{ + var accessible = getAccessible(aID); + var x = {}, y = {}, width = {}, height = {}; + accessible.getBounds(x, y, width, height); + 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; + + var rect = img.getBoundingClientRect(); + x = rect.left + areaX; + y = rect.top + areaY; + width = areaWidth; + height = areaHeight; + } + else { + var rect = elm.getBoundingClientRect(); + x = rect.left; + y = rect.top; + width = rect.width; + height = rect.height; + } + + var elmWindow = elm.ownerDocument.defaultView; + return CSSToDevicePixels(elmWindow, + x + elmWindow.mozInnerScreenX, + y + elmWindow.mozInnerScreenY, + width, + height); +} + +function CSSToDevicePixels(aWindow, aX, aY, aWidth, aHeight) +{ + var winUtil = aWindow. + QueryInterface(Components.interfaces.nsIInterfaceRequestor). + getInterface(Components.interfaces.nsIDOMWindowUtils); + + var ratio = winUtil.screenPixelsPerCSSPixel; + + // 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 000000000..299b91784 --- /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 000000000..37248795d --- /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 000000000..4fb277037 --- /dev/null +++ b/accessible/tests/mochitest/moz.build @@ -0,0 +1,37 @@ +# -*- 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', + 'editabletext/a11y.ini', + 'elm/a11y.ini', + 'events/a11y.ini', + 'focus/a11y.ini', + 'hittest/a11y.ini', + 'hyperlink/a11y.ini', + 'hypertext/a11y.ini', + 'jsat/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 000000000..743292dc6 --- /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 000000000..8d8290905 --- /dev/null +++ b/accessible/tests/mochitest/name.js @@ -0,0 +1,33 @@ +/** + * 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 000000000..4d743f1f5 --- /dev/null +++ b/accessible/tests/mochitest/name/a11y.ini @@ -0,0 +1,20 @@ +[DEFAULT] +support-files = + general.css + general.xbl + markup.js + markuprules.xml + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/moz.png + +[test_browserui.xul] +[test_counterstyle.html] +[test_general.html] +[test_general.xul] +[test_link.html] +[test_list.html] +[test_markup.html] +skip-if = (debug && os == 'win') # Bug 1296784 +[test_svg.html] +[test_toolbaritem.xul] +[test_tree.xul] diff --git a/accessible/tests/mochitest/name/general.css b/accessible/tests/mochitest/name/general.css new file mode 100644 index 000000000..5f750c4dc --- /dev/null +++ b/accessible/tests/mochitest/name/general.css @@ -0,0 +1,11 @@ +box.first { + -moz-binding: url('general.xbl#first'); +} + +.second { + -moz-binding: url('general.xbl#second'); +} + +.third { + -moz-binding: url('general.xbl#third'); +} diff --git a/accessible/tests/mochitest/name/general.xbl b/accessible/tests/mochitest/name/general.xbl new file mode 100644 index 000000000..07489f5f4 --- /dev/null +++ b/accessible/tests/mochitest/name/general.xbl @@ -0,0 +1,32 @@ +<?xml version="1.0"?> + +<bindings xmlns="http://www.mozilla.org/xbl" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <binding id="first"> + <content> + <xul:textbox anonid="labeled" class="bottom"/> + <xul:label control="labeled" value="Label"/> + <children/> + </content> + </binding> + + <binding id="second"> + <content> + <xul:box class="first"> + <xul:label control="toplabeled" value="Top textbox"/> + <xul:textbox anonid="toplabeled" class="top"/> + </xul:box> + <children/> + </content> + </binding> + + <binding id="third"> + <content> + <xul:description anonid="label" value="It's a " /> + <xul:description anonid="label2" value="cool button" /> + <xul:button anonid="button" aria-labelledby="label label2" + value="button" /> + </content> + </binding> +</bindings> diff --git a/accessible/tests/mochitest/name/markup.js b/accessible/tests/mochitest/name/markup.js new file mode 100644 index 000000000..d3ecd8982 --- /dev/null +++ b/accessible/tests/mochitest/name/markup.js @@ -0,0 +1,382 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 serializer = new XMLSerializer(); + + 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"}); + + // If @recreated attribute is used then this attribute change recreates an + // accessible. Wait for reorder event in this case or otherwise proceed next + // test immediately. + if (aRule.hasAttribute("recreated")) { + waitForEvent(EVENT_REORDER, aElm.parentNode, + gTestIterator.iterateNext, gTestIterator); + aElm.removeAttribute(attr); + + } else if (aRule.hasAttribute("textchanged")) { + waitForEvent(EVENT_TEXT_INSERTED, aElm, + gTestIterator.iterateNext, gTestIterator); + aElm.removeAttribute(attr); + + } else if (aRule.hasAttribute("contentchanged")) { + waitForEvent(EVENT_REORDER, aElm, + gTestIterator.iterateNext, gTestIterator); + aElm.removeAttribute(attr); + + } else { + aElm.removeAttribute(attr); + gTestIterator.iterateNext(); + } +} + +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 reorder event on " + + prettyName(parentNode) + "\n"); + } + waitForEvent(EVENT_REORDER, parentNode, + 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_REORDER, aElm, gTestIterator.iterateNext, gTestIterator); + + while (aElm.firstChild) + aElm.removeChild(aElm.firstChild); +} + +/** + * 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 000000000..7f64ada34 --- /dev/null +++ b/accessible/tests/mochitest/name/markuprules.xml @@ -0,0 +1,373 @@ +<?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 accesible 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" textchanged="true"/> + </ruleset> + + <ruleset id="HTMLInputReset" defaultName="Reset"> + <ruleset ref="HTMLControl:Head"/> + <rule attr="value" type="string" explict-name="false" textchanged="true"/> + </ruleset> + + <ruleset id="HTMLInputImage"> + <ruleset ref="HTMLControl:Head"/> + <rule attr="alt" type="string" recreated="true"/> + <rule attr="value" type="string" recreated="true"/> + <rule attr="title" type="string"/> + </ruleset> + + <ruleset id="HTMLInputImageNoValidSrc" defaultName="Submit Query"> + <ruleset ref="HTMLControl:Head"/> + <rule attr="alt" type="string" explict-name="false" recreated="true"/> + <rule attr="value" type="string" explict-name="false" recreated="true"/> + </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="HTMLImgEmptyAlt"> + <ruleset ref="ARIA"/> + <rule attr="title" type="string"/> + <rule attr="alt" 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> + + <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="HTMLImageEmptyAltTest" + ref="html:img" ruleset="HTMLImgEmptyAlt"> + <html:span id="l1" textequiv="test2">test2</html:span> + <html:span id="l2" textequiv="test3">test3</html:span> + <html:img id="imgemptyalt" + aria-label="Logo of Mozilla" + aria-labelledby="l1 l2" + title="This is a logo" + alt="" + 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_browserui.xul b/accessible/tests/mochitest/name/test_browserui.xul new file mode 100644 index 000000000..ec21708fd --- /dev/null +++ b/accessible/tests/mochitest/name/test_browserui.xul @@ -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" + title="Accessibility Name Calculating Test."> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <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="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + function addTab(aURL) + { + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1) + ]; + + this.invoke = function addTab_invoke() + { + tabBrowser().addTab(aURL); + } + + this.getID = function addTab_getID() + { + return "add tab: " + aURL; + } + } + + function switchTab(aTabBrowser, aWindow) + { + this.invoke = function switchTab_invoke() + { + synthesizeKey("VK_TAB", { ctrlKey: true }, browserWindow()); + } + + this.eventSeq = [ + new focusChecker(tabDocumentAt, 1) + ]; + + this.check = function switchTab_check(aEvent) + { + var title = getAccessible(browserDocument()).name; + isnot(title.indexOf(aEvent.accessible.name), -1, + "Window title contains the name of active tab document" + + " (Is '" + aEvent.accessible.name + "' in '" + title + "'?)"); + } + + this.getID = function switchTab_getID() { return "switch tab"; } + } + + //////////////////////////////////////////////////////////////////////////// + // Tests + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; // debug + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + gQueue.push(new addTab("about:mozilla")); + gQueue.push(new switchTab()); + gQueue.onFinish = function() + { + closeBrowserWindow(); + } + + gQueue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTests, "about:"); + ]]> + </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 000000000..506cea69a --- /dev/null +++ b/accessible/tests/mochitest/name/test_counterstyle.html @@ -0,0 +1,153 @@ +<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 type="application/javascript" + 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 000000000..28e7a11a9 --- /dev/null +++ b/accessible/tests/mochitest/name/test_general.html @@ -0,0 +1,631 @@ +<html> + +<head> + <title>nsIAccessible::name calculation</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 (every text node + // value in the name should be devided by spaces). + // XXX: this case is rather a feature than strong wanted behaviour. + testName("btn_labelledby_mixed_table", "text space text"); + + // 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"); + + ////////////////////////////////////////////////////////////////////////// + // 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"); + + ////////////////////////////////////////////////////////////////////////// + // 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"); + + + ///////////////////////////////////////////////////////////////////////// + // 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); + + 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>space</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:" title="about"> </a> + + <!-- 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> + + <!-- 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> + + <!-- 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> + +</body> +</html> diff --git a/accessible/tests/mochitest/name/test_general.xul b/accessible/tests/mochitest/name/test_general.xul new file mode 100644 index 000000000..c144e6f4f --- /dev/null +++ b/accessible/tests/mochitest/name/test_general.xul @@ -0,0 +1,382 @@ +<?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="general.css" + type="text/css"?> + + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessibility Name Calculating Test."> + + <script type="application/javascript" + 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"); + + // Anonymous content (see name.xbl#third) + var anonBtn = getAccessible("labelledby_box_anon").lastChild; + testName(anonBtn, "It's a cool button"); + + ////////////////////////////////////////////////////////////////////////// + // 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 and selected item of menulist + // (other items are ignored). + testName("btn_labelledby_mixed_menulist", + "nomore 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", "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"); + + + ////////////////////////////////////////////////////////////////////////// + // Name for nsIDOMXULLabeledControlElement. + + // Gets the name from @label attribute. + testName("btn_nsIDOMXULLabeledControlElement", "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"); + + + ////////////////////////////////////////////////////////////////////////// + // Name from the label element in anonymous content (see bug 362365). + + // Get the name from anonymous label element for anonymous textbox + // (@anonid is used). + var ID = "box_label_anon1"; + var box1Acc = testName(ID, null); + if (box1Acc) { + var textboxAcc = box1Acc.firstChild; + is(textboxAcc.name, "Label", + "Wrong label for anonymous textbox of " + ID); + } + + // Get the name from anonymous label element for anonymous textbox + // (@anonid is used). Nested bindings. + ID = "box_label_anon2"; + var box2Acc = testName(ID, null); + if (box2Acc) { + var textboxAcc = box2Acc.firstChild; + is(textboxAcc.name, "Label", + "Wrong label for anonymous textbox of " + ID); + + var topTextboxAcc = box2Acc.lastChild; + is(topTextboxAcc.name, "Top textbox", + "Wrong label for anonymous textbox of " + ID); + } + + + ////////////////////////////////////////////////////////////////////////// + // 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. + var textboxAcc = testName("toolbaritem_textbox", "ooospspss"); + + // Element from anonymous content of direct child of toolbaritem. + var entryAcc = textboxAcc.firstChild; + testRole(entryAcc, ROLE_ENTRY); + is(entryAcc.name, "ooospspss", + "Wrong name for text entry of autocomplete textbox 'toolbaritem_textbox'."); + + // Child from subtree of toolbaritem. + testName("toolbaritem_hboxbutton", "ooospspss"); + + + ////////////////////////////////////////////////////////////////////////// + // Name from children + + // ARIA role button is presented allowing the name calculation from + // children. + testName("box_children", "14"); + + // 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 caption label or its sub tree + testName("groupbox", "Some caption"); + testName("groupbox2", "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"/> + + <!-- aria-labelledby, multiple relations --> + <box class="third" id="labelledby_box_anon" role="group" /> + + <!-- trick aria-labelledby --> + <checkbox id="rememberHistoryDays" + label="Remember " + aria-labelledby="rememberHistoryDays historyDays rememberAfter"/> + <textbox id="historyDays" type="number" size="3" 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"/> + + <!-- nsIDOMXULLabeledControlElement --> + <button id="btn_nsIDOMXULLabeledControlElement" + label="labeled element"/> + + <!-- nsIDOMXULSelectControlItemElement --> + <listbox> + <listitem id="li_nsIDOMXULSelectControlItemElement" + label="select control item"/> + </listbox> + + <!-- 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"/> + </hbox> + + <!-- label element, anonymous content --> + <box id="box_label_anon1" + class="first" + role="group"/> + + <box id="box_label_anon2" + class="second" + role="group"/> + + <!-- tooltiptext --> + <box id="box_tooltiptext" + role="group" + tooltiptext="tooltiptext label"/> + + <!-- the name from @title of toolbaritem --> + <toolbar> + <toolbaritem title="ooospspss"> + <textbox id="toolbaritem_textbox" + flex="1" + type="autocomplete" + enablehistory="true"> + <hbox role="button" id="toolbaritem_hboxbutton"> + <description value="button"/> + </hbox> + </textbox> + </toolbaritem> + </toolbar> + + <!-- name from children --> + <box id="box_children" role="button">14</box> + + <!-- 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 label or sub tree --> + <groupbox id="groupbox"> + <caption label="Some caption" /> + <checkbox label="some checkbox label" /> + </groupbox> + <groupbox id="groupbox2"> + <caption><label>Some caption</label></caption> + <checkbox label="some checkbox label" /> + </groupbox> + </vbox> + + <!-- bug 441991; create name from other menuitem label listitem's own label --> + <hbox> + <listbox> + <listitem id="li_labelledby" + label="The moment the event starts" + aria-labelledby="menuitem-DISPLAY li_labelledby"/> + </listbox> + <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 000000000..773e63731 --- /dev/null +++ b/accessible/tests/mochitest/name/test_link.html @@ -0,0 +1,89 @@ +<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 type="application/javascript" + 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 000000000..073b70dd2 --- /dev/null +++ b/accessible/tests/mochitest/name/test_list.html @@ -0,0 +1,89 @@ +<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 type="application/javascript" + 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"); + + // change list style type + var list = getNode("list"); + list.setAttribute("style", "list-style-type: disc;"); + getComputedStyle(list, "").color; // make style processing sync + + testName("li_start", kDiscBulletText + "list start"); + testName("li_end", kDiscBulletText + "list end"); + } + + this.getID = function bulletUpdate_getID() + { + return "Update bullet of list items"; + } + } + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + gQueue.push(new bulletUpdate()); + 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 000000000..7b478e0ba --- /dev/null +++ b/accessible/tests/mochitest/name/test_markup.html @@ -0,0 +1,60 @@ +<html> + +<head> + <title>nsIAccessible::name calculation for elements</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 000000000..81dc0481e --- /dev/null +++ b/accessible/tests/mochitest/name/test_svg.html @@ -0,0 +1,55 @@ +<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 type="application/javascript" + 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_toolbaritem.xul b/accessible/tests/mochitest/name/test_toolbaritem.xul new file mode 100644 index 000000000..8968964a9 --- /dev/null +++ b/accessible/tests/mochitest/name/test_toolbaritem.xul @@ -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"?> +<?xml-stylesheet href="general.css" + type="text/css"?> + + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessibility Name Calculating Test."> + + <script type="application/javascript" + 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[ + var gQueue = null; + function doTest() { + let ids = []; + for (let item of ["button", "textbox"]) { + ids.push(item + "withtooltip"); + ids.push(item + "withouttooltip"); + ids.push("nested" + item + "withtooltip"); + ids.push("nested" + item + "withouttooltip"); + } + + for (let id of ids) { + if (id.endsWith("withtooltip")) { + testName(id, id, id + " should have individual name from its tooltip - "); + } else { + testName(id, "Toolbaritem title", id + " should have toolbaritem's title for a name - "); + } + } + 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=1216478" + title="Items with tooltips inside items with a label should use their own tooltip as an accessible name, not the ancestor's label"> + Mozilla Bug 1216478 + </a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + <toolbox> + <toolbar> + <toolbaritem title="Toolbaritem title"> + <toolbarbutton id="buttonwithtooltip" tooltiptext="buttonwithtooltip"/> + <toolbarbutton id="buttonwithouttooltip"/> + <textbox id="textboxwithtooltip" tooltiptext="textboxwithtooltip"/> + <textbox id="textboxwithouttooltip"/> + <vbox> + <toolbarbutton id="nestedbuttonwithtooltip" tooltiptext="nestedbuttonwithtooltip"/> + <toolbarbutton id="nestedbuttonwithouttooltip"/> + <textbox id="nestedtextboxwithtooltip" tooltiptext="nestedtextboxwithtooltip"/> + <textbox id="nestedtextboxwithouttooltip"/> + </vbox> + </toolbaritem> + </toolbar> + </toolbox> + + + </vbox> <!-- close tests area --> + </hbox> <!-- close main area --> +</window> diff --git a/accessible/tests/mochitest/name/test_tree.xul b/accessible/tests/mochitest/name/test_tree.xul new file mode 100644 index 000000000..fcb50d151 --- /dev/null +++ b/accessible/tests/mochitest/name/test_tree.xul @@ -0,0 +1,211 @@ +<?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="general.css" + type="text/css"?> + + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessibility Name Calculating Test."> + + <script type="application/javascript" + 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() + { + var 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 000000000..7bb1d81ac --- /dev/null +++ b/accessible/tests/mochitest/pivot.js @@ -0,0 +1,551 @@ +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +//////////////////////////////////////////////////////////////////////////////// +// Constants + +const PREFILTER_INVISIBLE = nsIAccessibleTraversalRule.PREFILTER_INVISIBLE; +const PREFILTER_ARIA_HIDDEN = nsIAccessibleTraversalRule.PREFILTER_ARIA_HIDDEN; +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 CHAR_BOUNDARY = nsIAccessiblePivot.CHAR_BOUNDARY; +const WORD_BOUNDARY = nsIAccessiblePivot.WORD_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: function(aRules) + { + aRules.value = [ROLE_HEADING]; + return aRules.value.length; + }, + + preFilter: PREFILTER_INVISIBLE, + + match: function(aAccessible) + { + return FILTER_MATCH; + }, + + QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule]) +} + +/** + * Traversal rule for all focusable nodes or leafs. + */ +var ObjectTraversalRule = +{ + getMatchRoles: function(aRules) + { + aRules.value = []; + return 0; + }, + + preFilter: PREFILTER_INVISIBLE | PREFILTER_ARIA_HIDDEN | PREFILTER_TRANSPARENT, + + match: function(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_STATICTEXT && aAccessible.name.trim()) + rv = FILTER_MATCH; + + return rv; + }, + + QueryInterface: XPCOMUtils.generateQI([nsIAccessibleTraversalRule]) +}; + +//////////////////////////////////////////////////////////////////////////////// +// Virtual state invokers and checkers + +/** + * A checker for virtual cursor changed events. + */ +function VCChangedChecker(aDocAcc, aIdOrNameOrAcc, aTextOffsets, aPivotMoveMethod, + aIsFromUserInput) +{ + 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; + }; + + 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 " + aIdOrNameOrAcc + ", got '" + + prettyName(position)); + + SimpleTest.is(aEvent.isFromUserInput, aIsFromUserInput, + "Expected user input is " + aIsFromUserInput + '\n'); + + if (aTextOffsets) { + SimpleTest.is(aDocAcc.virtualCursor.startOffset, aTextOffsets[0], + "wrong start offset"); + SimpleTest.is(aDocAcc.virtualCursor.endOffset, aTextOffsets[1], + "wrong end offset"); + } + + 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_TEXT, + 'moveNextByText': nsIAccessiblePivot.REASON_TEXT, + 'movePreviousByText': nsIAccessiblePivot.REASON_TEXT, + '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) +{ + 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) +{ + 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 ? true : false); + 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) + ]; + } 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) +{ + 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 (var i = 1; i < aSequence.length; i++) { + var 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 (var i = aSequence.length-2; i >= 0; i--) { + var 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.parentNode.removeChild(aPosNode); + }; + + 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.parentNode.removeChild(aRootNode); + }; + + 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 000000000..8add46094 --- /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 000000000..a456f2dfc --- /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 000000000..aba87bbd8 --- /dev/null +++ b/accessible/tests/mochitest/pivot/doc_virtualcursor_text.html @@ -0,0 +1,29 @@ +<!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> + <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 000000000..2fb339964 --- /dev/null +++ b/accessible/tests/mochitest/pivot/test_virtualcursor.html @@ -0,0 +1,129 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + <script type="application/javascript" + 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-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)); + + // Put cursor in an ignored subtree + // set isFromUserInput to false, just to test.. + gQueue.push(new setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById("hidden-link")), + false)); + // Next item shoud be outside of that subtree + gQueue.push(new setVCPosInvoker(docAcc, "moveNext", ObjectTraversalRule, "An ")); + + 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 000000000..1c11094fc --- /dev/null +++ b/accessible/tests/mochitest/pivot/test_virtualcursor_text.html @@ -0,0 +1,241 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + <script type="application/javascript" + 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 setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('s1-link-1')))); + 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 setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('section-2')))); + 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 setVCPosInvoker(docAcc, null, null, + getAccessible(doc.getElementById('start-block')))); + gQueue.push(new setVCTextInvoker(docAcc, 'movePreviousByText', WORD_BOUNDARY, null, false)); + + 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/relations.js b/accessible/tests/mochitest/relations.js new file mode 100644 index 000000000..0d9aae2b1 --- /dev/null +++ b/accessible/tests/mochitest/relations.js @@ -0,0 +1,192 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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; + +//////////////////////////////////////////////////////////////////////////////// +// 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 (var 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 (var idx = 0; idx < targets.length; idx++) { + var isFound = false; + var enumerate = actualTargets.enumerate(); + while (enumerate.hasMoreElements()) { + var relatedAcc = enumerate.getNext().QueryInterface(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. + var enumerate = actualTargets.enumerate(); + while (enumerate.hasMoreElements()) { + var relatedAcc = enumerate.getNext().QueryInterface(nsIAccessible); + for (var 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); + var relDescrStart = getRelationErrorMsg(aIdentifier, aRelType, true); + + 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 (var 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 (var idx = 0; idx < targets.length; idx++) { + var notFound = true; + var enumerate = actualTargets.enumerate(); + while (enumerate.hasMoreElements()) { + var relatedAcc = enumerate.getNext().QueryInterface(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; + + 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 000000000..a2da0cf2e --- /dev/null +++ b/accessible/tests/mochitest/relations/a11y.ini @@ -0,0 +1,12 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_bindings.xhtml] +[test_embeds.xul] +[test_general.html] +[test_general.xul] +[test_tabbrowser.xul] +[test_tree.xul] +[test_ui_modalprompt.html] +[test_update.html] diff --git a/accessible/tests/mochitest/relations/test_bindings.xhtml b/accessible/tests/mochitest/relations/test_bindings.xhtml new file mode 100644 index 000000000..65a7a0875 --- /dev/null +++ b/accessible/tests/mochitest/relations/test_bindings.xhtml @@ -0,0 +1,103 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> + +<head> + <title>Accessible relations for bindings</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style> + .button { + -moz-binding: url('#custombutton'); + } + + .button2 { + -moz-binding: url('#custombutton2'); + } + </style> + + <bindings xmlns="http://www.mozilla.org/xbl"> + <binding id="custombutton"> + <content aria-labelledby="button.label label"> + <label xmlns="http://www.w3.org/1999/xhtml" anonid="button.label"> + anon label + </label> + <button xmlns="http://www.w3.org/1999/xhtml" anonid="button.button" + aria-labelledby="button.label label"> + a button + </button> + <div xmlns="http://www.w3.org/1999/xhtml" + anonid="button.button2" class="button2" + aria-labelledby="button.label"></div> + <div xmlns="http://www.w3.org/1999/xhtml" + anonid="button.button3" class="button2"></div> + </content> + </binding> + <binding id="custombutton2"> + <content aria-labelledby="button2.label"> + <label xmlns="http://www.w3.org/1999/xhtml" anonid="button2.label"> + nested anon label + </label> + </content> + </binding> + </bindings> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../relations.js"></script> + + <script type="application/javascript"> + function doTests() + { + var button = document.getElementById("button"); + var anonLabel = document. + getAnonymousElementByAttribute(button, "anonid", "button.label"); + var anonButton = document. + getAnonymousElementByAttribute(button, "anonid", "button.button"); + var anonButton2 = document. + getAnonymousElementByAttribute(button, "anonid", "button.button2"); + var anonButton3 = document. + getAnonymousElementByAttribute(button, "anonid", "button.button3"); + var anonAnonLabel = document. + getAnonymousElementByAttribute(anonButton3, "anonid", "button2.label"); + + testRelation("label", RELATION_LABEL_FOR, button); + testRelation(anonLabel, RELATION_LABEL_FOR, [button, anonButton, anonButton2]); + testRelation(button, RELATION_LABELLED_BY, [anonLabel, "label"]); + testRelation(anonButton, RELATION_LABELLED_BY, anonLabel); + testRelation(anonButton2, RELATION_LABELLED_BY, anonLabel); + testRelation(anonButton3, RELATION_LABELLED_BY, anonAnonLabel); + testRelation(anonAnonLabel, RELATION_LABEL_FOR, anonButton3); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTests); + </script> +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=421242" + title="Allow relations in anonymous content for binding parent"> + Mozilla Bug 421242 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + <div id="eventdump"></div> + + <label id="label">explicit label</label> + <div id="button" class="button"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/relations/test_embeds.xul b/accessible/tests/mochitest/relations/test_embeds.xul new file mode 100644 index 000000000..0cb6d6c65 --- /dev/null +++ b/accessible/tests/mochitest/relations/test_embeds.xul @@ -0,0 +1,122 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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(aURI); + } + + 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 loadOneTab(aURI) + { + this.invoke = function loadOneTab_invoke() + { + tabBrowser().loadOneTab(aURI, null, null, null, false); + } + + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument) + ]; + + this.finalCheck = function loadURI_finalCheck() + { + testRelation(browserDocument(), RELATION_EMBEDS, + getAccessible(currentTabDocument())); + } + + this.getID = function loadOneTab_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:about")); + gQueue.push(new loadOneTab("about:mozilla")); + + gQueue.onFinish = function() + { + disableLogging(); + closeBrowserWindow(); + } + gQueue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTests, "about:"); + ]]> + </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 000000000..bcb1d282a --- /dev/null +++ b/accessible/tests/mochitest/relations/test_general.html @@ -0,0 +1,406 @@ +<html> + +<head> + <title>nsIAccessible::getAccessibleRelated() tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 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"); + + // error + testRelation("has_error", RELATION_ERRORMSG, "error"); + testRelation("error", RELATION_ERRORMSG_FOR, "has_error"); + + // finish test + SimpleTest.finish(); + } + + disableLogging(); // from test_embeds.xul + 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="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> + + <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_error" aria-errormessage="error"><section id="error"></section> +</body> +</html> diff --git a/accessible/tests/mochitest/relations/test_general.xul b/accessible/tests/mochitest/relations/test_general.xul new file mode 100644 index 000000000..23bf7276b --- /dev/null +++ b/accessible/tests/mochitest/relations/test_general.xul @@ -0,0 +1,238 @@ +<?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::getAccessibleRelated() 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="../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"]); + + // 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"); + + // 'default button' relation + testRelation("textbox", RELATION_DEFAULT_BUTTON, "submit"); + + // 'labelled by'/'label for' relation for xul:goupbox and xul:label of + // xul:caption + 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"/> + + <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> + + <textbox id="textbox"/> + <button id="submit" default="true" label="Default"/> + + <groupbox id="groupbox"> + <caption label="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_tabbrowser.xul b/accessible/tests/mochitest/relations/test_tabbrowser.xul new file mode 100644 index 000000000..8fd340fec --- /dev/null +++ b/accessible/tests/mochitest/relations/test_tabbrowser.xul @@ -0,0 +1,103 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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:", "about:mozilla"]; + tabBrowser().loadTabs(docURIs, false, true); + } + + this.finalCheck = function testTabRelations_finalCheck(aEvent) + { + //////////////////////////////////////////////////////////////////////// + // 'labelled by'/'label for' relations for xul:tab and xul:tabpanel + + var tabs = tabBrowser().tabContainer.childNodes; + var panels = tabBrowser().mTabBox.tabpanels.childNodes; + + 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.xul b/accessible/tests/mochitest/relations/test_tree.xul new file mode 100644 index 000000000..300fa5bc3 --- /dev/null +++ b/accessible/tests/mochitest/relations/test_tree.xul @@ -0,0 +1,106 @@ +<?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 type="application/javascript" + 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 000000000..21918b33c --- /dev/null +++ b/accessible/tests/mochitest/relations/test_ui_modalprompt.html @@ -0,0 +1,107 @@ +<html> + +<head> + <title>Modal prompts</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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"> + function hasTabModalPrompts() { + try { + return SpecialPowers.getBoolPref("prompts.tab_modal.enabled"); + } catch (ex) { + return false; + } + } + + function showAlert() + { + this.eventSeq = [ + { + type: EVENT_SHOW, + match: function(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) + { + var dialog = aEvent.accessible.DOMNode; + var info = dialog.ui.infoBody; + testRelation(info, RELATION_DESCRIPTION_FOR, dialog); + testRelation(dialog, RELATION_DESCRIBED_BY, info); + } + + this.getID = function showAlert_getID() + { + return "show alert"; + } + } + + //gA11yEventDumpToConsole = true; // debug + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + gQueue.push(new showAlert()); + gQueue.onFinish = function() + { + synthesizeKey("VK_RETURN", {}, browserWindow()); + closeBrowserWindow(); + } + gQueue.invoke(); // will call SimpleTest.finish() + } + + if (!hasTabModalPrompts()) { + todo(false, "Test disabled when tab modal prompts are not enabled."); + } else { + 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 000000000..a04831427 --- /dev/null +++ b/accessible/tests/mochitest/relations/test_update.html @@ -0,0 +1,225 @@ +<html> + +<head> + <title>Test updating of accessible relations</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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.style.overflow = "visible"; + } + + 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 000000000..b76907f78 --- /dev/null +++ b/accessible/tests/mochitest/role.js @@ -0,0 +1,178 @@ +//////////////////////////////////////////////////////////////////////////////// +// Role constants + +const ROLE_ALERT = nsIAccessibleRole.ROLE_ALERT; +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_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_COMBOBOX = nsIAccessibleRole.ROLE_COMBOBOX; +const ROLE_COMBOBOX_LIST = nsIAccessibleRole.ROLE_COMBOBOX_LIST; +const ROLE_COMBOBOX_OPTION = nsIAccessibleRole.ROLE_COMBOBOX_OPTION; +const ROLE_COLUMNHEADER = nsIAccessibleRole.ROLE_COLUMNHEADER; +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_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_FLAT_EQUATION = nsIAccessibleRole.ROLE_FLAT_EQUATION; +const ROLE_FORM = nsIAccessibleRole.ROLE_FORM; +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_LINK = nsIAccessibleRole.ROLE_LINK; +const ROLE_LIST = nsIAccessibleRole.ROLE_LIST; +const ROLE_LISTBOX = nsIAccessibleRole.ROLE_LISTBOX; +const ROLE_LISTITEM = nsIAccessibleRole.ROLE_LISTITEM; +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_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_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_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_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 000000000..295fd34e4 --- /dev/null +++ b/accessible/tests/mochitest/role/a11y.ini @@ -0,0 +1,10 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/moz.png + +[test_aria.html] +[test_aria.xul] +[test_general.html] +[test_general.xul] +[test_svg.html] diff --git a/accessible/tests/mochitest/role/test_aria.html b/accessible/tests/mochitest/role/test_aria.html new file mode 100644 index 000000000..22021fa57 --- /dev/null +++ b/accessible/tests/mochitest/role/test_aria.html @@ -0,0 +1,345 @@ +<!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 type="application/javascript" + 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() + { + // ARIA role map. + testRole("aria_alert", ROLE_ALERT); + testRole("aria_alertdialog", ROLE_DIALOG); + testRole("aria_application", ROLE_APPLICATION); + testRole("aria_article", ROLE_DOCUMENT); + testRole("aria_button", ROLE_PUSHBUTTON); + testRole("aria_checkbox", ROLE_CHECKBUTTON); + testRole("aria_columnheader", ROLE_COLUMNHEADER); + testRole("aria_combobox", ROLE_COMBOBOX); + testRole("aria_dialog", ROLE_DIALOG); + testRole("aria_directory", ROLE_LIST); + testRole("aria_document", ROLE_DOCUMENT); + testRole("aria_form", ROLE_FORM); + testRole("aria_feed", ROLE_GROUPING); + testRole("aria_grid", ROLE_TABLE); + testRole("aria_gridcell", ROLE_GRID_CELL); + testRole("aria_group", ROLE_GROUPING); + testRole("aria_heading", ROLE_HEADING); + testRole("aria_img", ROLE_GRAPHIC); + testRole("aria_link", ROLE_LINK); + testRole("aria_list", ROLE_LIST); + testRole("aria_listbox", ROLE_LISTBOX); + testRole("aria_listitem", ROLE_LISTITEM); + testRole("aria_log", ROLE_TEXT); // weak role + testRole("aria_marquee", ROLE_ANIMATION); + testRole("aria_math", ROLE_FLAT_EQUATION); + testRole("aria_menu", ROLE_MENUPOPUP); + testRole("aria_menubar", ROLE_MENUBAR); + testRole("aria_menuitem", ROLE_MENUITEM); + testRole("aria_menuitemcheckbox", ROLE_CHECK_MENU_ITEM); + testRole("aria_menuitemradio", ROLE_RADIO_MENU_ITEM); + testRole("aria_note", ROLE_NOTE); + testRole("aria_presentation", ROLE_TEXT); // weak role + testRole("aria_progressbar", ROLE_PROGRESSBAR); + testRole("aria_radio", ROLE_RADIOBUTTON); + testRole("aria_radiogroup", ROLE_RADIO_GROUP); + testRole("aria_region", ROLE_PANE); + testRole("aria_row", ROLE_ROW); + testRole("aria_rowheader", ROLE_ROWHEADER); + testRole("aria_scrollbar", ROLE_SCROLLBAR); + testRole("aria_searchbox", ROLE_ENTRY); + testRole("aria_separator", ROLE_SEPARATOR); + testRole("aria_slider", ROLE_SLIDER); + testRole("aria_spinbutton", ROLE_SPINBUTTON); + testRole("aria_status", ROLE_STATUSBAR); + testRole("aria_switch", ROLE_SWITCH); + testRole("aria_tab", ROLE_PAGETAB); + testRole("aria_tablist", ROLE_PAGETABLIST); + testRole("aria_tabpanel", ROLE_PROPERTYPAGE); + testRole("aria_textbox", ROLE_ENTRY); + testRole("aria_timer", ROLE_TEXT); // weak role + testRole("aria_toolbar", ROLE_TOOLBAR); + testRole("aria_tooltip", ROLE_TOOLTIP); + testRole("aria_tree", ROLE_OUTLINE); + testRole("aria_treegrid", ROLE_TREE_TABLE); + testRole("aria_treeitem", 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_DOCUMENT); + testRole("articleform", ROLE_FORM); + + // Test article exposed as document + testRole("testArticle", ROLE_DOCUMENT); + + // weak roles that are forms of "live regions" + testRole("log_table", ROLE_TABLE); + testRole("timer_div", ROLE_SECTION); + + // other roles that are forms of "live regions" + testRole("marquee_h1", ROLE_ANIMATION); + + // strong landmark + testRole("application", ROLE_APPLICATION); + testRole("form", ROLE_FORM); + testRole("application_table", ROLE_APPLICATION); + + // weak landmarks + var weak_landmarks = ["banner", "complementary", "contentinfo", + "main", "navigation", "search"]; + for (l in weak_landmarks) + testRole(weak_landmarks[l], ROLE_SECTION); + + for (l in weak_landmarks) { + var id = weak_landmarks[l] + "_table"; + testRole(id, ROLE_TABLE); + + var accessibleTable = getAccessible(id, [nsIAccessibleTable], null, + DONOTFAIL_IF_NO_INTERFACE); + ok(accessibleTable ? true : false, + "landmarked table should have nsIAccessibleTable"); + + if (accessibleTable) + is(accessibleTable.getCellAt(0,0).firstChild.name, "hi", "no cell"); + } + + ////////////////////////////////////////////////////////////////////////// + // test gEmptyRoleMap + testRole("buttontable_row", ROLE_NOTHING); + testRole("buttontable_cell", ROLE_NOTHING); + + // abstract roles + var abstract_roles = ["composite", "landmark", "structure", "widget", + "window", "input", "range", "select", "section", + "sectionhead"]; + for (a in abstract_roles) + testRole(abstract_roles[a], ROLE_SECTION); + + ////////////////////////////////////////////////////////////////////////// + // roles transformed by ARIA state attributes + testRole("togglebutton", ROLE_TOGGLE_BUTTON); + + ////////////////////////////////////////////////////////////////////////// + // ignore unknown roles, take first known + testRole("unknown_roles", ROLE_PUSHBUTTON); + + ////////////////////////////////////////////////////////////////////////// + // misc roles + testRole("note", ROLE_NOTE); + testRole("scrollbar", ROLE_SCROLLBAR); + testRole("dir", 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); + + // Test equation image + testRole("img_eq", ROLE_FLAT_EQUATION); + + // Test textual equation + testRole("txt_eq", ROLE_FLAT_EQUATION); + + 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> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <span id="aria_alert" role="alert"/> + <span id="aria_alertdialog" role="alertdialog"/> + <span id="aria_application" role="application"/> + <span id="aria_article" role="article"/> + <span id="aria_button" role="button"/> + <span id="aria_checkbox" role="checkbox"/> + <span id="aria_columnheader" role="columnheader"/> + <span id="aria_combobox" role="combobox"/> + <span id="aria_dialog" role="dialog"/> + <span id="aria_directory" role="directory"/> + <span id="aria_document" role="document"/> + <span id="aria_form" role="form"/> + <span id="aria_feed" role="feed"/> + <span id="aria_grid" role="grid"/> + <span id="aria_gridcell" role="gridcell"/> + <span id="aria_group" role="group"/> + <span id="aria_heading" role="heading"/> + <span id="aria_img" role="img"/> + <span id="aria_link" role="link"/> + <span id="aria_list" role="list"/> + <span id="aria_listbox" role="listbox"/> + <span id="aria_listitem" role="listitem"/> + <span id="aria_log" role="log"/> + <span id="aria_marquee" role="marquee"/> + <span id="aria_math" role="math"/> + <span id="aria_menu" role="menu"/> + <span id="aria_menubar" role="menubar"/> + <span id="aria_menuitem" role="menuitem"/> + <span id="aria_menuitemcheckbox" role="menuitemcheckbox"/> + <span id="aria_menuitemradio" role="menuitemradio"/> + <span id="aria_note" role="note"/> + <span id="aria_presentation" role="presentation" tabindex="0"/> + <span id="aria_progressbar" role="progressbar"/> + <span id="aria_radio" role="radio"/> + <span id="aria_radiogroup" role="radiogroup"/> + <span id="aria_region" role="region"/> + <span id="aria_row" role="row"/> + <span id="aria_rowheader" role="rowheader"/> + <span id="aria_scrollbar" role="scrollbar"/> + <span id="aria_searchbox" role="textbox"/> + <span id="aria_separator" role="separator"/> + <span id="aria_slider" role="slider"/> + <span id="aria_spinbutton" role="spinbutton"/> + <span id="aria_status" role="status"/> + <span id="aria_switch" role="switch"/> + <span id="aria_tab" role="tab"/> + <span id="aria_tablist" role="tablist"/> + <span id="aria_tabpanel" role="tabpanel"/> + <span id="aria_textbox" role="textbox"/> + <span id="aria_timer" role="timer"/> + <span id="aria_toolbar" role="toolbar"/> + <span id="aria_tooltip" role="tooltip"/> + <span id="aria_tree" role="tree"/> + <span id="aria_treegrid" role="treegrid"/> + <span id="aria_treeitem" role="treeitem"/> + + <article id="articlemain" role="main">a main area</article> + <article id="articleform" role="form">a form area</article> + + <div id="testArticle" 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> + <h1 role="marquee" id="marquee_h1">marquee</h1> + <div role="timer" id="timer_div">timer</div> + + <!-- landmarks --> + <div role="application" id="application">application</div> + <div role="form" id="form">form</div> + + <!-- weak landmarks --> + <div role="banner" id="banner">banner</div> + <div role="complementary" id="complementary">complementary</div> + <div role="contentinfo" id="contentinfo">contentinfo</div> + <div role="main" id="main">main</div> + <div role="navigation" id="navigation">navigation</div> + <div role="search" id="search">search</div> + + <!-- landmarks are tables --> + <table role="application" id="application_table">application table + <tr><td>hi<td></tr></table> + <table role="banner" id="banner_table">banner table + <tr><td>hi<td></tr></table> + <table role="complementary" id="complementary_table">complementary table + <tr><td>hi<td></tr></table> + <table role="contentinfo" id="contentinfo_table">contentinfo table + <tr><td>hi<td></tr></table> + <table role="main" id="main_table">main table + <tr><td>hi<td></tr></table> + <table role="navigation" id="navigation_table">navigation table + <tr><td>hi<td></tr></table> + <table role="search" id="search_table">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> + + <!-- user agents must not map abstract roles to platform API --> + <!-- test abstract base type roles --> + <div role="composite" id="composite">composite</div> + <div role="landmark" id="landmark">landmark</div> + <div role="roletype" id="roletype">roletype</div> + <div role="structure" id="structure">structure</div> + <div role="widget" id="widget">widget</div> + <div role="window" id="window">window</div> + <!-- test abstract input roles --> + <div role="input" id="input">input</div> + <div role="range" id="range">range</div> + <div role="select" id="select">select</div> + <!-- test abstract structure roles --> + <div role="section" id="section">section</div> + <div role="sectionhead" id="sectionhead">sectionhead</div> + + <!-- roles transformed by ARIA state attributes --> + <button aria-pressed="true" id="togglebutton"> + + <!-- take the first known mappable role --> + <div role="wiggly:worm abc123 button" id="unknown_roles">worm button</div> + + <!-- misc roles --> + <div role="note" id="note">note</div> + <div role="scrollbar" id="scrollbar">scrollbar</div> + + <div id="dir" 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>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> + +</body> +</html> diff --git a/accessible/tests/mochitest/role/test_aria.xul b/accessible/tests/mochitest/role/test_aria.xul new file mode 100644 index 000000000..544034d1f --- /dev/null +++ b/accessible/tests/mochitest/role/test_aria.xul @@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessibility Name Calculating Test."> + + <script type="application/javascript" + 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); + testRole("pressed_menu_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" /> + <button id="pressed_menu_button" aria-pressed="true" label="I am pressed" type="menu-button"> + <menupopup> + <menuitem label="I am a menu item" /> + </menupopup> + </button> + </vbox> + + + </hbox> +</window> + diff --git a/accessible/tests/mochitest/role/test_general.html b/accessible/tests/mochitest/role/test_general.html new file mode 100644 index 000000000..40f522482 --- /dev/null +++ b/accessible/tests/mochitest/role/test_general.html @@ -0,0 +1,186 @@ +<!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 type="application/javascript" + 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_SECTION); + testRole("header", ROLE_HEADER); + testRole("footer", ROLE_FOOTER); + testRole("article", ROLE_DOCUMENT); + testRole("aside", ROLE_NOTE); + testRole("section", ROLE_SECTION); + + // Bug 996821 + // Check that landmark elements get accessibles with styled overflow. + testRole("section_overflow", ROLE_SECTION); + testRole("nav_overflow", ROLE_SECTION); + testRole("header_overflow", ROLE_HEADER); + testRole("aside_overflow", ROLE_NOTE); + testRole("footer_overflow", ROLE_FOOTER); + testRole("article_overflow", ROLE_DOCUMENT); + + // test html:div + testRole("sec", ROLE_SECTION); + + // Test html:blockquote + testRole("quote", ROLE_SECTION); + + // 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_TEXT_CONTAINER. + // After fix for bug 471356, it was temporarily exposed as a paragraph, + // breaking JAWS compatibility. + testRole("data", ROLE_TEXT_CONTAINER); + + // 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); + + 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"/> + </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> +</body> +</html> diff --git a/accessible/tests/mochitest/role/test_general.xul b/accessible/tests/mochitest/role/test_general.xul new file mode 100644 index 000000000..d5982b63f --- /dev/null +++ b/accessible/tests/mochitest/role/test_general.xul @@ -0,0 +1,57 @@ +<?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 type="application/javascript" + 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("statusbarpanel"), + "statusbarpanel shouldn't be accessible."); + testRole("statusbarpanel-iconic", ROLE_PUSHBUTTON); + testRole("statusbarpanel-iconic-text", ROLE_PUSHBUTTON); + 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> + + <statusbarpanel id="statusbarpanel"></statusbarpanel> + <statusbarpanel id="statusbarpanel-iconic" class="statusbarpanel-iconic"></statusbarpanel> + <statusbarpanel id="statusbarpanel-iconic-text" class="statusbarpanel-iconic-text"></statusbarpanel> + <statusbar id="statusbar"></statusbar> + + </hbox> +</window> + diff --git a/accessible/tests/mochitest/role/test_svg.html b/accessible/tests/mochitest/role/test_svg.html new file mode 100644 index 000000000..164861dc8 --- /dev/null +++ b/accessible/tests/mochitest/role/test_svg.html @@ -0,0 +1,70 @@ +<!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 type="application/javascript" + 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("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); + + 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"> + <rect width="300" height="100" id="rect" + style="fill:rgb(0,0,255);stroke-width:1;stroke:rgb(0,0,0)"/> + <circle cx="100" cy="50" r="40" stroke="black" id="circle" + stroke-width="2" fill="red"/> + <ellipse cx="300" cy="80" rx="100" ry="50" id="ellipse" + style="fill:yellow;stroke:purple;stroke-width:2"/> + <line x1="0" y1="0" x2="200" y2="200" id="line" + style="stroke:rgb(255,0,0);stroke-width:2"/> + <polygon points="200,10 250,190 160,210" id="polygon" + style="fill:lime;stroke:purple;stroke-width:1"/> + <polyline points="20,20 40,25 60,40 80,120 120,140 200,180" id="polyline" + style="fill:none;stroke:black;stroke-width:3" /> + <path d="M150 0 L75 200 L225 200 Z" id="path"/> + <image x1="25" y1="80" width="50" height="20" id="image" + xlink:href="../moz.png"/> + </svg> + +</body> +</html> diff --git a/accessible/tests/mochitest/scroll/a11y.ini b/accessible/tests/mochitest/scroll/a11y.ini new file mode 100644 index 000000000..e2e9dfd48 --- /dev/null +++ b/accessible/tests/mochitest/scroll/a11y.ini @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_zoom.html] +[test_zoom_text.html] diff --git a/accessible/tests/mochitest/scroll/test_zoom.html b/accessible/tests/mochitest/scroll/test_zoom.html new file mode 100644 index 000000000..05dd3c444 --- /dev/null +++ b/accessible/tests/mochitest/scroll/test_zoom.html @@ -0,0 +1,148 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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"); + var [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"); + var [x, y] = getPos(anchor); + var wnd = getRootAccessible().DOMDocument.defaultView; + var [screenX, screenY] = CSSToDevicePixels(wnd, wnd.screenX, wnd.screenY); + var scrollToX = docX - screenX, scrollToY = docY - screenY; + + anchor.scrollToPoint(COORDTYPE_WINDOW_RELATIVE, scrollToX, scrollToY); + testPos(anchor, [x, docY]); + + // scrollToPoint relative parent + anchor = getAccessible("bottom3"); + var [x, y] = getPos(anchor); + var [parentX, parentY] = getPos(anchor.parent); + var 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/scroll/test_zoom_text.html b/accessible/tests/mochitest/scroll/test_zoom_text.html new file mode 100644 index 000000000..3ef8fcded --- /dev/null +++ b/accessible/tests/mochitest/scroll/test_zoom_text.html @@ -0,0 +1,158 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test scrollSubstringToPoint when page is zoomed</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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() + { + var tabDocument = currentTabDocument(); + var paragraphNode = tabDocument.getElementById("paragraph"); + var paragraph = getAccessible(paragraphNode, [nsIAccessibleText]); + var offset = 64; // beginning of 4th stanza + + var [x, y] = getPos(paragraph); + var [docX, docY] = getPos(tabDocument); + + paragraph.scrollSubstringToPoint(offset, offset, + COORDTYPE_SCREEN_RELATIVE, docX, docY); + testTextPos(paragraph, offset, [x, docY], COORDTYPE_SCREEN_RELATIVE); + + zoomDocument(tabDocument, 2.0); + + paragraphNode = tabDocument.getElementById("paragraph2"); + paragraph = getAccessible(paragraphNode, [nsIAccessibleText]); + offset = 52; // // beginning of 4th stanza + var [x, y] = getPos(paragraph); + paragraph.scrollSubstringToPoint(offset, offset, + COORDTYPE_SCREEN_RELATIVE, docX, docY); + testTextPos(paragraph, offset, [x, docY], COORDTYPE_SCREEN_RELATIVE); + + closeBrowserWindow(); + SimpleTest.finish(); + } + + var url = "data:text/html,<html>" + + "<meta http-equiv='Content-Type' content='text/html;charset=utf-8' />" + + "<body>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br><hr>" + + "<p id='paragraph'>" + + " Пошел котик на торжок<br>" + + " Купил котик пирожок<br>" + + " Пошел котик на улочку<br>" + + " Купил котик булочку<br>" + + "</p>" + + "<hr><br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br><hr>" + + "<p id='paragraph2'>" + + " Самому ли съесть<br>" + + " Либо Сашеньке снесть<br>" + + " Я и сам укушу<br>" + + " Я и Сашеньке снесу<br>" + + "</p>" + + "<hr><br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><br><br><br><br>" + + "<br><br><br><br><br><br><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>"; + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTest, + url, + { left: 0, top: 0, width: 600, height: 600 }); + </script> + +</head> +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=727942" + title="scrollSubstringToPoint 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> +</body> +</html> diff --git a/accessible/tests/mochitest/selectable.js b/accessible/tests/mochitest/selectable.js new file mode 100644 index 000000000..87bf296f0 --- /dev/null +++ b/accessible/tests/mochitest/selectable.js @@ -0,0 +1,80 @@ +/** + * 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 (var idx = 0; idx < len; idx++) { + var expectedAcc = getAccessible(aSelectedChildren[idx]); + var actualAcc = selectedChildren.queryElementAt(idx, nsIAccessible); + is(actualAcc, expectedAcc, + 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 (var idx = 0; idx < len; idx++) { + var expectedAcc = getAccessible(aSelectedChildren[idx]); + is(acc.getSelectedItemAt(idx), expectedAcc, + 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++) { + if (child == getAccessible(aSelectedChildren[jdx])) { + 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 000000000..4fc11fee8 --- /dev/null +++ b/accessible/tests/mochitest/selectable/a11y.ini @@ -0,0 +1,11 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/treeview.css + +[test_aria.html] +[test_listbox.xul] +[test_menu.xul] +[test_menulist.xul] +[test_select.html] +[test_tree.xul] diff --git a/accessible/tests/mochitest/selectable/test_aria.html b/accessible/tests/mochitest/selectable/test_aria.html new file mode 100644 index 000000000..075871b66 --- /dev/null +++ b/accessible/tests/mochitest/selectable/test_aria.html @@ -0,0 +1,225 @@ +<html> + +<head> + <title>nsIAccessibleSelectable ARIA widgets testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + </style> + + <script type="application/javascript" + 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="../selectable.js"></script> + + <script type="application/javascript"> + function testSelectable(aID, aSelectableChildren) + { + var acc = getAccessible(aID, [nsIAccessibleSelectable]); + + testSelectableSelection(acc, []); + + acc.selectAll(); + testSelectableSelection(acc, aSelectableChildren); + + acc.unselectAll(); + testSelectableSelection(acc, []); + } + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // role="tablist" + + id = "tablist"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for " + id); + + testSelectableSelection(id, [ ]); + + ////////////////////////////////////////////////////////////////////////// + // role="listbox" + + id = "listbox1"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for " + id); + + testSelectableSelection(id, [ ]); + + ////////////////////////////////////////////////////////////////////////// + // role="listbox" aria-multiselectable + + id = "listbox2"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for " + id); + + testSelectableSelection(id, [ ]); + + select = getAccessible(id, [nsIAccessibleSelectable]); + select.addItemToSelection(0); + testSelectableSelection(id, [ "listbox2_item1" ]); + select.removeItemFromSelection(0); + testSelectableSelection(id, [ ]); + select.selectAll(); + testSelectableSelection(id, [ "listbox2_item1", "listbox2_item2" ]); + select.unselectAll(); + testSelectableSelection(id, [ ]); + + ////////////////////////////////////////////////////////////////////////// + // role="grid" + + id = "grid1"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for " + id); + + testSelectableSelection(id, [ ]); + + ////////////////////////////////////////////////////////////////////////// + // role="tree" + + id = "tree1"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for " + id); + + testSelectableSelection(id, [ ]); + + ////////////////////////////////////////////////////////////////////////// + // role="treegrid" + + id = "treegrid1"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for " + id); + + testSelectableSelection(id, [ ]); + + ////////////////////////////////////////////////////////////////////////// + // role="grid" aria-multiselectable, selectable children in subtree + + id = "grid2"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for " + id); + + testSelectable(id, + ["grid2_colhead1", "grid2_colhead2", "grid2_colhead3", + "grid2_rowhead", "grid2_cell1", "grid2_cell2"]); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=530014" + title="ARIA single selectable widget should implement nsIAccessibleSelectable"> + Mozilla Bug 530014 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=566551" + title="ARIA grid and accessible selectable methods shouldn't use GetNextSibling"> + Mozilla Bug 566551 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=590176" + title="add pseudo SelectAccessible interface"> + Mozilla Bug 590176 + </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">tab1</div> + <div role="tab">tab2</div> + </div> + + <div role="listbox" id="listbox1"> + <div role="option">item1</div> + <div role="option">item2</div> + </div> + + <div role="listbox" id="listbox2" aria-multiselectable="true"> + <div role="option" id="listbox2_item1">item1</div> + <div role="option" id="listbox2_item2">item2</div> + </div> + + <div role="grid" id="grid1"> + <div role="row"> + <span role="gridcell">cell</span> + <span role="gridcell">cell</span> + </div> + <div role="row"> + <span role="gridcell">cell</span> + <span role="gridcell">cell</span> + </div> + </div> + + <div role="tree" id="tree1"> + <div role="treeitem"> + item1 + <div role="group"> + <div role="treeitem">item1.1</div> + </div> + </div> + <div>item2</div> + </div> + + <div role="treegrid" id="treegrid1"> + <div role="row" aria-level="1"> + <span role="gridcell">cell</span> + <span role="gridcell">cell</span> + </div> + <div role="row" aria-level="2"> + <span role="gridcell">cell</span> + <span role="gridcell">cell</span> + </div> + <div role="row" aria-level="1"> + <span role="gridcell">cell</span> + <span role="gridcell">cell</span> + </div> + </div> + + <table tabindex="0" border="2" cellspacing="0" id="grid2" role="grid" + aria-multiselectable="true"> + <thead> + <tr> + <th tabindex="-1" role="columnheader" id="grid2_colhead1" + style="width:6em">Entry #</th> + <th tabindex="-1" role="columnheader" id="grid2_colhead2" + style="width:10em">Date</th> + <th tabindex="-1" role="columnheader" id="grid2_colhead3" + style="width:20em">Expense</th> + </tr> + </thead> + <tbody> + <tr> + <td tabindex="-1" role="rowheader" id="grid2_rowhead" + aria-readonly="true">1</td> + <td tabindex="-1" role="gridcell" id="grid2_cell1" + aria-selected="false">03/14/05</td> + <td tabindex="-1" role="gridcell" id="grid2_cell2" + aria-selected="false">Conference Fee</td> + </tr> + </tobdy> + </table> +</body> +</html> diff --git a/accessible/tests/mochitest/selectable/test_listbox.xul b/accessible/tests/mochitest/selectable/test_listbox.xul new file mode 100644 index 000000000..990589257 --- /dev/null +++ b/accessible/tests/mochitest/selectable/test_listbox.xul @@ -0,0 +1,152 @@ +<?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 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" + src="../selectable.js" /> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Test + + //gA11yEventDumpID = "debug"; + + var gQueue = null; + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // single selectable listbox + + var id = "listbox"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for list of " + id); + + var select = getAccessible(id, [nsIAccessibleSelectable]); + testSelectableSelection(select, [ ]); + + select.addItemToSelection(1); + testSelectableSelection(select, [ "lb1_item2" ], "addItemToSelect(1): "); + + select.removeItemFromSelection(1); + testSelectableSelection(select, [ ], + "removeItemFromSelection(1): "); + + todo(select.selectAll() == false, + "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 + + var id = "listbox2"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for list of " + id); + + var 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"> + <listbox id="listbox"> + <listcols> + <listcol flex="1"/> + <listcol flex="1"/> + </listcols> + <listitem id="lb1_item1"> + <listcell label="cell0"/> + <listcell label="cell1"/> + </listitem> + <listitem id="lb1_item2"> + <listcell label="cell3"/> + <listcell label="cell4"/> + </listitem> + </listbox> + + <listbox id="listbox2" seltype="multiple"> + <listcols> + <listcol flex="1"/> + <listcol flex="1"/> + </listcols> + <listitem id="lb2_item1"> + <listcell label="cell0"/> + <listcell label="cell1"/> + </listitem> + <listitem id="lb2_item2"> + <listcell label="cell3"/> + <listcell label="cell4"/> + </listitem> + </listbox> + + <vbox id="debug"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/selectable/test_menu.xul b/accessible/tests/mochitest/selectable/test_menu.xul new file mode 100644 index 000000000..4accedbbd --- /dev/null +++ b/accessible/tests/mochitest/selectable/test_menu.xul @@ -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"?> +<?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 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" + 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.xul b/accessible/tests/mochitest/selectable/test_menulist.xul new file mode 100644 index 000000000..5bd7a26b2 --- /dev/null +++ b/accessible/tests/mochitest/selectable/test_menulist.xul @@ -0,0 +1,96 @@ +<?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 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" + 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_select.html b/accessible/tests/mochitest/selectable/test_select.html new file mode 100644 index 000000000..9369fdfaf --- /dev/null +++ b/accessible/tests/mochitest/selectable/test_select.html @@ -0,0 +1,243 @@ +<html> + +<head> + <title>nsIAccessibleSelectable HTML select testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + </style> + + <script type="application/javascript" + 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="../selectable.js"></script> + + <script type="application/javascript"> + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // select@size="1" 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 2nd item + select.addItemToSelection(1); + testSelectableSelection(select, [ "cb1_item2" ], "addItemToSelection(1): "); + + // unselect 2nd item, 1st item gets selected automatically + select.removeItemFromSelection(1); + testSelectableSelection(select, [ "cb1_item1" ], + "removeItemFromSelection(1): "); + + // doesn't change selection + is(select.selectAll(), false, + "No way to select all items in combobox '" + id + "'"); + testSelectableSelection(select, [ "cb1_item1" ], "selectAll: "); + + // doesn't change selection + select.unselectAll(); + testSelectableSelection(select, [ "cb1_item1" ], "unselectAll: "); + + ////////////////////////////////////////////////////////////////////////// + // select@size="1" with optgroups + + id = "combobox2"; + combobox = getAccessible(id); + comboboxList = combobox.firstChild; + ok(isAccessible(comboboxList, [nsIAccessibleSelectable]), + "No selectable accessible for list of " + id); + + select = getAccessible(comboboxList, [nsIAccessibleSelectable]); + testSelectableSelection(select, [ "cb2_item1" ]); + + select.addItemToSelection(1); + testSelectableSelection(select, [ "cb2_item2" ]); + + select.removeItemFromSelection(1); + testSelectableSelection(select, [ "cb2_item1" ]); + + is(select.selectAll(), false, + "No way to select all items in combobox " + id + "'"); + testSelectableSelection(select, [ "cb2_item1" ]); + + select.unselectAll(); + testSelectableSelection(select, [ "cb2_item1" ]); + + ////////////////////////////////////////////////////////////////////////// + // select@size="4" aka single selectable listbox + + var id = "listbox"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for " + id); + + select = getAccessible(id, [nsIAccessibleSelectable]); + testSelectableSelection(select, [ ]); + + // select 2nd item + select.addItemToSelection(1); + testSelectableSelection(select, [ "lb1_item2" ], "addItemToSelection(1): "); + + // unselect 2nd item, 1st item gets selected automatically + select.removeItemFromSelection(1); + testSelectableSelection(select, [ ], + "removeItemFromSelection(1): "); + + // doesn't change selection + is(select.selectAll(), false, + "No way to select all items in single selectable listbox '" + id + "'"); + testSelectableSelection(select, [ ], "selectAll: "); + + // doesn't change selection + select.unselectAll(); + testSelectableSelection(select, [ ], "unselectAll: "); + + ////////////////////////////////////////////////////////////////////////// + // select@size="4" with optgroups, single selectable + + id = "listbox2"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for " + id); + + select = getAccessible(id, [nsIAccessibleSelectable]); + testSelectableSelection(select, [ ]); + + select.addItemToSelection(1); + testSelectableSelection(select, [ "lb2_item2" ]); + + select.removeItemFromSelection(1); + testSelectableSelection(select, [ ]); + + is(select.selectAll(), false, + "No way to select all items in single selectable listbox " + id + "'"); + testSelectableSelection(select, [ ]); + + select.unselectAll(); + testSelectableSelection(select, [ ]); + + ////////////////////////////////////////////////////////////////////////// + // select@size="4" multiselect aka listbox + + id = "listbox3"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for " + id); + + select = getAccessible(id, [nsIAccessibleSelectable]); + testSelectableSelection(select, [ ]); + + select.addItemToSelection(0); + testSelectableSelection(select, [ "lb3_item1" ], "addItemToSelection: "); + + select.removeItemFromSelection(0); + testSelectableSelection(select, [ ], "removeItemFromSelection: "); + + is(select.selectAll(), true, + "All items in listbox '" + id + "' should be selected"); + testSelectableSelection(select, [ "lb3_item1", "lb3_item2"], + "selectAll: "); + + select.unselectAll(); + testSelectableSelection(select, [ ], "unselectAll: "); + + ////////////////////////////////////////////////////////////////////////// + // select@size="4" multiselect with optgroups + + var id = "listbox4"; + ok(isAccessible(id, [nsIAccessibleSelectable]), + "No selectable accessible for " + id); + + select = getAccessible(id, [nsIAccessibleSelectable]); + testSelectableSelection(select, [ ]); + + select.addItemToSelection(0); + testSelectableSelection(select, [ "lb4_item1" ]); + + select.removeItemFromSelection(0); + testSelectableSelection(select, [ ]); + + is(select.selectAll(), true, + "All items in listbox '" + id + "' should be selected"); + testSelectableSelection(select, [ "lb4_item1", "lb4_item2"]); + + select.unselectAll(); + testSelectableSelection(select, [ ]); + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> + +</head> + +<body> + + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=530014" + title="ARIA single selectable widget should implement nsIAccessibleSelectable"> + Mozilla Bug 530014 + </a><br> + <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> + + <select id="combobox"> + <option id="cb1_item1">option1</option> + <option id="cb1_item2">option2</option> + </select> + + <select id="combobox2"> + <option id="cb2_item1">option1</option> + <optgroup>optgroup + <option id="cb2_item2">option2</option> + </optgroup> + </select> + + <select id="listbox" size="4"> + <option id="lb1_item1">option1</option> + <option id="lb1_item2">option2</option> + </select> + + <select id="listbox2" size="4"> + <option id="lb2_item1">option1</option> + <optgroup>optgroup> + <option id="lb2_item2">option2</option> + </optgroup> + </select> + + <select id="listbox3" size="4" multiple="true"> + <option id="lb3_item1">option1</option> + <option id="lb3_item2">option2</option> + </select> + + <select id="listbox4" size="4" multiple="true"> + <option id="lb4_item1">option1</option> + <optgroup>optgroup> + <option id="lb4_item2">option2</option> + </optgroup> + </select> + +</body> +</html> diff --git a/accessible/tests/mochitest/selectable/test_tree.xul b/accessible/tests/mochitest/selectable/test_tree.xul new file mode 100644 index 000000000..81e082534 --- /dev/null +++ b/accessible/tests/mochitest/selectable/test_tree.xul @@ -0,0 +1,188 @@ +<?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 type="application/javascript" + 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" && seltype != "cell" && seltype != "text") + 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("treecell", new nsTreeTreeView())); + gQueue.push(new statesChecker("treetext", 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="treecell" flex="1" seltype="cell"> + <treecols> + <treecol id="col_cell" flex="1" primary="true" label="column"/> + </treecols> + <treechildren/> + </tree> + + <tree id="treetext" flex="1" seltype="text"> + <treecols> + <treecol id="col_text" 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 000000000..73ec126e3 --- /dev/null +++ b/accessible/tests/mochitest/states.js @@ -0,0 +1,266 @@ +//////////////////////////////////////////////////////////////////////////////// +// Helper functions for accessible states testing. +// +// requires: +// common.js +// role.js +// +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// State constants + +// const STATE_BUSY is defined in common.js +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_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 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, + "Focussed " + 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) { + isState(state & STATE_SELECTABLE, STATE_SELECTABLE, false, + "Selected element must be selectable!"); + } +} + +/** + * Tests an acessible 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 000000000..f07afc3e9 --- /dev/null +++ b/accessible/tests/mochitest/states/a11y.ini @@ -0,0 +1,37 @@ +[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.xul] +[test_aria_imgmap.html] +[test_aria_widgetitems.html] +[test_buttons.html] +[test_controls.html] +[test_controls.xul] +[test_doc.html] +[test_doc_busy.html] +skip-if = os == 'mac' && os_version == '10.6' +[test_docarticle.html] +[test_editablebody.html] +[test_expandable.xul] +[test_frames.html] +[test_inputs.html] +[test_link.html] +[test_popup.xul] +[test_selects.html] +[test_stale.html] +[test_tabs.xul] +[test_textbox.xul] +[test_tree.xul] +[test_visibility.html] +[test_visibility.xul] +skip-if = buildapp == "mulet" diff --git a/accessible/tests/mochitest/states/test_aria.html b/accessible/tests/mochitest/states/test_aria.html new file mode 100644 index 000000000..7d1ecf650 --- /dev/null +++ b/accessible/tests/mochitest/states/test_aria.html @@ -0,0 +1,629 @@ +<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 type="application/javascript" + 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 + + // 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); + + // 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/editable on grid and gridcell + testStates("aria_grid_default", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_grid_default_colheader_readonly", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_grid_default_colheader_inherited", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_grid_default_rowheader_readonly", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_grid_default_rowheader_inherited", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_grid_default_cell_readonly", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_grid_default_cell_inherited", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + + testStates("aria_grid_readonly", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_grid_readonly_colheader_editable", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_grid_readonly_colheader_inherited", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_grid_readonly_rowheader_editable", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_grid_readonly_rowheader_inherited", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_grid_readonly_cell_editable", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_grid_readonly_cell_inherited", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + + // readonly/editable on treegrid and gridcell + testStates("aria_treegrid_default", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_treegrid_default_colheader_readonly", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_treegrid_default_colheader_inherited", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_treegrid_default_rowheader_readonly", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_treegrid_default_rowheader_inherited", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_treegrid_default_cell_readonly", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_treegrid_default_cell_inherited", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + + testStates("aria_treegrid_readonly", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_treegrid_readonly_colheader_editable", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_treegrid_readonly_colheader_inherited", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_treegrid_readonly_rowheader_editable", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_treegrid_readonly_rowheader_inherited", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + testStates("aria_treegrid_readonly_cell_editable", 0, EXT_STATE_EDITABLE, + STATE_READONLY, 0); + testStates("aria_treegrid_readonly_cell_inherited", STATE_READONLY, 0, + 0, EXT_STATE_EDITABLE); + + // 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 weak landmarks + testStates("aria_main_link", STATE_LINKED); + testStates("aria_navigation_link", STATE_LINKED); + testStates("aria_main_anchor", STATE_SELECTABLE); + testStates("aria_navigation_anchor", STATE_SELECTABLE); + + // aria-orientation + testStates("aria_combobox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + 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, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL); + 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); + + 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> + + <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_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> + +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_aria.xul b/accessible/tests/mochitest/states/test_aria.xul new file mode 100644 index 000000000..b15cc7781 --- /dev/null +++ b/accessible/tests/mochitest/states/test_aria.xul @@ -0,0 +1,60 @@ +<?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("pressed_menu_button", STATE_PRESSED | STATE_HASPOPUP, 0, STATE_CHECKABLE);
+
+ 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" />
+ <button id="pressed_menu_button" aria-pressed="true" label="I am pressed" type="menu-button">
+ <menupopup>
+ <menuitem label="I am a menu item" />
+ </menupopup>
+ </button>
+ </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 000000000..1dfe355bd --- /dev/null +++ b/accessible/tests/mochitest/states/test_aria_imgmap.html @@ -0,0 +1,79 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 000000000..81bce4194 --- /dev/null +++ b/accessible/tests/mochitest/states/test_aria_widgetitems.html @@ -0,0 +1,162 @@ +<!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 type="application/javascript" + 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 000000000..52c642ce5 --- /dev/null +++ b/accessible/tests/mochitest/states/test_buttons.html @@ -0,0 +1,85 @@ +<!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 type="application/javascript" + 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 000000000..7369f6d01 --- /dev/null +++ b/accessible/tests/mochitest/states/test_controls.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML control states</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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.xul b/accessible/tests/mochitest/states/test_controls.xul new file mode 100644 index 000000000..83de45256 --- /dev/null +++ b/accessible/tests/mochitest/states/test_controls.xul @@ -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 input control 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" + src="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + function openColorpicker(aID) + { + this.popupNode = getNode(aID).mPicker.parentNode; + this.popup = getAccessible(this.popupNode); + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.popupNode) + ]; + + this.invoke = function openColorpicker_invoke() + { + getNode(aID).showPopup(); + } + + this.finalCheck = function openColorpicker_finalCheck() + { + testStates(this.popup.firstChild, + STATE_FOCUSABLE | STATE_SELECTABLE, 0, + STATE_UNAVAILABLE); + } + + this.getID = function openColorpicker_getID() + { + return "open colorpicker"; + } + } + + 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("colorpicker", STATE_FOCUSABLE | STATE_HASPOPUP, 0, STATE_UNAVAILABLE); + testStates("colorpicker-disabled", STATE_HASPOPUP, 0, STATE_FOCUSABLE); + 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, 0, STATE_UNAVAILABLE); + testStates("listbox-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE); + testStates("listitem-disabledlistbox", STATE_UNAVAILABLE, 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("scale", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE); + testStates("scale-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE); + 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.push(new openColorpicker("colorpicker")); + 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"/> + + <colorpicker id="colorpicker" type="button"/> + <colorpicker id="colorpicker-disabled" type="button" disabled="true"/> + + <menulist id="combobox"> + <menupopup> + <menuitem label="item1"/> + </menupopup> + </menulist> + + <menulist id="combobox-disabled" disabled="true"> + <menupopup> + <menuitem label="item1"/> + </menupopup> + </menulist> + + <listbox id="listbox"> + <listitem id="listitem" label="list item"/> + </listbox> + + <listbox id="listbox-disabled" disabled="true"> + <listitem id="listitem-disabledlistbox" label="list item"/> + </listbox> + + <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> + + <scale id="scale" min="1" max="10"/> + <scale id="scale-disabled" min="1" max="10" disabled="true"/> + + <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 000000000..83edd9dac --- /dev/null +++ b/accessible/tests/mochitest/states/test_doc.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<html> +<head> + <title>states of document</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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","banner"); // no platform mapping + testStates(document, 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); + + document.designMode = "on"; + + testStates(document, 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("p", 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("document", 0, EXT_STATE_EDITABLE, STATE_READONLY); + testStates("editable_document", 0, EXT_STATE_EDITABLE, STATE_READONLY); + + 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> + + <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 000000000..8e1422da1 --- /dev/null +++ b/accessible/tests/mochitest/states/test_doc_busy.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<html> +<head> + <title>states of document</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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; // debugging stuff + + function loadFile() + { + var eventSeq = [ + new stateChangeChecker(STATE_BUSY, false, true, document, null, false, true), + new stateChangeChecker(STATE_BUSY, false, false, document) + ]; + defineScenario(this, eventSeq); // both events were fired + + var unexpectedEventSeq = [ + new stateChangeChecker(STATE_BUSY, false, true, document), + new stateChangeChecker(STATE_BUSY, false, false, document) + ]; + defineScenario(this, [], unexpectedEventSeq); // events were coalesced + + this.invoke = function loadFile_invoke() + { + synthesizeMouse(getNode("link"), 1, 1, {}); + } + + this.getID = function loadFile_getID() + { + return "load file: state busy change events on document"; + } + } + + var gQueue = null; + function doTest() + { + // State busy change event on file loading. + //enableLogging("docload"); // debugging + gQueue = new eventQueue(); + gQueue.push(new loadFile()); + //gQueue.onFinish = function() { disableLogging(); } // debugging + gQueue.invoke(); // Will call 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 000000000..1ac62ee4b --- /dev/null +++ b/accessible/tests/mochitest/states/test_docarticle.html @@ -0,0 +1,80 @@ +<html> +<head> + <title>states of document article</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 000000000..3b1486876 --- /dev/null +++ b/accessible/tests/mochitest/states/test_editablebody.html @@ -0,0 +1,46 @@ +<!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 type="application/javascript" + 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.xul b/accessible/tests/mochitest/states/test_expandable.xul new file mode 100644 index 000000000..1e210a0ef --- /dev/null +++ b/accessible/tests/mochitest/states/test_expandable.xul @@ -0,0 +1,118 @@ +<?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" + title="Expanded state change events tests for comboboxes and autocompletes."> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" /> + + <script 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. + function getBrowser() + { + return { + mCurrentBrowser: { engines: new Array() } + }; + } + var BrowserSearch = { + updateOpenSearchBadge: function() {} + }; + + 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> + + <textbox id="autocomplete" type="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 000000000..cb3bca844 --- /dev/null +++ b/accessible/tests/mochitest/states/test_frames.html @@ -0,0 +1,95 @@ +<html> + +<head> + <title>frame based document testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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() + { + frameDoc = document.getElementById("frame_doc").contentDocument; + frameDocArticle = document.getElementById("frame_doc_article").contentDocument; + frameDocCheckbox = document.getElementById("frame_doc_checkbox").contentDocument; + 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, 0, 0, STATE_READONLY, 0, + "test1: frameDocCheckbox"); + testStates(frameDocTextbox, 0, EXT_STATE_EDITABLE, STATE_READONLY, 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, 0, 0, STATE_READONLY, 0, + "test2: frameDocCheckbox"); + testStates(frameDocTextbox, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0, + "test2: frameDocTextbox"); + + frameDocArticle.designMode = "on"; + testStates(frameDocArticle, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0, + "test3: frameDocArticle"); + + frameDocCheckbox.designMode = "on"; + testStates(frameDocCheckbox, 0, 0, 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 000000000..20c6deaf8 --- /dev/null +++ b/accessible/tests/mochitest/states/test_inputs.html @@ -0,0 +1,271 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML input states</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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("VK_BACK_SPACE", {}); + 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 000000000..3f89f9f7f --- /dev/null +++ b/accessible/tests/mochitest/states/test_link.html @@ -0,0 +1,144 @@ +<html> + +<head> + <title>HTML link states testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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; // debug stuff + + var gLinkWindow = null; + function closeDocChecker() + { + this.__proto__ = new asyncInvokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE); + + this.check = function closeDocChecker_check(aEvent) + { + gLinkWindow = aEvent.accessible.rootDocument.window; + } + + this.match = function closeDocChecker_match(aEvent) + { + // A temporary about:blank document gets loaded before 'example.com' + // document. + return aEvent.DOMNode.URL == "http://www.example.com/"; + } + } + + function clickLink(aID) + { + this.eventSeq = [ + new stateChangeChecker(STATE_TRAVERSED, false, true, "link_traversed"), + new closeDocChecker() + ]; + + this.invoke = function clickLink_invoke() + { + synthesizeMouse(getNode("link_traversed"), 1, 1, { shiftKey: true }); + } + + this.getID = function clickLink_getID() + { + return "link + '" + aID + "' clicked."; + } + } + + var gQueue = null; + 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); + + // a: no traversed state + testStates("link_traversed", 0, 0, STATE_TRAVERSED); + + // a: traversed state + //enableLogging("docload"); // debug stuff + + gQueue = new eventQueue(); + gQueue.push(new clickLink("link_traversed")); + gQueue.onFinish = + function() + { + gLinkWindow.close(); + //disableLogging(); // debug stuff + } + + gQueue.invoke(); // will call SimpleTest.finsih(); + } + + 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> + + <a id="link_traversed" href="http://www.example.com" target="_top">example.com</a> + +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_popup.xul b/accessible/tests/mochitest/states/test_popup.xul new file mode 100644 index 000000000..bc40a8b70 --- /dev/null +++ b/accessible/tests/mochitest/states/test_popup.xul @@ -0,0 +1,55 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL popup attribute test"> + + <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() + { + // 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 000000000..0029fcbe4 --- /dev/null +++ b/accessible/tests/mochitest/states/test_selects.html @@ -0,0 +1,203 @@ +<html> + +<head> + <title>HTML selects accessible states tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 openComboboxNCheckStates(aID) + { + this.combobox = getAccessible(aID); + this.comboboxList = this.combobox.firstChild; + this.comboboxOption = this.comboboxList.firstChild; + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, this.comboboxOption) + ]; + + this.invoke = function openComboboxNCheckStates_invoke() + { + getNode(aID).focus(); + synthesizeKey("VK_DOWN", { altKey: true }); + } + + this.finalCheck = function openComboboxNCheckStates_invoke() + { + // Expanded state on combobox. + testStates(this.combobox, STATE_EXPANDED); + + // Floating state on combobox list. + testStates(this.comboboxList, STATE_FLOATING); + } + + this.getID = function openComboboxNCheckStates_getID() + { + return "open combobox and test states"; + } + } + + //gA11yEventDumpToConsole = true; + + var gQueue = null; + 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, + EXT_STATE_ACTIVE, STATE_FOCUSED, 0); + + var opt2 = comboboxList.lastChild; + testStates(opt2, STATE_SELECTABLE | STATE_FOCUSABLE, 0, STATE_SELECTED, 0, + STATE_FOCUSED, EXT_STATE_ACTIVE); + + // 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); + + // open combobox + gQueue = new eventQueue(); + gQueue.push(new openComboboxNCheckStates("combobox")); + 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=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 000000000..8f85a1995 --- /dev/null +++ b/accessible/tests/mochitest/states/test_stale.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<html> +<head> + <title>Stale state testing</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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"); + 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.xul b/accessible/tests/mochitest/states/test_tabs.xul new file mode 100644 index 000000000..a596e178b --- /dev/null +++ b/accessible/tests/mochitest/states/test_tabs.xul @@ -0,0 +1,70 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL tabbox hierarchy tests"> + + <script type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js" /> + <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[ + //////////////////////////////////////////////////////////////////////////// + // 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.xul b/accessible/tests/mochitest/states/test_textbox.xul new file mode 100644 index 000000000..3daf2abe1 --- /dev/null +++ b/accessible/tests/mochitest/states/test_textbox.xul @@ -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="nsIAccessible XUL textboxes states 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 getInput(aID) + { + return getNode(aID).inputField; + } + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // Ordinary textbox + testStates(getInput("textbox"), + STATE_FOCUSABLE, + EXT_STATE_EDITABLE, + STATE_PROTECTED | STATE_UNAVAILABLE, + EXT_STATE_SUPPORTS_AUTOCOMPLETION, + "ordinary textbox"); + + ////////////////////////////////////////////////////////////////////////// + // Password textbox + testStates(getInput("password"), + STATE_FOCUSABLE | STATE_PROTECTED, + EXT_STATE_EDITABLE, + STATE_UNAVAILABLE, + EXT_STATE_SUPPORTS_AUTOCOMPLETION, + "password textbox"); + + ////////////////////////////////////////////////////////////////////////// + // Textarea + testStates(getInput("textarea"), + STATE_FOCUSABLE, + EXT_STATE_EDITABLE | EXT_STATE_MULTI_LINE, + STATE_PROTECTED | STATE_UNAVAILABLE, + EXT_STATE_SUPPORTS_AUTOCOMPLETION, + "multiline textbox"); + + ////////////////////////////////////////////////////////////////////////// + // Readonly textbox + testStates(getInput("readonly_textbox"), + STATE_FOCUSABLE | STATE_READONLY, + EXT_STATE_EDITABLE, + STATE_PROTECTED | STATE_UNAVAILABLE, + EXT_STATE_SUPPORTS_AUTOCOMPLETION, + "readonly textbox"); + + ////////////////////////////////////////////////////////////////////////// + // Disabled textbox + testStates(getInput("disabled_textbox"), + STATE_UNAVAILABLE, + EXT_STATE_EDITABLE, + STATE_FOCUSABLE | STATE_PROTECTED, + EXT_STATE_SUPPORTS_AUTOCOMPLETION, + "readonly textbox"); + + ////////////////////////////////////////////////////////////////////////// + // Readonly textarea + testStates(getInput("readonly_textarea"), + STATE_FOCUSABLE | STATE_READONLY, + EXT_STATE_EDITABLE | EXT_STATE_MULTI_LINE, + STATE_PROTECTED | STATE_UNAVAILABLE, + EXT_STATE_SUPPORTS_AUTOCOMPLETION, + "readonly multiline textbox"); + + ////////////////////////////////////////////////////////////////////////// + // Disabled textarea + testStates(getInput("disabled_textarea"), + STATE_UNAVAILABLE, + EXT_STATE_EDITABLE| EXT_STATE_MULTI_LINE, + STATE_PROTECTED | STATE_FOCUSABLE, + EXT_STATE_SUPPORTS_AUTOCOMPLETION, + "readonly multiline textbox"); + + ////////////////////////////////////////////////////////////////////////// + // 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"> + <textbox id="textbox"/> + <textbox id="password" type="password"/> + <textbox id="textarea" multiline="true" cols="80" rows="5"/> + + <textbox id="readonly_textbox" readonly="true"/> + <textbox id="disabled_textbox" disabled="true"/> + <textbox id="readonly_textarea" multiline="true" readonly="true" + cols="80" rows="5"/> + <textbox id="disabled_textarea" multiline="true" disabled="true" + cols="80" rows="5"/> + + <textbox id="searchbox" flex="1" type="search" results="historyTree"/> + <textbox id="searchfield" placeholder="Search all add-ons" + type="search" searchbutton="true"/> + </vbox> + </hbox> +</window> diff --git a/accessible/tests/mochitest/states/test_tree.xul b/accessible/tests/mochitest/states/test_tree.xul new file mode 100644 index 000000000..878a8d25b --- /dev/null +++ b/accessible/tests/mochitest/states/test_tree.xul @@ -0,0 +1,152 @@ +<?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 type="application/javascript" + 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(); + } + + if (MAC && (navigator.userAgent.indexOf("Mac OS X 10.6") != -1)) { + todo(false, + "Re-enable on Mac OS 10.6 after fixing bug 845095 - intermittent orange"); + } else { + 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 000000000..a2e4a34e6 --- /dev/null +++ b/accessible/tests/mochitest/states/test_visibility.html @@ -0,0 +1,175 @@ +<html> +<head> + <title>visibility state testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js"></script> + <script type="application/javascript" + 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="../browser.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function loadURIInvoker(aURI, aFunc) + { + this.invoke = function loadURIInvoker_invoke() + { + tabBrowser().loadURI(aURI); + } + + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument) + ]; + + this.finalCheck = function loadURIInvoker_finalCheck() + { + aFunc.call(); + } + + this.getID = function loadURIInvoker_getID() + { + return "load uri " + aURI; + } + } + + function addTabInvoker(aURL, aFunc) + { + this.eventSeq = [ + new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1) + ]; + + this.invoke = function addTabInvoker_invoke() + { + tabBrowser().loadOneTab(aURL, null, "", null, false); + } + + this.finalCheck = function addTabInvoker_finalCheck() + { + aFunc.call(); + } + + this.getID = function addTabInvoker_getID() + { + return "add tab: " + aURL; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Tests + + function testBackgroundTab() + { + // Accessibles in background tab should have offscreen state and no + // invisible state. + var tabDoc = tabDocumentAt(0); + var input = getAccessible(tabDoc.getElementById("input")); + testStates(input, STATE_OFFSCREEN, 0, STATE_INVISIBLE); + } + + function testScrolledOff() + { + var tabDoc = tabDocumentAt(1); + + // scrolled off + input = getAccessible(tabDoc.getElementById("input_scrolledoff")); + testStates(input, STATE_OFFSCREEN, 0, STATE_INVISIBLE); + + // scrolled off item (twice) + var lastLiNode = tabDoc.getElementById("li_last"); + var lastLi = getAccessible(lastLiNode); + testStates(lastLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE); + + // scroll into view the item + lastLiNode.scrollIntoView(true); + testStates(lastLi, 0, 0, STATE_OFFSCREEN | STATE_INVISIBLE); + + // first item is scrolled off now (testcase for bug 768786) + var firstLi = getAccessible(tabDoc.getElementById("li_first")); + testStates(firstLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE); + } + + var gInputDocURI = "data:text/html,<html><body>"; + gInputDocURI += "<input id='input'></body></html>"; + + var gDocURI = "data:text/html,<html><body>"; + gDocURI += "<div style='border:2px solid blue; width: 500px; height: 600px;'></div>"; + gDocURI += "<input id='input_scrolledoff'>"; + gDocURI += "<ul style='border:2px solid red; width: 100px; height: 50px; overflow: auto;'>"; + gDocURI += " <li id='li_first'>item1</li><li>item2</li><li>item3</li>"; + gDocURI += " <li>item4</li><li>item5</li><li id='li_last'>item6</li>"; + gDocURI += "</ul>"; + gDocURI += "</body></html>"; + + 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); + + gQueue = new eventQueue(); + + gQueue.push(new addTabInvoker("about:blank", testBackgroundTab)); + gQueue.push(new loadURIInvoker(gDocURI, testScrolledOff)); + + gQueue.onFinish = function() { closeBrowserWindow(); } + gQueue.invoke(); // Will call SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTests, gInputDocURI, { width: 600, height: 600 }); + </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> + + </div> +</body> +</html> diff --git a/accessible/tests/mochitest/states/test_visibility.xul b/accessible/tests/mochitest/states/test_visibility.xul new file mode 100644 index 000000000..8b2ac990c --- /dev/null +++ b/accessible/tests/mochitest/states/test_visibility.xul @@ -0,0 +1,152 @@ +<?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 elements visibility states testing"> + + <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" + 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> + + <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: 5em;"> + <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/states/z_frames.html b/accessible/tests/mochitest/states/z_frames.html new file mode 100644 index 000000000..819adee63 --- /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 000000000..a7a69b4da --- /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 000000000..799764424 --- /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 000000000..0f4e1b9d6 --- /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 000000000..90d756afe --- /dev/null +++ b/accessible/tests/mochitest/states/z_frames_update.html @@ -0,0 +1,22 @@ +<html> +<head> +<script> +function replaceBody() +{ + var accService = Components.classes["@mozilla.org/accessibilityService;1"]. + getService(Components.interfaces.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 000000000..e171594d6 --- /dev/null +++ b/accessible/tests/mochitest/table.js @@ -0,0 +1,778 @@ +/** + * This file provides set of helper functions to test nsIAccessibleTable + * interface. + * + * Required: + * common.js + * role.js + * 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 (var idx = 0; idx < colsCount; idx++) { + var headerCellObj = { + role: ROLE_COLUMNHEADER + }; + headersObj.children.push(headerCellObj); + } + + if (aColHeaderType == kTreeColumnHeader) { + var columnPickerObj = { + role: ROLE_PUSHBUTTON + }; + + headersObj.children.push(columnPickerObj); + } + + tableObj.children.push(headersObj); + } + + // rows and cells accessibles + for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + var rowObj = { + role: aRowRoles ? aRowRoles[rowIdx] : ROLE_ROW, + children: [] + }; + + for (var colIdx = 0; colIdx < colsCount; colIdx++) { + var 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: 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 (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (var colIdx = 0; colIdx < colsCount; colIdx++) { + var celltype = aCellsArray[rowIdx][colIdx]; + if (celltype & kOrigin) { + + // table getRowExtentAt + var rowExtent = table.getRowExtentAt(rowIdx, colIdx); + for (var idx = rowIdx + 1; + idx < rowCount && (aCellsArray[idx][colIdx] & kRowSpanned); + idx++); + + 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); + for (var idx = colIdx + 1; + idx < colsCount && (aCellsArray[rowIdx][idx] & kColSpanned); + idx++); + + 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 = new Array(); + + // isColumnSelected test + for (var colIdx = 0; colIdx < colsCount; colIdx++) { + var isColSelected = true; + for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + if (aCellsArray[rowIdx][colIdx] == false || + 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 actualSelColsCountObj = { value: null }; + var actualSelCols = acc.getSelectedColumnIndices(actualSelColsCountObj); + + var actualSelColsCount = actualSelColsCountObj.value; + is (actualSelColsCount, selCols.length, + msg + "Wrong count of selected columns for " + prettyName(aIdentifier) + + "from getSelectedColumns."); + + for (var i = 0; i < actualSelColsCount; i++) { + is (actualSelCols[i], selCols[i], + msg + "Column at index " + selCols[i] + " should be selected."); + } + + // Rows selection tests. + var selRows = new Array(); + + // isRowSelected test + var selrowCount = 0; + for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + var isRowSelected = true; + for (var colIdx = 0; colIdx < colsCount; colIdx++) { + if (aCellsArray[rowIdx][colIdx] == false || + 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 actualSelrowCountObj = { value: null }; + var actualSelRows = acc.getSelectedRowIndices(actualSelrowCountObj); + + var actualSelrowCount = actualSelrowCountObj.value; + is (actualSelrowCount, selRows.length, + msg + "Wrong count of selected rows for " + prettyName(aIdentifier) + + "from getSelectedRows."); + + for (var i = 0; i < actualSelrowCount; i++) { + is (actualSelRows[i], selRows[i], + msg + "Row at index " + selRows[i] + " should be selected."); + } + + // Cells selection tests. + var selCells = new Array(); + + // isCellSelected test + for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (var colIdx = 0; colIdx < colsCount; colIdx++) { + if (aCellsArray[rowIdx][colIdx] & kSpanned) + continue; + + var isSelected = aCellsArray[rowIdx][colIdx] == true; + 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 actualSelCellsCountObj = { value: null }; + var actualSelCells = acc.getSelectedCellIndices(actualSelCellsCountObj); + + var actualSelCellsCount = actualSelCellsCountObj.value; + is(actualSelCellsCount, selCells.length, + msg + "Wrong count of selected cells for " + prettyName(aIdentifier) + + "from getSelectedCells."); + + for (var 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 (var i = 0; i < actualSelCellsCount; i++) { + var actualSelCellAccessible = + actualSelCellsArray.queryElementAt(i, nsIAccessibleTableCell); + + var colIdx = acc.getColumnIndexAt(selCells[i]); + var rowIdx = acc.getRowIndexAt(selCells[i]); + var expectedSelCellAccessible = acc.getCellAt(rowIdx, colIdx); + + ok(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 (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (var 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 == true) + testStates(cell, STATE_SELECTED); + else + testStates(cell, STATE_SELECTABLE, 0, STATE_SELECTED); + } + } +} + +/** + * Test unselectColumn method of accessible table. + */ +function testUnselectTableColumn(aIdentifier, aColIdx, aCellsArray) +{ + var acc = getAccessible(aIdentifier, [nsIAccessibleTable]); + if (!acc) + return; + + var rowCount = aCellsArray.length; + for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + var cellState = aCellsArray[rowIdx][aColIdx]; + // Unselect origin cell. + var [origRowIdx, origColIdx] = + getOrigRowAndColumn(aCellsArray, rowIdx, aColIdx); + aCellsArray[origRowIdx][origColIdx] = false; + } + + acc.unselectColumn(aColIdx); + testTableSelection(aIdentifier, aCellsArray, + "Unselect " + aColIdx + " column: "); +} + +/** + * Test selectColumn method of accessible table. + */ +function testSelectTableColumn(aIdentifier, aColIdx, aCellsArray) +{ + var acc = getAccessible(aIdentifier, [nsIAccessibleTable]); + if (!acc) + return; + + var rowCount = aCellsArray.length; + var colsCount = aCellsArray[0].length; + + for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (var colIdx = 0; colIdx < colsCount; colIdx++) { + var cellState = aCellsArray[rowIdx][colIdx]; + + if (colIdx == aColIdx) { // select target column + if (!(cellState & kSpanned)) { + // Select the cell if it is origin. + aCellsArray[rowIdx][colIdx] = true; + + } else { + // If the cell is spanned then search origin cell and select it. + var [origRowIdx, origColIdx] = getOrigRowAndColumn(aCellsArray, + rowIdx, colIdx); + aCellsArray[origRowIdx][origColIdx] = true; + } + + } else if (!(cellState & kSpanned)) { // unselect other columns + if (colIdx > aColIdx) { + // Unselect the cell if traversed column index is greater than column + // index of target cell. + aCellsArray[rowIdx][colIdx] = false; + + } else if (!(aCellsArray[rowIdx][aColIdx] & kColSpanned)) { + // Unselect the cell if the target cell is not row spanned. + aCellsArray[rowIdx][colIdx] = false; + + } else { + // Unselect the cell if it is not spanned to the target cell. + for (var spannedColIdx = colIdx + 1; spannedColIdx < aColIdx; + spannedColIdx++) { + var spannedCellState = aCellsArray[rowIdx][spannedColIdx]; + if (!(spannedCellState & kRowSpanned)) { + aCellsArray[rowIdx][colIdx] = false; + break; + } + } + } + } + } + } + + acc.selectColumn(aColIdx); + testTableSelection(aIdentifier, aCellsArray, + "Select " + aColIdx + " column: "); +} + +/** + * Test unselectRow method of accessible table. + */ +function testUnselectTableRow(aIdentifier, aRowIdx, aCellsArray) +{ + var acc = getAccessible(aIdentifier, [nsIAccessibleTable]); + if (!acc) + return; + + var colsCount = aCellsArray[0].length; + for (var colIdx = 0; colIdx < colsCount; colIdx++) { + // Unselect origin cell. + var [origRowIdx, origColIdx] = getOrigRowAndColumn(aCellsArray, + aRowIdx, colIdx); + aCellsArray[origRowIdx][origColIdx] = false; + } + + acc.unselectRow(aRowIdx); + testTableSelection(aIdentifier, aCellsArray, + "Unselect " + aRowIdx + " row: "); +} + +/** + * Test selectRow method of accessible table. + */ +function testSelectTableRow(aIdentifier, aRowIdx, aCellsArray) +{ + var acc = getAccessible(aIdentifier, [nsIAccessibleTable]); + if (!acc) + return; + + var rowCount = aCellsArray.length; + var colsCount = aCellsArray[0].length; + + for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + for (var colIdx = 0; colIdx < colsCount; colIdx++) { + var cellState = aCellsArray[rowIdx][colIdx]; + + if (rowIdx == aRowIdx) { // select the given row + if (!(cellState & kSpanned)) { + // Select the cell if it is origin. + aCellsArray[rowIdx][colIdx] = true; + + } else { + // If the cell is spanned then search origin cell and select it. + var [origRowIdx, origColIdx] = getOrigRowAndColumn(aCellsArray, + rowIdx, colIdx); + + aCellsArray[origRowIdx][origColIdx] = true; + } + + } else if (!(cellState & kSpanned)) { // unselect other rows + if (rowIdx > aRowIdx) { + // Unselect the cell if traversed row index is greater than row + // index of target cell. + aCellsArray[rowIdx][colIdx] = false; + + } else if (!(aCellsArray[aRowIdx][colIdx] & kRowSpanned)) { + // Unselect the cell if the target cell is not row spanned. + aCellsArray[rowIdx][colIdx] = false; + + } else { + // Unselect the cell if it is not spanned to the target cell. + for (var spannedRowIdx = rowIdx + 1; spannedRowIdx < aRowIdx; + spannedRowIdx++) { + var spannedCellState = aCellsArray[spannedRowIdx][colIdx]; + if (!(spannedCellState & kRowSpanned)) { + aCellsArray[rowIdx][colIdx] = false; + break; + } + } + } + } + } + } + + acc.selectRow(aRowIdx); + testTableSelection(aIdentifier, aCellsArray, + "Select " + aRowIdx + " row: "); +} + +/** + * 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 (var 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 (var 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--) { + var prevCellState = aCellsArray[prevRowIdx][aColIdx]; + if (!(prevCellState & kRowSpanned)) { + origRowIdx = prevRowIdx; + break; + } + } + } + + if (cellState & kColSpanned) { + for (var prevColIdx = aColIdx - 1; prevColIdx >= 0; prevColIdx--) { + var 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 000000000..ba8e6ba98 --- /dev/null +++ b/accessible/tests/mochitest/table/a11y.ini @@ -0,0 +1,27 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_css_tables.html] +[test_headers_ariagrid.html] +[test_headers_ariatable.html] +[test_headers_listbox.xul] +[test_headers_table.html] +[test_headers_tree.xul] +[test_indexes_ariagrid.html] +[test_indexes_listbox.xul] +[test_indexes_table.html] +[test_indexes_tree.xul] +[test_layoutguess.html] +[test_mtable.html] +[test_sels_ariagrid.html] +[test_sels_listbox.xul] +[test_sels_table.html] +[test_sels_tree.xul] +[test_struct_ariagrid.html] +[test_struct_ariatreegrid.html] +[test_struct_listbox.xul] +[test_struct_table.html] +[test_struct_tree.xul] +[test_table_1.html] +[test_table_2.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 000000000..f93b0770a --- /dev/null +++ b/accessible/tests/mochitest/table/test_css_tables.html @@ -0,0 +1,116 @@ +<!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 type="application/javascript" + 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 000000000..88abb248a --- /dev/null +++ b/accessible/tests/mochitest/table/test_headers_ariagrid.html @@ -0,0 +1,185 @@ +<!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 type="application/javascript" + 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 + + 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 crazy 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 crazy 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 crazy 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 000000000..d6d3b1a97 --- /dev/null +++ b/accessible/tests/mochitest/table/test_headers_ariatable.html @@ -0,0 +1,96 @@ +<!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 type="application/javascript" + 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 + + 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_listbox.xul b/accessible/tests/mochitest/table/test_headers_listbox.xul new file mode 100644 index 000000000..343f112db --- /dev/null +++ b/accessible/tests/mochitest/table/test_headers_listbox.xul @@ -0,0 +1,194 @@ +<?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 listbox"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // XUL listbox + + var headerInfoMap = [ + { + cell: "lb1_cell0", + rowHeaderCells: [], + columnHeaderCells: [ "lb1_header1" ] + }, + { + cell: "lb1_cell1", + rowHeaderCells: [], + columnHeaderCells: [ "lb1_header2" ] + }, + { + cell: "lb1_cell2", + rowHeaderCells: [], + columnHeaderCells: [ "lb1_header3" ] + }, + { + cell: "lb1_cell3", + rowHeaderCells: [], + columnHeaderCells: [ "lb1_header1" ] + }, + { + cell: "lb1_cell4", + rowHeaderCells: [], + columnHeaderCells: [ "lb1_header2" ] + }, + { + cell: "lb1_cell5", + rowHeaderCells: [], + columnHeaderCells: [ "lb1_header3" ] + }, + ]; + + testHeaderCells(headerInfoMap); + + ////////////////////////////////////////////////////////////////////////// + // XUL listbox with ARIA + + headerInfoMap = [ + { + cell: "lb2_cell0", + rowHeaderCells: [], + columnHeaderCells: [] + }, + { + cell: "lb2_cell1", + rowHeaderCells: [], + columnHeaderCells: [] + }, + { + cell: "lb2_cell2", + rowHeaderCells: [], + columnHeaderCells: [] + }, + { + cell: "lb2_cell3", + rowHeaderCells: [], + columnHeaderCells: [ "lb2_cell0" ] + }, + { + cell: "lb2_cell4", + rowHeaderCells: [ "lb2_cell3" ], + columnHeaderCells: [ "lb2_cell1" ] + }, + { + cell: "lb2_cell5", + rowHeaderCells: [ "lb2_cell3" ], + columnHeaderCells: [ "lb2_cell2" ] + }, + { + cell: "lb2_cell6", + rowHeaderCells: [], + columnHeaderCells: [ "lb2_cell0" ] + }, + { + cell: "lb2_cell7", + rowHeaderCells: [ "lb2_cell6" ], + columnHeaderCells: [ "lb2_cell1" ] + }, + { + cell: "lb2_cell8", + rowHeaderCells: [ "lb2_cell6" ], + columnHeaderCells: [ "lb2_cell2" ] + } + ]; + + testHeaderCells(headerInfoMap); + + 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=512424" + title="implement IAccessibleTable2"> + Mozilla Bug 512424 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <label control="listbox" value="multicolumn listbox with header"/> + <listbox id="listbox"> + <listhead> + <listheader id="lb1_header1" label="header1"/> + <listheader id="lb1_header2" label="header2"/> + <listheader id="lb1_header3" label="header3"/> + </listhead> + <listcols> + <listcol flex="1"/> + <listcol flex="1"/> + <listcol flex="1"/> + </listcols> + <listitem> + <listcell id="lb1_cell0" label="cell0"/> + <listcell id="lb1_cell1" label="cell1"/> + <listcell id="lb1_cell2" label="cell2"/> + </listitem> + <listitem> + <listcell id="lb1_cell3" label="cell3"/> + <listcell id="lb1_cell4" label="cell4"/> + <listcell id="lb1_cell5" label="cell5"/> + </listitem> + <listitem> + <listcell id="lb1_cell6" label="cell6"/> + <listcell id="lb1_cell7" label="cell7"/> + <listcell id="lb1_cell8" label="cell8"/> + </listitem> + </listbox> + + <label control="listbox2" value="multicolumn listbox with ARIA headers"/> + <listbox id="listbox2"> + <listcols> + <listcol flex="1"/> + <listcol flex="1"/> + <listcol flex="1"/> + </listcols> + <listitem> + <listcell role="columnheader" id="lb2_cell0" label="cell0"/> + <listcell role="columnheader" id="lb2_cell1" label="cell1"/> + <listcell role="columnheader" id="lb2_cell2" label="cell2"/> + </listitem> + <listitem> + <listcell role="rowheader" id="lb2_cell3" label="cell3"/> + <listcell id="lb2_cell4" label="cell4"/> + <listcell id="lb2_cell5" label="cell5"/> + </listitem> + <listitem> + <listcell role="rowheader" id="lb2_cell6" label="cell6"/> + <listcell id="lb2_cell7" label="cell7"/> + <listcell id="lb2_cell8" label="cell8"/> + </listitem> + </listbox> + + </vbox> + </hbox> + +</window> + 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 000000000..26691fbfb --- /dev/null +++ b/accessible/tests/mochitest/table/test_headers_table.html @@ -0,0 +1,713 @@ +<!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 type="application/javascript" + 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); + + 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> +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_headers_tree.xul b/accessible/tests/mochitest/table/test_headers_tree.xul new file mode 100644 index 000000000..9b69e8a0d --- /dev/null +++ b/accessible/tests/mochitest/table/test_headers_tree.xul @@ -0,0 +1,101 @@ +<?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 type="application/javascript" + 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 000000000..62ef0ed9d --- /dev/null +++ b/accessible/tests/mochitest/table/test_indexes_ariagrid.html @@ -0,0 +1,139 @@ +<!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 type="application/javascript" + 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 crazy ARIA grid + idxes = [ + [0, 1], + [2, 3] + ]; + testTableIndexes("grid2", 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 crazy 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> + +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_indexes_listbox.xul b/accessible/tests/mochitest/table/test_indexes_listbox.xul new file mode 100644 index 000000000..997d687f4 --- /dev/null +++ b/accessible/tests/mochitest/table/test_indexes_listbox.xul @@ -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="Table indices of accessible table for XUL listbox"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + var idxes = [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8] + ]; + testTableIndexes("listbox", idxes); + + 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=512424" + title="implement IAccessibleTable2"> + Mozilla Bug 512424 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <label control="listbox" value="multicolumn listbox with header"/> + <listbox id="listbox"> + <listhead> + <listheader label="header1"/> + <listheader label="header2"/> + <listheader label="header3"/> + </listhead> + <listcols> + <listcol flex="1"/> + <listcol flex="1"/> + <listcol flex="1"/> + </listcols> + <listitem> + <listcell label="cell0"/> + <listcell label="cell1"/> + <listcell label="cell2"/> + </listitem> + <listitem> + <listcell label="cell3"/> + <listcell label="cell4"/> + <listcell label="cell5"/> + </listitem> + <listitem> + <listcell label="cell6"/> + <listcell label="cell7"/> + <listcell label="cell8"/> + </listitem> + </listbox> + </vbox> + </hbox> + +</window> + 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 000000000..356a55933 --- /dev/null +++ b/accessible/tests/mochitest/table/test_indexes_table.html @@ -0,0 +1,410 @@ +<!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 type="application/javascript" + 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 + var 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 crazy 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); + + 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 crazy 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 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> + +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_indexes_tree.xul b/accessible/tests/mochitest/table/test_indexes_tree.xul new file mode 100644 index 000000000..89a751419 --- /dev/null +++ b/accessible/tests/mochitest/table/test_indexes_tree.xul @@ -0,0 +1,71 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible Table indexes tests"> + + <script type="application/javascript" + 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 000000000..e35193c85 --- /dev/null +++ b/accessible/tests/mochitest/table/test_layoutguess.html @@ -0,0 +1,506 @@ +<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 type="application/javascript" + 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() + { + // Attribute we're looking for + var attr = { + "layout-guess": "true" + }; + + // table with role of grid + testAbsentAttrs("table1", attr); + // table with role of grid and datatable="0" + testAbsentAttrs("table1.1", attr); + + // table with landmark role + testAbsentAttrs("table2", attr); + + // table with summary + testAbsentAttrs("table3", attr); + + // table with caption + testAbsentAttrs("table4", attr); + + // layout table with empty caption + testAttrs("table4.2", attr, true); + + // table with thead element + testAbsentAttrs("table5", attr); + + // table with tfoot element + testAbsentAttrs("table5.1", attr); + + // table with colgroup or col elements + testAbsentAttrs("table5.2", attr); + testAbsentAttrs("table5.3", attr); + + // table with th element + testAbsentAttrs("table6", attr); + + // table with headers attribute + testAbsentAttrs("table6.2", attr); + + // table with scope attribute + testAbsentAttrs("table6.2.2", attr); + + // table with abbr attribute + testAbsentAttrs("table6.2.3", attr); + + // table with abbr element + testAbsentAttrs("table6.3", attr); + + // table with abbr element having empty text node + testAbsentAttrs("table6.4", attr); + + // table with abbr element and non-empty text node + testAttrs("table6.5", attr, true); + + // layout table with nested table + testAttrs("table9", attr, true); + + // layout table with 1 column + testAttrs("table10", attr, true); + + // layout table with 1 row + testAttrs("table11", attr, true); + + // table with 5 columns + testAbsentAttrs("table12", attr); + + // table with a bordered cell + testAbsentAttrs("table13", attr); + + // table with alternating row background colors + testAbsentAttrs("table14", attr); + + // table with 3 columns and 21 rows + testAbsentAttrs("table15", attr); + + // layout table that has a 100% width + testAttrs("table16", attr, true); + + // layout table that has a 95% width in pixels + testAttrs("table17", attr, true); + + // layout table with less than 10 columns + testAttrs("table18", attr, true); + + // layout table with embedded iframe + testAttrs("table19", attr, true); + + // tree grid, no layout table + testAbsentAttrs("table20", attr); + + // layout table containing nested data table (having data structures) + testAttrs("table21", attr, true); + testAttrs("table21.2", attr, true); + testAttrs("table21.3", attr, true); + testAttrs("table21.4", attr, true); + testAttrs("table21.5", attr, true); + testAttrs("table21.6", attr, true); + + // layout table having datatable="0" attribute and containing data table structure (tfoot element) + testAttrs("table22", attr, true); + + 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> + +</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 000000000..37ea8af50 --- /dev/null +++ b/accessible/tests/mochitest/table/test_mtable.html @@ -0,0 +1,128 @@ +<!DOCTYPE html> +<html> +<head> + <title>MathML table tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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. + var idxes = [[0]]; + testTableIndexes("simple_label", idxes); + var cellsArray = [[kDataCell]]; + rowsArray = [ROLE_MATHML_LABELED_ROW]; + testTableStruct("simple_label", cellsArray, kNoColumnHeader, + "", "", kMathTable, rowsArray); + + 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> + </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 000000000..e8b082471 --- /dev/null +++ b/accessible/tests/mochitest/table/test_sels_ariagrid.html @@ -0,0 +1,161 @@ +<!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 type="application/javascript" + 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); + testUnselectTableColumn("table", 3, cellsArray); + testUnselectTableRow("table", 3, cellsArray); + testSelectTableColumn("table", 0, cellsArray); + testSelectTableRow("table", 0, cellsArray); + + ////////////////////////////////////////////////////////////////////////// + // a bit crazy ARIA grid + cellsArray = + [ + [ false, false], + [ false, false] + ]; + + testTableSelection("grid2", cellsArray); + testSelectTableColumn("grid2", 0, cellsArray); + testSelectTableRow("grid2", 0, cellsArray); + testUnselectTableColumn("grid2", 0, cellsArray); + testUnselectTableRow("grid2", 0, cellsArray); + + ////////////////////////////////////////////////////////////////////////// + // ARIA grid (column and row headers) + + cellsArray = + [ + [ undefined, true, false], + [ undefined, true, false] + ]; + + testTableSelection("grid3", cellsArray); + testSelectTableColumn("grid3", 0, cellsArray); + testSelectTableRow("grid3", 0, cellsArray); + testUnselectTableColumn("grid3", 0, cellsArray); + testUnselectTableRow("grid3", 0, 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 crazy 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_listbox.xul b/accessible/tests/mochitest/table/test_sels_listbox.xul new file mode 100644 index 000000000..83697e8d0 --- /dev/null +++ b/accessible/tests/mochitest/table/test_sels_listbox.xul @@ -0,0 +1,247 @@ +<?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="nsIAccessibleTable selection methods on xul:listbox test."> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <script type="application/javascript" + src="../common.js"></script> + <script type="application/javascript" + src="../table.js"></script> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + var id = "listbox3"; + var acc = getAccessible(id, [nsIAccessibleTable]); + + var rowCount = acc.rows; + var colsCount = acc.columns; + + // columns selection + testColumnSelection(id, acc, colsCount, 0, null); + acc.selectColumn(0); + testColumnSelection(id, acc, colsCount, 0, null); + + // rows selection + testRowSelection(id, acc, rowCount, 0, null); + acc.selectRow(0); + testRowSelection(id, acc, rowCount, 1, [0]); + acc.selectRow(1); + testRowSelection(id, acc, rowCount, 1, [1]); + acc.unselectRow(1); + testRowSelection(id, acc, rowCount, 0, null); + + // cells selection + testCellSelection(id, acc, rowCount, colsCount, 0, null); + acc.selectRow(2); + testCellSelection(id, acc, rowCount, colsCount, 3, [6, 7, 8]); + acc.unselectRow(2); + testCellSelection(id, acc, rowCount, colsCount, 0, null); + + SimpleTest.finish(); + } + + /** + * Helper function to test isColumnSelected(), selectedColumnCount and + * getSelectedColumn() methods. + */ + function testColumnSelection(aId, aAcc, aCount, aSelCount, aSelIndexesArray) + { + // isColumnSelected + for (var col = 0; col < aCount; col++) { + if (aSelIndexesArray && aSelIndexesArray.indexOf(col) != -1) { + is(aAcc.isColumnSelected(col), true, + aId + ": column " + col + " should be selected"); + } else { + is(aAcc.isColumnSelected(col), false, + aId + ": column " + col + " shouldn't be selected"); + } + } + + // selectedColumnCount + is(aAcc.selectedColumnCount, aSelCount, + aId + ": wrong number of selected columns"); + + // getSelectedColumns + var selColsCount = {}, selCols = {}; + aAcc.getSelectedColumnIndices(selColsCount, selCols); + + is(selColsCount.value, aSelCount, + aId + ": wrong number of selected columns"); + + if (!aSelIndexesArray) { + is(selCols.value, undefined, + aId + ": no columns should be selected"); + } else { + for (var i = 0; i < selCols.length; i++) { + is(selCols[i], aSelIndexesArray[i], + aId + ": wrong selected column index " + i); + } + } + } + + /** + * Helper function to test isRowSelected(), selectedRowCount() and + * getSelectedRow() methods. + */ + function testRowSelection(aId, aAcc, aCount, aSelCount, aSelIndexesArray) + { + // isRowSelected + for (var row = 0; row < aCount; row++) { + if (aSelIndexesArray && aSelIndexesArray.indexOf(row) != -1) { + is(aAcc.isRowSelected(row), true, + aId + ": row " + row + " should be selected"); + } else { + is(aAcc.isRowSelected(row), false, + aId + ": row " + row + " shouldn't be selected"); + } + } + + // selectedRowCount + is(aAcc.selectedRowCount, aSelCount, + aId + ": wrong number of selected rows"); + + // getSelectedRows + var selColsCount = {}, selCols = {}; + aAcc.getSelectedRowIndices(selColsCount, selCols); + + is(selColsCount.value, aSelCount, + aId + ": wrong number of selected rows"); + + if (!aSelIndexesArray) { + is(selCols.value, undefined, + aId + ": no row should be selected"); + } else { + for (var i = 0; i < selCols.length; i++) { + is(selCols[i], aSelIndexesArray[i], + aId + ": wrong selected row index " + i); + } + } + } + + /** + * Helper function to test isCellSelected(), selectedCellCount() and + * getSelectedCells() methods. + */ + function testCellSelection(aId, aAcc, aRowCount, aColCount, + aSelCount, aSelIndexesArray) + { + // isCellSelected + for (var row = 0; row < aRowCount; row++) { + for (var col = 0; col < aColCount; col++) { + var index = aAcc.getIndexAt(row, col); + if (aSelIndexesArray && aSelIndexesArray.indexOf(index) != -1) { + is(aAcc.isCellSelected(row, col), true, + aId + ": cell (" + row + ", " + col + ") should be selected"); + } else { + is(aAcc.isCellSelected(row, col), false, + aId + ": cell (" + row + ", " + col + ") shouldn't be selected"); + } + } + } + + // selectedCellCount + is(aAcc.selectedCellCount, aSelCount, + aId + ": wrong number of selected cells"); + + // getSelectedCells + var selColsCount = {}, selCols = {}; + aAcc.getSelectedCellIndices(selColsCount, selCols); + + is(selColsCount.value, aSelCount, + aId + ": wrong number of selected cells"); + + if (!aSelIndexesArray) { + is(selCols.value, undefined, + aId + ": no cells should be selected"); + } else { + for (var i = 0; i < selCols.length; i++) { + is(selCols[i], aSelIndexesArray[i], + aId + ": wrong selected cell index " + i); + } + } + } + + 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> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=512424" + title="implement IAccessibleTable2"> + Mozilla Bug 512424 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <label control="listbox2" value="multicolumn listbox: "/> + <listbox id="listbox2"> + <listcols> + <listcol flex="1"/> + <listcol flex="1"/> + </listcols> + <listitem> + <listcell label="cell1"/> + <listcell label="cell2"/> + </listitem> + <listitem> + <listcell label="cell1"/> + <listcell label="cell2"/> + </listitem> + </listbox> + + <label control="listbox3" value="multicolumn listbox with header"/> + <listbox id="listbox3"> + <listhead> + <listheader label="header1"/> + <listheader label="header2"/> + <listheader label="header3"/> + </listhead> + <listcols> + <listcol flex="1"/> + <listcol flex="1"/> + <listcol flex="1"/> + </listcols> + <listitem> + <listcell label="cell0"/> + <listcell label="cell1"/> + <listcell label="cell2"/> + </listitem> + <listitem> + <listcell label="cell3"/> + <listcell label="cell4"/> + <listcell label="cell5"/> + </listitem> + <listitem> + <listcell label="cell6"/> + <listcell label="cell7"/> + <listcell label="cell8"/> + </listitem> + </listbox> + </vbox> + </hbox> + +</window> + 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 000000000..c0b37384e --- /dev/null +++ b/accessible/tests/mochitest/table/test_sels_table.html @@ -0,0 +1,180 @@ +<!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 type="application/javascript" + 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 rowCount = 4; + for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) + testSelectTableRow("table", rowIdx, cellsArray); + + for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) { + testSelectTableRow("table", rowIdx, cellsArray); + testUnselectTableRow("table", rowIdx, cellsArray); + } + + var columsCount = 8; + for (var colIdx = 0; colIdx < columsCount; colIdx++) + testSelectTableColumn("table", colIdx, cellsArray); + + for (var colIdx = 0; colIdx < columsCount; colIdx++) { + testSelectTableColumn("table", colIdx, cellsArray); + testUnselectTableColumn("table", colIdx, 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=417929" + title="nsIAccessiblTable selectRows does not unselect previously selected rows"> + Mozilla Bug 417929 + </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.xul b/accessible/tests/mochitest/table/test_sels_tree.xul new file mode 100644 index 000000000..ab2d1fc25 --- /dev/null +++ b/accessible/tests/mochitest/table/test_sels_tree.xul @@ -0,0 +1,79 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible Table selection tests"> + + <script type="application/javascript" + 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); + testSelectTableRow("tree", 0, cellsArray); + testUnselectTableRow("tree", 0, 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 000000000..9f771ef3b --- /dev/null +++ b/accessible/tests/mochitest/table/test_struct_ariagrid.html @@ -0,0 +1,149 @@ +<!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 type="application/javascript" + 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); + + 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 crazy 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> +</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 000000000..7c97adb9b --- /dev/null +++ b/accessible/tests/mochitest/table/test_struct_ariatreegrid.html @@ -0,0 +1,76 @@ +<!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 type="application/javascript" + 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_listbox.xul b/accessible/tests/mochitest/table/test_struct_listbox.xul new file mode 100644 index 000000000..102269358 --- /dev/null +++ b/accessible/tests/mochitest/table/test_struct_listbox.xul @@ -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="Table accessible tree and table interface tests for XUL listboxes"> + + <script type="application/javascript" + 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="../table.js"></script> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // Multicolumn listbox. + + var cellsArray = [ + [kDataCell, kDataCell], + [kDataCell, kDataCell] + ]; + + testTableStruct("listbox1", cellsArray); + + ////////////////////////////////////////////////////////////////////////// + // Multicolumn listbox with header. + + var cellsArray = [ + [kDataCell, kDataCell, kDataCell], + [kDataCell, kDataCell, kDataCell], + [kDataCell, kDataCell, kDataCell] + ]; + + testTableStruct("listbox2", cellsArray, kListboxColumnHeader); + + 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=512424" + title="implement IAccessibleTable2"> + Mozilla Bug 512424 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <vbox flex="1"> + + <label control="listbox1" value="multicolumn listbox: "/> + <listbox id="listbox1"> + <listcols> + <listcol flex="1"/> + <listcol flex="1"/> + </listcols> + <listitem> + <listcell label="cell1"/> + <listcell label="cell2"/> + </listitem> + <listitem> + <listcell label="cell1"/> + <listcell label="cell2"/> + </listitem> + </listbox> + + <label control="listbox2" value="multicolumn listbox with header"/> + <listbox id="listbox2"> + <listhead> + <listheader label="header1"/> + <listheader label="header2"/> + <listheader label="header3"/> + </listhead> + <listcols> + <listcol flex="1"/> + <listcol flex="1"/> + <listcol flex="1"/> + </listcols> + <listitem> + <listcell label="cell0"/> + <listcell label="cell1"/> + <listcell label="cell2"/> + </listitem> + <listitem> + <listcell label="cell3"/> + <listcell label="cell4"/> + <listcell label="cell5"/> + </listitem> + <listitem> + <listcell label="cell6"/> + <listcell label="cell7"/> + <listcell label="cell8"/> + </listitem> + </listbox> + </vbox> + </hbox> + +</window> + 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 000000000..98b954935 --- /dev/null +++ b/accessible/tests/mochitest/table/test_struct_table.html @@ -0,0 +1,203 @@ +<!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 type="application/javascript" + 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); + + 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> + +</body> +</html> diff --git a/accessible/tests/mochitest/table/test_struct_tree.xul b/accessible/tests/mochitest/table/test_struct_tree.xul new file mode 100644 index 000000000..2037bf714 --- /dev/null +++ b/accessible/tests/mochitest/table/test_struct_tree.xul @@ -0,0 +1,74 @@ +<?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 type="application/javascript" + 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 000000000..ab3937770 --- /dev/null +++ b/accessible/tests/mochitest/table/test_table_1.html @@ -0,0 +1,105 @@ +<!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 type="application/javascript" + 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"); + + var columnDescription = accTable.getColumnDescription(1); + var rowDescription = accTable.getRowDescription(1); + + 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 000000000..38477c2fa --- /dev/null +++ b/accessible/tests/mochitest/table/test_table_2.html @@ -0,0 +1,89 @@ +<!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 type="application/javascript" + 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) { + 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/test_OuterDocAccessible.html b/accessible/tests/mochitest/test_OuterDocAccessible.html new file mode 100644 index 000000000..c2efa18fe --- /dev/null +++ b/accessible/tests/mochitest/test_OuterDocAccessible.html @@ -0,0 +1,89 @@ +<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 type="application/javascript" + 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 = Components.results.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!"); + var actionTempStr; // not really used, just needs to receive a value + try { + actionTempStr = outerDocAcc.getActionName(0); + do_throw("No exception thrown for actionName!"); + } catch(e) { + ok(e.result, ns_error_invalid_arg, + "Wrong return value for actionName call!"); + } + + try { + actionTempStr = outerDocAcc.getActionDescription(0); + do_throw("No exception thrown for actionDescription!"); + } catch(e) { + ok(e.result, ns_error_invalid_arg, + "Wrong return value for actionDescription call!"); + } + + try { + outerDocAcc.doAction(0); + do_throw("No exception thrown for doAction!"); + } catch(e) { + ok(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 000000000..882099026 --- /dev/null +++ b/accessible/tests/mochitest/test_aria_token_attrs.html @@ -0,0 +1,329 @@ +<!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 type="application/javascript" + 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_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 + testStates("menuitem_checked_true", (STATE_CHECKABLE | STATE_CHECKED)); + testStates("menuitem_checked_false", STATE_CHECKABLE, 0, 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 (menuitemradio) checkable and checked states + testStates("menuitemradio_checked_true", (STATE_CHECKABLE | 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 (radio) checkable and checked states + testStates("radio_checked_true", (STATE_CHECKABLE | 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 (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_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">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div> + <div id="menuitem_checked_false" role="menuitem" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div> + <div id="menuitem_checked_empty" role="menuitem" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div> + <div id="menuitem_checked_undefined" role="menuitem" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div> + <div id="menuitem_checked_absent" role="menuitem">This menuitem has <emph>no</emph> aria-checked attribute and should <emph>not</emph> get STATE_CHECKABLE.</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_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="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_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="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 000000000..63ab4d37b --- /dev/null +++ b/accessible/tests/mochitest/test_bug420863.html @@ -0,0 +1,103 @@ +<!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 type="application/javascript" + 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, false); + + // 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, false); + + // 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_descr.html b/accessible/tests/mochitest/test_descr.html new file mode 100644 index 000000000..b9dc031d5 --- /dev/null +++ b/accessible/tests/mochitest/test_descr.html @@ -0,0 +1,121 @@ +<html> + +<head> + <title>nsIAccessible::description tests</title> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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"); + + // 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", ""); + + 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> + + <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> +</body> +</html> diff --git a/accessible/tests/mochitest/test_nsIAccessibleDocument.html b/accessible/tests/mochitest/test_nsIAccessibleDocument.html new file mode 100644 index 000000000..9e0dc7489 --- /dev/null +++ b/accessible/tests/mochitest/test_nsIAccessibleDocument.html @@ -0,0 +1,96 @@ +<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 type="application/javascript" 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="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 + 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 nsIDOMDocument retrieval. + var domDoc = null; + try { + domDoc = docAcc.DOMDocument.QueryInterface(nsIDOMDocument); + } catch(e) {} + ok(domDoc, "no nsIDOMDocument for this doc accessible!"); + is(domDoc, document, "Document nodes do not match!"); + + // Test for correct nsIDOMWindow retrieval. + var domWindow = null; + try { + domWindow = docAcc.window.QueryInterface(nsIDOMWindow); + } 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 000000000..39e7e41e1 --- /dev/null +++ b/accessible/tests/mochitest/test_nsIAccessibleImage.html @@ -0,0 +1,202 @@ +<!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 type="application/javascript" + 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 (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 = new Array("jump"); + testThis("linkedImage", "moz.png", 89, 38, 1, + actionNamesArray); + + // Image with long desc + var actionNamesArray = new Array("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 = new Array("click", "showlongdesc"); + testThis("clickAndLongdesc", "moz.png", + 89, 38, 2, actionNamesArray); + + // Image with click + actionNamesArray = null; + actionNamesArray = new Array("click"); + testThis("click", "moz.png", + 89, 38, 1, actionNamesArray); + + // Image with long desc + actionNamesArray = null; + actionNamesArray = new Array("showlongdesc"); + testThis("longdesc2", "moz.png", + 89, 38, 1, actionNamesArray); + + // Image described by HTML:a@href with whitespaces + actionNamesArray = null; + actionNamesArray = new Array("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 000000000..a91ea9acc --- /dev/null +++ b/accessible/tests/mochitest/text.js @@ -0,0 +1,634 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 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 password 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 + * + * @note All this function does is test that getText doe snot expose the + * password text itself, but something else. + */ +function testPasswordText(aIDs, aStartOffset, aEndOffset, aText) +{ + for (var i = 0; i < aIDs.length; i++) + { + var acc = getAccessible(aIDs[i], nsIAccessibleText); + try { + isnot(acc.getText(aStartOffset, aEndOffset), aText, + "getText: plain text between start and end offsets '" + aStartOffset + + "', '" + aEndOffset + " for '" + prettyName(aIDs[i]) + "'"); + } catch (e) { + ok(false, + "getText fails between start and end offsets '" + aStartOffset + + "', '" + aEndOffset + " for '" + prettyName(aIDs[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) { + var text = 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 (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; + } + + 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); + + ok(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); + + ok(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) { + var ids = (aArgs[0] instanceof Array) ? aArgs[0] : [ aArgs[0] ]; + var boundaryType = aArgs[1]; + var list = aArgs[2]; + for (var i = 0; i < list.length; i++) { + var offset1 = list[i][0], offset2 = list[i][1]; + var text = list[i][2], startOffset = list[i][3], endOffset = list[i][4]; + var failureList = list[i][5]; + for (var offset = offset1; offset <= offset2; offset++) { + for (var idIdx = 0; idIdx < ids.length; idIdx++) { + var id = ids[idIdx]; + + var flagOk1 = kOk, flagOk2 = kOk, flagOk3 = kOk; + if (failureList) { + for (var 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; + } + } + } + + var 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) { + var ids = aArgs[5]; + for (var i = 0; i < ids.length; i++) { + var 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 (var i = 5; i < aArgs.length; i = i + 4) { + var ID = aArgs[i]; + var acc = getAccessible(ID, nsIAccessibleText); + var toDoFlag1 = aArgs[i + 1]; + var toDoFlag2 = aArgs[i + 2]; + var 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"; + } +} diff --git a/accessible/tests/mochitest/text/a11y.ini b/accessible/tests/mochitest/text/a11y.ini new file mode 100644 index 000000000..96283a736 --- /dev/null +++ b/accessible/tests/mochitest/text/a11y.ini @@ -0,0 +1,16 @@ +[DEFAULT] +support-files = doc.html + !/accessible/tests/mochitest/*.js + +[test_atcaretoffset.html] +[test_charboundary.html] +[test_doc.html] +[test_dynamic.html] +[test_general.xul] +[test_gettext.html] +[test_hypertext.html] +[test_lineboundary.html] +[test_passwords.html] +[test_selection.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 000000000..d57406c22 --- /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 000000000..330298a62 --- /dev/null +++ b/accessible/tests/mochitest/text/test_atcaretoffset.html @@ -0,0 +1,455 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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: function() + { return this.mOffset; } + }); + + Object.defineProperty(this, "offsetDescr", { get: function() + { + return this.mOffset + " offset (" + this.mLine.number + " line, " + + (this.mOffset - this.mLine.start) + " offset on the line)"; + } + }); + + Object.defineProperty(this, "tests", { get: function() + { + // 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: function() + { + 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: function() + { + return new line(aWholeText, aLines, aIndex - 1); + } + }); + Object.defineProperty(this, "nextLine", { get: function() + { + return new line(aWholeText, aLines, aIndex + 1); + } + }); + + Object.defineProperty(this, "start", { get: function() + { + if (aIndex < 0) + return 0; + + if (aIndex >= aLines.length) + return aWholeText.length; + + return aLines[aIndex][2]; + } + }); + Object.defineProperty(this, "end", { get: function() + { + if (aIndex < 0) + return 0; + + if (aIndex >= aLines.length) + return aWholeText.length; + + return aLines[aIndex][3]; + } + }); + + Object.defineProperty(this, "number", { get: function() + { return aIndex; } + }); + Object.defineProperty(this, "wholeText", { get: function() + { return aWholeText; } + }); + this.isFakeLine = function line_isFakeLine() + { + return aIndex < 0 || aIndex >= aLines.length; + } + + Object.defineProperty(this, "lastWord", { get: function() + { + 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: function() + { + 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: function() + { + if (aIndex < 0 || aIndex >= aLines.length) + return []; + + return aLines[aIndex][4].lsf || []; + } + }); + Object.defineProperty(this, "lineEndFailures", { get: function() + { + 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: function() + { + 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: function() + { + 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: function() { return aLine; } }); + + Object.defineProperty(this, "start", { get: function() + { + if (this.isFakeStartWord()) + return 0; + + if (this.isFakeEndWord()) + return aLine.end; + return aWords[aIndex]; + } + }); + Object.defineProperty(this, "end", { get: function() + { + 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, 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.indexOf(failure[0]) != -1 && boundary == failure[1]) { + isOk1 = failure[2]; + isOk2 = failure[3]; + isOk3 = failure[4]; + } + } + + func.call(null, 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, kTodo, kTodo, kTodo ], + [ "TextAfterOffset", BOUNDARY_WORD_END, kTodo, kTodo, kTodo ] + ]; + 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> + + <textarea id="ta_wrapped" cols="5">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 000000000..2fddcb5be --- /dev/null +++ b/accessible/tests/mochitest/text/test_charboundary.html @@ -0,0 +1,140 @@ +<!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 type="application/javascript" + 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 000000000..9c6788275 --- /dev/null +++ b/accessible/tests/mochitest/text/test_doc.html @@ -0,0 +1,42 @@ +<!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 type="application/javascript" + 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 000000000..0e5d4394a --- /dev/null +++ b/accessible/tests/mochitest/text/test_dynamic.html @@ -0,0 +1,88 @@ +<!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 type="application/javascript" + 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.xul b/accessible/tests/mochitest/text/test_general.xul new file mode 100644 index 000000000..0bd720e28 --- /dev/null +++ b/accessible/tests/mochitest/text/test_general.xul @@ -0,0 +1,80 @@ +<?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: XUL label text interface"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <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); + + ////////////////////////////////////////////////////////////////////////// + // XUL textbox + + testTextAtOffset([ getNode("tbox1").inputField ], 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> + + <textbox id="tbox1" value="test" multiline="true"/> + </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 000000000..303edc58a --- /dev/null +++ b/accessible/tests/mochitest/text/test_gettext.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<html> +<head> + <title>Get text between offsets tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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" ]; + + 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 + + var 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"); + + 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">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/><br/></div> + <div id="e3" contenteditable="true">oneword + +two words +</div> + <div id="ebr3" contenteditable="true">oneword<br/><br/>two words<br/><br/></div> + <textarea id="t3" cols="300">oneword + +two words +</textarea> + </pre> + +</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 000000000..2d71e11a9 --- /dev/null +++ b/accessible/tests/mochitest/text/test_hypertext.html @@ -0,0 +1,147 @@ +<!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 type="application/javascript" + 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" ]; + + //////////////////////////////////////////////////////////////////////// + // 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", kTodo, kOk, kTodo); + + ////////////////////////////////////////////////////////////////////////// + // 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", kTodo, kOk, kTodo); + 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> + <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> + +</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 000000000..8370d25d0 --- /dev/null +++ b/accessible/tests/mochitest/text/test_lineboundary.html @@ -0,0 +1,265 @@ +<!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 type="application/javascript" + 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], + [7, 7, "", 7, 7], + [8, 15, "Line 3 ", 8, 15]]); + + ////////////////////////////////////////////////////////////////////////// + // __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", 0, 3 ] ]); + testTextAtOffset([ getAccessible("ht_3").firstChild.firstChild ], + BOUNDARY_LINE_START, + [ [ 0, 3, "foo\n", 0, 4 ], [ 4, 4, "", 4, 4 ] ]); + + ////////////////////////////////////////////////////////////////////////// + // '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 ] ]); + + 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/><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/><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> + </body> +</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 000000000..8a47b5944 --- /dev/null +++ b/accessible/tests/mochitest/text/test_passwords.html @@ -0,0 +1,60 @@ +<!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 type="application/javascript" + 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); + testPasswordText(IDs, 0, 4, "test"); + + 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 000000000..b832231cf --- /dev/null +++ b/accessible/tests/mochitest/text/test_selection.html @@ -0,0 +1,101 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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... + + 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> + +</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 000000000..6d3c09e8c --- /dev/null +++ b/accessible/tests/mochitest/text/test_wordboundary.html @@ -0,0 +1,291 @@ +<!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 type="application/javascript" + 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 + var ids = [ "i2", "d2", "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 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" ]; + 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", kTodo, kOk, kTodo ], + [ 8, "ml_edivbr1", kTodo, kOk, kTodo ], + [ 9, "ml_divbr1", kTodo, kOk, kTodo ], + [ 9, "ml_edivbr1", kTodo, kOk, kTodo ] ] ], + [ 13, 18, "\n\ntwo", 7, 12 ], + [ 19, 19, " words", 12, 18, + [ [ 19, "ml_divbr1", kTodo, kTodo, kTodo, ], + [ 19, "ml_edivbr1", kTodo, kTodo, kTodo, ] ] ] + ] ); + + testTextAtOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 8, "oneword\n\n", 0, 9, + [ [ 7, "ml_divbr1", kTodo, kTodo, kTodo ], + [ 7, "ml_edivbr1", kTodo, kTodo, kTodo ], + [ 8, "ml_divbr1", kTodo, kTodo, kTodo ], + [ 8, "ml_edivbr1", kTodo, kTodo, kTodo ] ] ], + [ 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", kTodo, kTodo, kOk ], + [ 18, "ml_edivbr1", kTodo, kTodo, kOk ], + [ 19, "ml_divbr1", kTodo, kTodo, kOk ], + [ 19, "ml_edivbr1", kTodo, kTodo, kOk ] ] ] ]); + + testTextAfterOffset(ids, BOUNDARY_WORD_START, + [ [ 0, 8, "two ", 9, 13, + [ [ 7, "ml_divbr1", kTodo, kTodo, kTodo ], + [ 7, "ml_edivbr1", kTodo, kTodo, kTodo ], + [ 8, "ml_divbr1", kTodo, kTodo, kTodo ], + [ 8, "ml_edivbr1", kTodo, kTodo, kTodo ] ] ], + [ 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", kTodo, kTodo, kOk ], + [ 18, "ml_edivbr1", kTodo, kTodo, 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 ] ]); + + 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 "/> + <pre><div id="d2">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> + + <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> + + <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/><br/></div> +<div id="ml_ediv1" contenteditable="true">oneword + +two words +</div> +<div id="ml_edivbr1" contenteditable="true">oneword<br/><br/>two words<br/><br/></div> +<textarea id="ml_t1" cols="300">oneword + +two words +</textarea> + </pre> + + <div id="cntr_1">a <a href="#">b</a></div> +</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 000000000..dff90bfea --- /dev/null +++ b/accessible/tests/mochitest/text/test_words.html @@ -0,0 +1,133 @@ +<!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 type="application/javascript" + 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 000000000..046bd57e8 --- /dev/null +++ b/accessible/tests/mochitest/textattrs/a11y.ini @@ -0,0 +1,7 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/moz.png + +[test_general.html] +[test_invalid.html] diff --git a/accessible/tests/mochitest/textattrs/test_general.html b/accessible/tests/mochitest/textattrs/test_general.html new file mode 100644 index 000000000..142701b17 --- /dev/null +++ b/accessible/tests/mochitest/textattrs/test_general.html @@ -0,0 +1,735 @@ +<html> + +<head> + <title>Text attributes tests</title> + <meta charset="utf-8" /> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <style type="text/css"> + .gencontent:before { content: "*"; } + .gencontent:after { content: "*"; } + </style> + + <script type="application/javascript" + 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 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); + + attrs = { "text-position": "super", "font-size": "10pt" }; + testTextAttrs(ID, 5, attrs, defAttrs, 5, 13); + + attrs = {}; + testTextAttrs(ID, 13, attrs, defAttrs, 13, 27); + + attrs = { "text-position": "super" }; + testTextAttrs(ID, 27, attrs, defAttrs, 27, 35); + + attrs = {}; + testTextAttrs(ID, 35, attrs, defAttrs, 35, 39); + + attrs = { "text-position": "sub", "font-size": "10pt" }; + testTextAttrs(ID, 39, attrs, defAttrs, 39, 50); + + attrs = {}; + testTextAttrs(ID, 50, attrs, defAttrs, 50, 55); + + attrs = { "text-position": "sub" }; + testTextAttrs(ID, 55, attrs, defAttrs, 55, 64); + + attrs = {}; + testTextAttrs(ID, 64, attrs, defAttrs, 64, 69); + + attrs = { "text-position": "super" }; + testTextAttrs(ID, 69, attrs, defAttrs, 69, 84); + + attrs = {}; + testTextAttrs(ID, 84, attrs, defAttrs, 84, 89); + + attrs = { "text-position": "sub" }; + testTextAttrs(ID, 89, attrs, defAttrs, 89, 102); + + attrs = {}; + testTextAttrs(ID, 102, attrs, defAttrs, 102, 107); + + attrs = { "text-position": "super" }; + testTextAttrs(ID, 107, attrs, defAttrs, 107, 123); + + attrs = {}; + testTextAttrs(ID, 123, attrs, defAttrs, 123, 128); + + attrs = { "text-position": "sub" }; + testTextAttrs(ID, 128, attrs, defAttrs, 128, 142); + + ////////////////////////////////////////////////////////////////////////// + // 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": "rgb(0, 0, 0)", + }; + 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": "rgb(0, 0, 0)", + }; + testTextAttrs(ID, 15, attrs, defAttrs, 15, 22); + + attrs = { + "text-line-through-style": "solid", + "text-line-through-color": "rgb(0, 0, 0)", + }; + 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": "rgb(0, 0, 0)", + }; + testTextAttrs(ID, 39, attrs, defAttrs, 39, 44); + + ////////////////////////////////////////////////////////////////////////// + // area18, "auto-generation text" tests + ID = "area18"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + testDefaultTextAttrs(ID, defAttrs); + + var attrs = { + "auto-generated": "true" + }; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 3); + testTextAttrs(ID, 3, { }, defAttrs, 3, 7); + testTextAttrs(ID, 7, attrs, defAttrs, 7, 8); + + ////////////////////////////////////////////////////////////////////////// + // area19, "HTML5 mark tag" test + // text enclosed in mark tag will have a different background color + ID = "area19"; + defAttrs = buildDefaultTextAttrs(ID, "12pt"); + + attrs = {}; + testTextAttrs(ID, 0, attrs, defAttrs, 0, 10); + + tempElem = getNode(ID).firstChild.nextSibling; + gComputedStyle = document.defaultView.getComputedStyle(tempElem, ""); + attrs = { "background-color": gComputedStyle.backgroundColor }; + testTextAttrs(ID, 11, attrs, defAttrs, 10, 17); + + attrs = {}; + testTextAttrs(ID, 18, attrs, defAttrs, 17, 28); + + ////////////////////////////////////////////////////////////////////////// + // area20, "aOffset as -1 (Mozilla Bug 789621)" test + + ID = "area20"; + defAttrs = buildDefaultTextAttrs(ID, "15pt"); + testDefaultTextAttrs(ID, defAttrs); + + testTextAttrs(ID, -1, {}, defAttrs, 0, 11); + 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>colored</mark> uncolored + </p> + + <p id="area20" style="font-size: 15pt;">offset test</p> + +</body> +</html> diff --git a/accessible/tests/mochitest/textattrs/test_invalid.html b/accessible/tests/mochitest/textattrs/test_invalid.html new file mode 100644 index 000000000..495db0888 --- /dev/null +++ b/accessible/tests/mochitest/textattrs/test_invalid.html @@ -0,0 +1,62 @@ +<html> + +<head> + <title>Invalid text attribute</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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/textcaret/a11y.ini b/accessible/tests/mochitest/textcaret/a11y.ini new file mode 100644 index 000000000..22d09751d --- /dev/null +++ b/accessible/tests/mochitest/textcaret/a11y.ini @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_browserui.xul] +[test_general.html] diff --git a/accessible/tests/mochitest/textcaret/test_browserui.xul b/accessible/tests/mochitest/textcaret/test_browserui.xul new file mode 100644 index 000000000..e2be6a464 --- /dev/null +++ b/accessible/tests/mochitest/textcaret/test_browserui.xul @@ -0,0 +1,67 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessibility Caret Offset Test."> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <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="../browser.js"></script> + + <script type="application/javascript"> + <![CDATA[ + + //////////////////////////////////////////////////////////////////////////// + // Tests + + //gA11yEventDumpToConsole = true; // debug + //enableLogging("tree,verbose"); + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + gQueue.push(new setCaretOffset(urlbarInput(), -1, urlbarInput())); + gQueue.push(new setCaretOffset(urlbarInput(), 0)); + gQueue.onFinish = function() + { + closeBrowserWindow(); + } + + gQueue.invoke(); + } + + SimpleTest.waitForExplicitFinish(); + openBrowserWindow(doTests, "about:"); + ]]> + </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=723833" + title="IAccessibleText::setCaretOffset on location or search bar causes focus to jump"> + Bug 723833 + </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/textcaret/test_general.html b/accessible/tests/mochitest/textcaret/test_general.html new file mode 100644 index 000000000..69f83959f --- /dev/null +++ b/accessible/tests/mochitest/textcaret/test_general.html @@ -0,0 +1,183 @@ +<html> + +<head> + <title>Text accessible caret testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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) + { + var prefs = Components.classes["@mozilla.org/preferences-service;1"]. + getService(Components.interfaces.nsIPrefBranch); + 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, 16); + 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:">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 000000000..4e610c61f --- /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 000000000..21a758e17 --- /dev/null +++ b/accessible/tests/mochitest/textrange/test_general.html @@ -0,0 +1,108 @@ +<!DOCTYPE html> +<html> +<head> + <title>Text Range tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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", textarea); + + 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 000000000..a7c79091f --- /dev/null +++ b/accessible/tests/mochitest/textrange/test_selection.html @@ -0,0 +1,120 @@ +<!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 type="application/javascript" + 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); + var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + var 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); + var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + var 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); + var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + var 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); + var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + var 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); + var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + var 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")); + var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges; + var 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", "c2", 5, "c2", 6); + + 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> +</body> +</html> diff --git a/accessible/tests/mochitest/textselection/a11y.ini b/accessible/tests/mochitest/textselection/a11y.ini new file mode 100644 index 000000000..6581af56d --- /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 000000000..87d95eaf0 --- /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 type="application/javascript" + 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"> + /** + * Invokers + */ + function addSelection(aID, aStartOffset, aEndOffset) + { + this.hyperTextNode = getNode(aID); + this.hyperText = getAccessible(aID, [ nsIAccessibleText ]); + + this.eventSeq = [ + new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID) + ]; + + this.invoke = function addSelection_invoke() + { + this.hyperText.addSelection(aStartOffset, aEndOffset); + } + + this.finalCheck = function addSelection_finalCheck() + { + is(this.hyperText.selectionCount, 1, + "addSelection: Wrong selection count for " + aID); + var startOffset = {}, endOffset = {}; + this.hyperText.getSelectionBounds(0, startOffset, endOffset); + + is(startOffset.value, aStartOffset, + "addSelection: Wrong start offset for " + aID); + is(endOffset.value, aEndOffset, + "addSelection: Wrong end offset for " + aID); + } + + this.getID = function addSelection_getID() + { + return "nsIAccessibleText::addSelection test for " + aID; + } + } + + function changeSelection(aID, aStartOffset, aEndOffset) + { + this.hyperTextNode = getNode(aID); + this.hyperText = getAccessible(aID, [ nsIAccessibleText ]); + + this.eventSeq = [ + new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID) + ]; + + this.invoke = function changeSelection_invoke() + { + this.hyperText.setSelectionBounds(0, aStartOffset, aEndOffset); + } + + this.finalCheck = function changeSelection_finalCheck() + { + is(this.hyperText.selectionCount, 1, + "setSelectionBounds: Wrong selection count for " + aID); + var startOffset = {}, endOffset = {}; + this.hyperText.getSelectionBounds(0, startOffset, endOffset); + + is(startOffset.value, aStartOffset, + "setSelectionBounds: Wrong start offset for " + aID); + is(endOffset.value, aEndOffset, + "setSelectionBounds: Wrong end offset for " + aID); + } + + this.getID = function changeSelection_getID() + { + return "nsIAccessibleText::setSelectionBounds test for " + aID; + } + } + + function removeSelection(aID) + { + this.hyperText = getAccessible(aID, [ nsIAccessibleText ]); + + this.eventSeq = [ + new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, document) + ]; + + this.invoke = function removeSelection_invoke() + { + this.hyperText.removeSelection(0); + } + + this.finalCheck = function removeSelection_finalCheck() + { + is(this.hyperText.selectionCount, 0, + "removeSelection: Wrong selection count for " + aID); + } + + this.getID = function removeSelection_getID() + { + return "nsIAccessibleText::removeSelection test for " + aID; + } + } + + function changeDOMSelection(aID, aNodeID1, aNodeOffset1, + aNodeID2, aNodeOffset2, + aTests) + { + this.hyperText = getAccessible(aID, [ nsIAccessibleText ]); + + this.eventSeq = [ + new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID) + ]; + + this.invoke = function changeDOMSelection_invoke() + { + var sel = window.getSelection(); + var range = document.createRange(); + range.setStart(getNode(aNodeID1), aNodeOffset1); + range.setEnd(getNode(aNodeID2), aNodeOffset2); + sel.addRange(range); + } + + this.finalCheck = function changeDOMSelection_finalCheck() + { + for (var i = 0; i < aTests.length; i++) { + var text = getAccessible(aTests[i][0], nsIAccessibleText); + is(text.selectionCount, 1, + "setSelectionBounds: Wrong selection count for " + aID); + var startOffset = {}, endOffset = {}; + text.getSelectionBounds(0, startOffset, endOffset); + + is(startOffset.value, aTests[i][1], + "setSelectionBounds: Wrong start offset for " + aID); + is(endOffset.value, aTests[i][2], + "setSelectionBounds: Wrong end offset for " + aID); + } + } + + this.getID = function changeDOMSelection_getID() + { + return "DOM selection change for " + aID; + } + } + + function onfocusEventSeq(aID) + { + var caretMovedChecker = + new invokerChecker(EVENT_TEXT_CARET_MOVED, aID); + var selChangedChecker = + new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID); + selChangedChecker.unexpected = true; + + return [ caretMovedChecker, selChangedChecker ]; + } + + /** + * Do tests + */ + + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new addSelection("paragraph", 1, 3)); + gQueue.push(new changeSelection("paragraph", 2, 4)); + gQueue.push(new removeSelection("paragraph")); + + gQueue.push(new synthFocus("textbox", onfocusEventSeq("textbox"))); + gQueue.push(new changeSelection("textbox", 1, 3)); + + gQueue.push(new synthFocus("textarea", onfocusEventSeq("textarea"))); + gQueue.push(new changeSelection("textarea", 1, 3)); + + gQueue.push(new changeDOMSelection("c1", "c1_span1", 0, "c1_span2", 0, + [["c1", 2, 2]])); + gQueue.push(new changeDOMSelection("c2", "c2", 0, "c2_div2", 1, + [["c2", 0, 3], ["c2_div2", 0, 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=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</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 000000000..1f7127866 --- /dev/null +++ b/accessible/tests/mochitest/textselection/test_userinput.html @@ -0,0 +1,95 @@ +<html> + +<head> + <title>Text selection by user input</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 synthTabAndCheckPrevTabbed(aID, aPrevID) + { + this.__proto__ = new synthTab(aID, new focusChecker(aID)); + + this.finalCheck = function changeSelection_finalCheck() + { + var prevTabbed = getAccessible(aPrevID, [ nsIAccessibleText ]); + is(prevTabbed.selectionCount, 0, + "Wrong selection count for " + aPrevID); + + var exceptionCaught = false; + try { + var startOffsetObj = {}, endOffsetObj = {}; + prevTabbed.getSelectionBounds(0, startOffsetObj, endOffsetObj); + } catch (e) { + exceptionCaught = true; + } + + ok(exceptionCaught, "No selection was expected for " + aPrevID); + } + + this.getID = function changeSelection_getID() + { + return "Hidden selection check for " + aPrevID; + } + } + + /** + * Do tests + */ + + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTests() + { + gQueue = new eventQueue(); + + // Tab to 't2' and then tab out it: it must has no selection. + gQueue.push(new synthFocus("t1")); + gQueue.push(new synthTab("t2", new focusChecker("t2"))); + gQueue.push(new synthTabAndCheckPrevTabbed("t3", "t2")); + + 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=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 000000000..c43e4552e --- /dev/null +++ b/accessible/tests/mochitest/tree/a11y.ini @@ -0,0 +1,51 @@ +[DEFAULT] +support-files = + dockids.html + wnd.xul + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/formimage.png + !/accessible/tests/mochitest/letters.gif + !/accessible/tests/mochitest/moz.png + !/accessible/tests/mochitest/tree/wnd.xul + !/dom/media/test/bug461281.ogg + +[test_applicationacc.xul] +skip-if = true # Bug 561508 +[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.xul] +[test_canvas.html] +[test_combobox.xul] +[test_cssflexbox.html] +[test_cssoverflow.html] +[test_dochierarchy.html] +[test_dockids.html] +[test_filectrl.html] +[test_formctrl.html] +skip-if = buildapp == "mulet" +[test_formctrl.xul] +[test_gencontent.html] +[test_groupbox.xul] +[test_iframe.html] +[test_img.html] +[test_invalid_img.xhtml] +[test_invalidationlist.html] +[test_list.html] +[test_map.html] +[test_media.html] +skip-if = buildapp == "mulet" +[test_select.html] +[test_tabbox.xul] +[test_tabbrowser.xul] +[test_table.html] +[test_tree.xul] +[test_txtcntr.html] +[test_txtctrl.html] +[test_txtctrl.xul] diff --git a/accessible/tests/mochitest/tree/dockids.html b/accessible/tests/mochitest/tree/dockids.html new file mode 100644 index 000000000..c59ae3267 --- /dev/null +++ b/accessible/tests/mochitest/tree/dockids.html @@ -0,0 +1,30 @@ +<!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"); + l.href = "http://www.mozilla.org"; + l.textContent = "Another "; + document.documentElement.appendChild(l); + + l = document.createElement("a"); + 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.xul b/accessible/tests/mochitest/tree/test_applicationacc.xul new file mode 100644 index 000000000..5811d00d3 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_applicationacc.xul @@ -0,0 +1,74 @@ +<?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 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"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + // Note: bug 560239 can be tested if this test runs in standalone mode only. + + var gURL = "../tree/wnd.xul" + 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_globals.html b/accessible/tests/mochitest/tree/test_aria_globals.html new file mode 100644 index 000000000..771640fcc --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_globals.html @@ -0,0 +1,129 @@ +<!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 type="application/javascript" + 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", + "disabled", + "dropeffect", + "flowto", + "grabbed", + "haspopup", + "hidden", + "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 (var 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 (var 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="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="hidden" aria-hidden="true"></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_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_hidden" aria-hidden="true"></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 000000000..b7fa91cee --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_grid.html @@ -0,0 +1,279 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML table tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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); + + ////////////////////////////////////////////////////////////////////////// + // crazy grids (mad 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("crazy_grid1", accTree); + + accTree = { + role: ROLE_TABLE, + children: [ + { // tr@role="row" + role: ROLE_ROW, + tagName: "TR", + children: [ + { // td generic accessible + role: ROLE_TEXT_CONTAINER, + children: [ + { // td text leaf + role: ROLE_TEXT_LEAF, + name: "cell1", + children: [ ] + } + ] + }, + { // td@role="gridcell" + role: ROLE_GRID_CELL, + name: "cell2", + children: [ { TEXT_LEAF: [ ] } ] + } + ] + } + ] + }; + testAccessibleTree("crazy_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("crazy_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("crazy_grid4", accTree); + + ////////////////////////////////////////////////////////////////////////// + // grids that could contain whitespace accessibles but shouldn't. + + var accTree = + { TREE_TABLE: [ + { ROW: [ + { GRID_CELL: [ + { TEXT_LEAF: [ ] } + ] }, + { GRID_CELL: [ + { TEXT_LEAF: [ ] } + ] }, + { GRID_CELL: [ + { TEXT_LEAF: [ ] } + ] } + ] }, + ] }; + + testAccessibleTree("whitespaces-grid", 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="crazy_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="crazy_grid2" role="grid"> + <table role="presentation"> + <tr role="row"> + <td id="ct_cell1">cell1</td> + <td role="gridcell">cell2</td> + </tr> + </table> + </div> + + <div id="crazy_grid3" role="grid"> + <div role="row"> + <div role="gridcell"> + <table role="presentation"> + <tr> + <td>cell3</td> + </tr> + </table> + </div> + </div> + </div> + + <div id="crazy_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> +</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 000000000..30f2eafe6 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_imgmap.html @@ -0,0 +1,108 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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_COMBOBOX, + 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 000000000..b53962709 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_list.html @@ -0,0 +1,92 @@ +<!DOCTYPE html> +<html> +<head> + <title>ARIA lists</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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); + + ////////////////////////////////////////////////////////////////////////// + // crazy list (mad 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("crazy_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="crazy_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 000000000..aa95b652b --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_menu.html @@ -0,0 +1,93 @@ +<!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 type="application/javascript" + 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. + tree = + { SECTION: [ // container + { MENUPOPUP: [ // menu + { MENUITEM: [ + { STATICTEXT: [] }, // bullet + { TEXT_LEAF: [] } + ] } + ] } + ] } + testAccessibleTree("menu", tree); + + // Menuitem with explicit no popup. + tree = + { SECTION: [ // container + { MENUPOPUP: [ // menu + { MENUITEM: [ + { STATICTEXT: [] }, // bullet + { TEXT_LEAF: [] } + ] } + ] } + ] } + testAccessibleTree("menu_nopopup", tree); + + // Menuitem with popup. + tree = + { SECTION: [ // container + { MENUPOPUP: [ // menu + { PARENT_MENUITEM: [ // menuitem with aria-haspopup="true" + { STATICTEXT: [] }, // 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 000000000..da4f52064 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_owns.html @@ -0,0 +1,187 @@ +<!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 type="application/javascript" + 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); + + 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> +</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 000000000..d8133ee9e --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_presentation.html @@ -0,0 +1,179 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 000000000..64d3bd891 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_aria_table.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<html> +<head> + <title>ARIA table tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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); + + 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> + +</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 000000000..d63b4ae11 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_brokencontext.html @@ -0,0 +1,265 @@ +<!DOCTYPE html> +<html> +<head> + <title>Broken context hierarchy</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 + { NOTHING: [ // tr + { NOTHING: [ // th + { TEXT_LEAF: [ ] } + ] }, + { NOTHING: [ // td + { TEXT_LEAF: [ ] } + ] } + ] } + ] }; + testAccessibleTree("button_table", tree); + + // HTML table display:inline + checkIfNotAccessible("inline_table1"); + checkIfNotAccessible("tr_in_inline_table1"); + checkIfTDGeneric("td1_in_inline_table1"); + checkIfTDGeneric("td2_in_inline_table1"); + + // HTML table display:inline inside table. We shouldn't be fooled + // by the outside table and shouldn't create table accessible and table cell + // accessible in this case. + checkIfNotAccessible("inline_table2"); + checkIfNotAccessible("tr_in_inline_table2"); + checkIfTDGeneric("td_in_inline_table2"); + + // HTML table display:block inside table. + checkIfNotAccessible("block_table"); + checkIfNotAccessible("tr_in_block_table"); + checkIfTDGeneric("td_in_block_table"); + + //////////////////////////////////////////////////////////////////////////// + // 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 + { NOTHING: [ // li + { STATICTEXT: [ ] }, + { TEXT_LEAF: [ ] } + ] }, + { NOTHING: [ // span styled as a list + { STATICTEXT: [ ] }, + { TEXT_LEAF: [ ] } + ] } + ] }; + testAccessibleTree("button_ul", tree); + + tree = + { PUSHBUTTON: [ // ol + { NOTHING: [ // li + { STATICTEXT: [ ] }, + { TEXT_LEAF: [ ] } + ] } + ] }; + testAccessibleTree("button_ol", tree); + + tree = + { PUSHBUTTON: [ // dl + { NOTHING: [ // dt + { TEXT_LEAF: [ ] } + ] }, + { NOTHING: [ // 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> + + <table id="inline_table1" border="1" style="display:inline"> + <tr id="tr_in_inline_table1"> + <td id="td1_in_inline_table1">table1 cell1</td> + <td id="td2_in_inline_table1">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> + <tr> + <td style="display:block"> + <table id="block_table" style="display:inline"> + <tr id="tr_in_block_table"> + <td id="td_in_block_table">cell0</td> + </tr> + </table> + </td> + <td>cell1</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.xul b/accessible/tests/mochitest/tree/test_button.xul new file mode 100644 index 000000000..39d189f41 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_button.xul @@ -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 XUL button hierarchy 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"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // button + + var accTree = { + role: ROLE_PUSHBUTTON, + name: "hello", + children: [ ] + }; + testAccessibleTree("button1", accTree); + + ////////////////////////////////////////////////////////////////////////// + // toolbarbutton + + var accTree = { + role: ROLE_PUSHBUTTON, + name: "hello", + children: [ ] + }; + testAccessibleTree("button2", 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' and 'menu-button'"> + 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"/> + </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 000000000..314ad84c7 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_canvas.html @@ -0,0 +1,55 @@ +<!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 type="application/javascript" + 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.xul b/accessible/tests/mochitest/tree/test_combobox.xul new file mode 100644 index 000000000..79ce866dc --- /dev/null +++ b/accessible/tests/mochitest/tree/test_combobox.xul @@ -0,0 +1,291 @@ +<?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 menulist and textbox @autocomplete hierarchy 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"> + <![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); + + ////////////////////////////////////////////////////////////////////////// + // editable menulist + + accTree = { + role: ROLE_COMBOBOX, + children: [ + { + role: ROLE_ENTRY, + children: [ + // no text leaf accessible for text node + ] + }, + { + role: ROLE_COMBOBOX_LIST, // context menu + children: [] + }, + { + role: ROLE_PUSHBUTTON, // dropmarker + children: [] + }, + { + role: ROLE_COMBOBOX_LIST, // option list + children: [ + { + role: ROLE_COMBOBOX_OPTION, + children: [] + }, + { + role: ROLE_COMBOBOX_OPTION, + children: [] + } + ] + } + ] + }; + + if (!MAC) { + testAccessibleTree("menulist2", accTree); + } else { + todo(false, "Make this test pass on OSX (bug 551957)"); + } + + ////////////////////////////////////////////////////////////////////////// + // textbox@type=autocomplete #1 (history) + + accTree = { + // textbox + role: ROLE_AUTOCOMPLETE, + children: [ + { + // html:input + role: ROLE_ENTRY, + children: [ + { + // #text + role: ROLE_TEXT_LEAF, + name: "http://mochi.test:8888/redirect-a11y.html", + children: [] + } + ] + }, + { + // xul:menupopup + role: ROLE_COMBOBOX_LIST, // context menu popup + children: [] + } + ] + }; + + // XPFE and Toolkit autocomplete widgets differ. + var ac1h = document.getElementById("autocomplete"); + if ("clearResults" in ac1h) { + SimpleTest.ok(true, "Testing (Old) XPFE autocomplete widget. (ac1h)"); + + // Popup is always created. + accTree.children.push( + { + // xul:panel + role: ROLE_COMBOBOX_LIST, + children: [ + { + // xul:tree + role: ROLE_TABLE, + children: [ + { + // xul:treecols + role: ROLE_LIST, + children: [ + { + // xul:treecol + role: ROLE_COLUMNHEADER, + children: [] + } + ] + } + ] + } + ] + } + ); + } else { + SimpleTest.ok(true, "Testing (New) Toolkit autocomplete widget. (ac1h)"); + + // Popup is lazily created, so not present in this case. + } + + testAccessibleTree("autocomplete", accTree); + + ////////////////////////////////////////////////////////////////////////// + // textbox@type=autocomplete #2 (child menupoup) + + accTree = { + // textbox + role: ROLE_AUTOCOMPLETE, + children: [ + { + // menupopup + role: ROLE_COMBOBOX_LIST, // autocomplete menu popup + children: [ + { + // menuitem + role: ROLE_COMBOBOX_OPTION, + children: [] + } + ] + }, + { + // html:input + role: ROLE_ENTRY, + children: [ + // no text leaf accessible for text node + ] + }, + { + // xul:menupopup + role: ROLE_COMBOBOX_LIST, // context menu popup + children: [] + } + ] + }; + + // XPFE and Toolkit autocomplete widgets differ. + var ac2cmp = document.getElementById("autocomplete2"); + if ("clearResults" in ac2cmp) { + SimpleTest.ok(true, "Testing (Old) XPFE autocomplete widget. (ac2mp)"); + + // Popup is always created. + accTree.children.push( + { + // xul:panel + role: ROLE_COMBOBOX_LIST, + children: [ + { + // xul:tree + role: ROLE_TABLE, + children: [ + { + // xul:treecols + role: ROLE_LIST, + children: [ + { + // xul:treecol + role: ROLE_COLUMNHEADER, + children: [] + } + ] + } + ] + } + ] + } + ); + } else { + SimpleTest.ok(true, "Testing (New) Toolkit autocomplete widget. (ac2mp)"); + + // Popup is lazily created, so not present in this case. + } + + testAccessibleTree("autocomplete2", 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' and 'menu-button'"> + 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> + + <menulist id="menulist2" editable="true"> + <menupopup> + <menuitem label="item"/> + <menuitem label="item"/> + </menupopup> + </menulist> + + <textbox id="autocomplete" type="autocomplete" + autocompletesearch="unifiedcomplete" + value="http://mochi.test:8888/redirect-a11y.html"/> + + <textbox id="autocomplete2" type="autocomplete"> + <menupopup> + <menuitem label="item1"/> + </menupopup> + </textbox> + </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 000000000..f3786ac92 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_cssflexbox.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<html> + +<head> + <title>CSS flexbox tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 000000000..c67128fbc --- /dev/null +++ b/accessible/tests/mochitest/tree/test_cssoverflow.html @@ -0,0 +1,146 @@ +<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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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) + { + todo_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) + { + todo_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_dochierarchy.html b/accessible/tests/mochitest/tree/test_dochierarchy.html new file mode 100644 index 000000000..0104c6aba --- /dev/null +++ b/accessible/tests/mochitest/tree/test_dochierarchy.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test document hierarchy</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 000000000..943ac18b3 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_dockids.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test document hierarchy</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 000000000..f989d1eea --- /dev/null +++ b/accessible/tests/mochitest/tree/test_filectrl.html @@ -0,0 +1,58 @@ +<!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 type="application/javascript" + 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_TEXT_CONTAINER, + 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 000000000..c5d5bc0b1 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_formctrl.html @@ -0,0 +1,132 @@ +<!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 type="application/javascript" + 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: [ + { ENTRY: [ ] }, + { PUSHBUTTON: [ ] }, + { PUSHBUTTON: [ ] } + ] }; + testAccessibleTree("number", accTree); + + // output + accTree = { + role: ROLE_SECTION, + 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.xul b/accessible/tests/mochitest/tree/test_formctrl.xul new file mode 100644 index 000000000..34dc3795e --- /dev/null +++ b/accessible/tests/mochitest/tree/test_formctrl.xul @@ -0,0 +1,130 @@ +<?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 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"> + <![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 000000000..0932f5c29 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_gencontent.html @@ -0,0 +1,71 @@ +<!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 type="application/javascript" + 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.xul b/accessible/tests/mochitest/tree/test_groupbox.xul new file mode 100644 index 000000000..0b68e6018 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_groupbox.xul @@ -0,0 +1,64 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Accessible XUL groupbox hierarchy 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"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + var accTree = + { GROUPING: [ + { LABEL: [ + { TEXT_LEAF: [ ] } + ] }, + { 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"> + <caption label="Some caption" /> + <checkbox label="some checkbox label" /> + </groupbox> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/tree/test_iframe.html b/accessible/tests/mochitest/tree/test_iframe.html new file mode 100644 index 000000000..dae398fd8 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_iframe.html @@ -0,0 +1,52 @@ +<!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 type="application/javascript" + 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_img.html b/accessible/tests/mochitest/tree/test_img.html new file mode 100644 index 000000000..9ac52da39 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_img.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML img tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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 000000000..2079c86ee --- /dev/null +++ b/accessible/tests/mochitest/tree/test_invalid_img.xhtml @@ -0,0 +1,50 @@ +<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 type="application/javascript" + 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_TEXT, + children: [ { role: ROLE_TEXT_LEAF } ] + }; + 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 000000000..c42891a5d --- /dev/null +++ b/accessible/tests/mochitest/tree/test_invalidationlist.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<html> +<head> + <title>Test document hierarchy</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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: [ + { SECTION: [ // div + { LABEL: [ ] } // link + ] }, + { SECTION: [ // div table-cell referred by label + { TEXT_LEAF: [ ] }, // 'Z' + { TEXT_LEAF: [ ] } // ' ' + ] } + ] }; + testAccessibleTree("container", tree); + + 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><label for="x"></label></div> + <div style="display: table-cell;" id="x">Z<span style='white-space:pre'> </span><span></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 000000000..bf5e8768d --- /dev/null +++ b/accessible/tests/mochitest/tree/test_list.html @@ -0,0 +1,247 @@ +<!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 type="application/javascript" + 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_STATICTEXT, + 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 + { STATICTEXT: [ ] }, + { DEFINITION_LIST: [ // dl + { TERM: [ // dt + { TEXT_LEAF: [] } + ] }, + { DEFINITION: [ // dd + { TEXT_LEAF: [] } + ] } + ] } + ] } + ] }; + + testAccessibleTree("list6", tree); + + // li having no display:list-item style + var tree = + { LIST: [ // ul + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + { TEXT_LEAF: [] }, + { LISTITEM: [ // li + { TEXT_LEAF: [] } + ] } + ] }; + testAccessibleTree("list7", tree); + + var tree = + { LIST: [ // ul + { LISTITEM: [ // li + { TEXT_LEAF: [] }, + ] }, + { LISTITEM: [ // li + { TEXT_LEAF: [] } + ] } + ] }; + testAccessibleTree("list8", tree); + + // span having display:list-item style + testAccessibleTree("list9", discAccTree); + + 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> +</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 000000000..6f0b0f2b6 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_map.html @@ -0,0 +1,83 @@ +<!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 type="application/javascript" + 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 000000000..b44a7293d --- /dev/null +++ b/accessible/tests/mochitest/tree/test_media.html @@ -0,0 +1,84 @@ +<!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 type="application/javascript" + 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 the accessible tree + + var accTree = { + role: ROLE_GROUPING, + children: [ + { // start/stop button + role: ROLE_PUSHBUTTON, + name: "Play", + children: [] + }, + { // buffer bar + role: ROLE_PROGRESSBAR, + children: [] + }, + { // progress bar + role: ROLE_PROGRESSBAR, + children: [] + }, + { // slider of progress bar + role: ROLE_SLIDER, + //name: "0:00 of 0:02 elapsed", + children: [] + }, + { // mute button + role: ROLE_PUSHBUTTON, + name: "Mute", + children: [] + }, + { // slider of volume bar + role: ROLE_SLIDER, + children: [] + }, + ] + }; + testAccessibleTree("audio", accTree); + + todo(false, "Enable name test for slider. Fail on Linux."); + 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> + + <audio id="audio" src="../bug461281.ogg" + 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 000000000..151985622 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_select.html @@ -0,0 +1,139 @@ +<!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 type="application/javascript" + 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_STATICTEXT, + children: [ ] + }, + { + role: ROLE_COMBOBOX_OPTION, + children: [ + { + role: ROLE_TEXT_LEAF + } + ] + }, + { + role: ROLE_COMBOBOX_OPTION, + children: [ + { + role: ROLE_TEXT_LEAF + } + ] + }, + ] + }, + { + role: ROLE_COMBOBOX_OPTION, + children: [ + { + role: ROLE_TEXT_LEAF + } + ] + } + ] + } + ] + }; + 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_tabbox.xul b/accessible/tests/mochitest/tree/test_tabbox.xul new file mode 100644 index 000000000..4d4101388 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_tabbox.xul @@ -0,0 +1,99 @@ +<?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 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"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // tabbox + + var accTree = { + role: ROLE_PAGETABLIST, + children: [ + { + role: ROLE_PAGETAB, + children: [] + }, + { + role: ROLE_PAGETAB, + 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.xul b/accessible/tests/mochitest/tree/test_tabbrowser.xul new file mode 100644 index 000000000..1e25fdb9c --- /dev/null +++ b/accessible/tests/mochitest/tree/test_tabbrowser.xul @@ -0,0 +1,255 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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:", "about:mozilla"]; + tabBrowser().loadTabs(docURIs, false, true); + } + + 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:") + 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:") + role: ROLE_PAGETAB, + children: [ + { + // xul:toolbarbutton ("Close Tab") + role: ROLE_PUSHBUTTON, + children: [] + } + ] + }, + { + // tab ("about:mozilla") + role: ROLE_PAGETAB, + children: [ + { + // xul:toolbarbutton ("Close Tab") + role: ROLE_PUSHBUTTON, + 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:") + 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().mTabBox.tabpanels, tabboxAccTree); + } + + this.getID = function testTabHierarchy_getID() + { + return "hierarchy of tabs"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + //gA11yEventDumpToConsole = true; + //enableLogging("tree,verbose,stack"); + + 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 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 000000000..bfbd6ae5a --- /dev/null +++ b/accessible/tests/mochitest/tree/test_table.html @@ -0,0 +1,282 @@ +<!DOCTYPE html> +<html> +<head> + <title>HTML table tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 ignored. + accTree = + { TABLE: [ + { ROW: [ + { CELL: [ { TEXT_LEAF: [ ] } ] }, + { CELL: [ { TEXT_LEAF: [ ] } ] } + ] } + ] }; + + testAccessibleTree("table_caption_empty", accTree); + + // Two captions, first has empty text, both are ignored. + accTree = + { TABLE: [ + { 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); + + ////////////////////////////////////////////////////////////////////////// + // 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 (intermediate accessible for tbody) + accTree = + { TABLE: [ + { TEXT_CONTAINER: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] } + ] } + ] } + ] } ] + }; + testAccessibleTree("table5", accTree); + + ///////////////////////////////////////////////////////////////////////// + // log table + accTree = + { TABLE: [ + { ROW: [ + { CELL: [ + { TEXT_LEAF: [ ] } + ] } + ] } + ] }; + testAccessibleTree("logtable", 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="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;overflow:auto;"> + <tr> + <td>bla</td> + </tr> + </tbody> + </table> + + <table id="logtable" role="log"><tr><td>blah</td></tr></table> +</body> +</html> diff --git a/accessible/tests/mochitest/tree/test_tree.xul b/accessible/tests/mochitest/tree/test_tree.xul new file mode 100644 index 000000000..0fd222c42 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_tree.xul @@ -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 type="application/javascript" + 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 treeBoxObject = aTree.treeBoxObject; + var view = aTree.view; + var columnCount = treeBoxObject.columns.count; + + for (var idx = 0; idx < columnCount; idx++) + accTreeForColumns.children.push({ COLUMNHEADER: [ ] }); + if (!aTree.hasAttribute("hidecolumnpicker")) + accTreeForColumns.children.push({ PUSHBUTTON: [ { MENUPOPUP: [] } ] }); + + for (var 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() + { + var 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 000000000..80a225ce7 --- /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 type="application/javascript" + 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_SECTION, + 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> + + <!-- only whitespace between images should be exposed --> + <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 000000000..79613cd41 --- /dev/null +++ b/accessible/tests/mochitest/tree/test_txtctrl.html @@ -0,0 +1,173 @@ +<!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 type="application/javascript" + 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_ENTRY, + children: [ + { // text child + role: ROLE_TEXT_LEAF, + children: [] + } + ] + }; + + testAccessibleTree("txc7", accTree); + + // input@type="search", value + accTree = { + role: ROLE_ENTRY, + 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.xul b/accessible/tests/mochitest/tree/test_txtctrl.xul new file mode 100644 index 000000000..cb43b498f --- /dev/null +++ b/accessible/tests/mochitest/tree/test_txtctrl.xul @@ -0,0 +1,219 @@ +<?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 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="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //gA11yEventDumpToConsole = true; + //enableLogging("tree,verbose"); // debug stuff + + //////////////////////////////////////////////////////////////////////////// + // Test + + function doTest() + { + ////////////////////////////////////////////////////////////////////////// + // textboxes + + var accTree = + { SECTION: [ + { ENTRY: [ { TEXT_LEAF: [] } ] }, + { MENUPOPUP: [] } + ] }; + + // default textbox + testAccessibleTree("txc", accTree); + + // multiline + testAccessibleTree("txc_multiline", accTree); + + ////////////////////////////////////////////////////////////////////////// + // search textbox + accTree = + { SECTION: [ + { ENTRY: [ { TEXT_LEAF: [] } ] }, + { MENUPOPUP: [] } + ] }; + testAccessibleTree("txc_search", accTree); + + ////////////////////////////////////////////////////////////////////////// + // search textbox with search button + + if (MAC) { + accTree = + { SECTION: [ + { ENTRY: [ { TEXT_LEAF: [] } ] }, + { MENUPOPUP: [] } + ] }; + } else { + accTree = + { SECTION: [ + { ENTRY: [ { TEXT_LEAF: [] } ] }, + { PUSHBUTTON: [] }, + { MENUPOPUP: [] } + ] }; + } + + testAccessibleTree("txc_search_searchbutton", accTree); + + ////////////////////////////////////////////////////////////////////////// + // number textbox + + accTree = + { SECTION: [ + { ENTRY: [ { TEXT_LEAF: [] } ] }, + { MENUPOPUP: [] }, + { PUSHBUTTON: [] }, + { PUSHBUTTON: [] } + ] }; + + testAccessibleTree("txc_number", accTree); + + ////////////////////////////////////////////////////////////////////////// + // password textbox + + accTree = + { SECTION: [ + { PASSWORD_TEXT: [ { TEXT_LEAF: [] } ] }, + { MENUPOPUP: [] } + ] }; + + testAccessibleTree("txc_password", accTree); + + ////////////////////////////////////////////////////////////////////////// + // autocomplete textbox + + accTree = { + // textbox + role: ROLE_AUTOCOMPLETE, + children: [ + { + // html:input + role: ROLE_ENTRY, + children: [ + { + // #text + role: ROLE_TEXT_LEAF, + children: [] + } + ] + }, + { + // xul:menupopup + role: ROLE_COMBOBOX_LIST, + children: [] + } + ] + }; + + function test_AutocompleteControl() { + testAccessibleTree("txc_autocomplete", accTree); + SimpleTest.finish(); + } + + // XPFE and Toolkit autocomplete widgets differ. + var txc = document.getElementById("txc_autocomplete"); + if ("clearResults" in txc) { + SimpleTest.ok(true, "Testing (Old) XPFE autocomplete widget."); + + // Popup is always created. (See code below.) + + accTree.children.push( + { + // xul:panel + role: ROLE_COMBOBOX_LIST, + children: [ + { + // xul:tree + role: ROLE_TABLE, + children: [ + { + // xul:treecols + role: ROLE_LIST, + children: [ + { + // xul:treecol + role: ROLE_COLUMNHEADER, + children: [] + } + ] + } + ] + } + ] + } + ); + test_AutocompleteControl(); + + } else { + SimpleTest.ok(true, "Testing (New) Toolkit autocomplete widget."); + + // Dumb access to trigger popup lazy creation. + dump("Trigget popup lazy creation"); + waitForEvent(EVENT_REORDER, txc, test_AutocompleteControl); + txc.popup; + + accTree.children.push( + { + role: ROLE_LIST, + children: [ + { + role: ROLE_LIST, + children: [ + { + role: ROLE_COLUMNHEADER, + children: [] + } + ] + } + ] + } + ); + } + } + + 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"> + <textbox id="txc" value="hello"/> + <textbox id="txc_search" type="search" value="hello"/> + <textbox id="txc_search_searchbutton" searchbutton="true" type="search" value="hello"/> + <textbox id="txc_number" type="number" value="44"/> + <textbox id="txc_password" type="password" value="hello"/> + <textbox id="txc_multiline" multiline="true" value="hello"/> + <textbox id="txc_autocomplete" type="autocomplete" value="hello"/> + </vbox> + </hbox> + +</window> diff --git a/accessible/tests/mochitest/tree/wnd.xul b/accessible/tests/mochitest/tree/wnd.xul new file mode 100644 index 000000000..3b87cb5e0 --- /dev/null +++ b/accessible/tests/mochitest/tree/wnd.xul @@ -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 000000000..d725ebff3 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/a11y.ini @@ -0,0 +1,41 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + !/accessible/tests/mochitest/letters.gif + !/accessible/tests/mochitest/moz.png + +[test_ariadialog.html] +[test_ariaowns.html] +[test_bug852150.xhtml] +[test_bug883708.xhtml] +[test_bug884251.xhtml] +[test_bug895082.html] +[test_bug1040735.html] +[test_bug1100602.html] +[test_bug1175913.html] +[test_bug1189277.html] +[test_bug1276857.html] +[test_canvas.html] +[test_colorpicker.xul] +[test_contextmenu.xul] +[test_cssoverflow.html] +[test_deck.xul] +[test_doc.html] +[test_gencontent.html] +[test_general.html] +[test_hidden.html] +[test_imagemap.html] +skip-if = buildapp == "mulet" +[test_list.html] +[test_list_editabledoc.html] +[test_listbox.xul] +[test_menu.xul] +[test_menubutton.xul] +[test_optgroup.html] +[test_recreation.html] +[test_select.html] +[test_shutdown.xul] +[test_table.html] +[test_textleaf.html] +[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 000000000..559431626 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_ariadialog.html @@ -0,0 +1,119 @@ +<!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 type="application/javascript" + 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: fixed; 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_ariaowns.html b/accessible/tests/mochitest/treeupdate/test_ariaowns.html new file mode 100644 index 000000000..44fc22a2d --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html @@ -0,0 +1,693 @@ +<!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 type="application/javascript" + 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_checkbox"), + new invokerChecker(EVENT_SHOW, getNode, "t1_checkbox"), + new invokerChecker(EVENT_REORDER, getNode("t1_container")) + ]; + + this.invoke = function removeEl_invoke() + { + // remove a container of t1_subdiv + getNode("t1_span").parentNode.removeChild(getNode("t1_span")); + } + + 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"; + } + } + + /** + * Steal an element from other ARIA owns element. This use case guarantees + * that result of setAttribute/removeAttribute doesn't depend on their order. + */ + function stealFromOtherARIAOwns() + { + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode("t3_container2")) + ]; + + this.invoke = function stealFromOtherARIAOwns_invoke() + { + getNode("t3_container2").setAttribute("aria-owns", "t3_child"); + } + + this.finalCheck = function stealFromOtherARIAOwns_finalCheck() + { + var tree = + { SECTION: [ + ] }; + testAccessibleTree("t3_container1", tree); + + tree = + { SECTION: [ + { CHECKBUTTON: [ + ] } + ] }; + testAccessibleTree("t3_container2", 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_container2")) + ]; + + this.invoke = function appendElToRecacheChildren_invoke() + { + var div = document.createElement("div"); + div.setAttribute("role", "radio") + getNode("t3_container2").appendChild(div); + } + + this.finalCheck = function appendElToRecacheChildren_finalCheck() + { + var tree = + { SECTION: [ + ] }; + testAccessibleTree("t3_container1", tree); + + tree = + { SECTION: [ + { RADIOBUTTON: [ ] }, + { CHECKBUTTON: [ ] } // ARIA owned + ] }; + testAccessibleTree("t3_container2", 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 (var id of aIdList) { + this.eventSeq.push(new invokerChecker(EVENT_HIDE, getNode(id))); + } + + for (var 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).parentNode.removeChild(getNode(aParent)); + } + + 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`; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + //////////////////////////////////////////////////////////////////////////// + + //gA11yEventDumpToConsole = true; + //enableLogging("tree,eventTree,verbose"); // debug stuff + + var gQueue = null; + + function doTest() + { + gQueue = new eventQueue(); + + // 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.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="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> + + <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> +</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 000000000..527f23197 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1040735.html @@ -0,0 +1,42 @@ +<!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 type="application/javascript" + 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_bug1100602.html b/accessible/tests/mochitest/treeupdate/test_bug1100602.html new file mode 100644 index 000000000..1637e5057 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1100602.html @@ -0,0 +1,114 @@ +<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 type="application/javascript" + 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"> + /** + * Change list style type to none. + */ + function hideBullet() + { + this.eventSeq = []; + this.liAcc = getAccessible("list_element"); + this.bullet = this.liAcc.firstChild; + + this.eventSeq.push(new invokerChecker(EVENT_HIDE, this.bullet)); + this.eventSeq.push(new invokerChecker(EVENT_REORDER, this.liAcc)); + + this.invoke = function hideBullet_invoke() + { + getNode("list").setAttribute("style", "list-style-type: none;"); + } + + this.finalCheck = function hideBullet_finalCheck() + { + is(this.liAcc.name, "list element", + "Check that first child of LI is not a bullet."); + } + + this.getID = function hideBullet_getID() + { + return "Hide bullet by setting style to none"; + } + } + + /** + * Change list style type to circles. + */ + function showBullet() + { + this.eventSeq = []; + this.liAcc = getAccessible("list_element"); + + this.eventSeq.push(new invokerChecker(EVENT_SHOW, + function(aNode) { return aNode.firstChild; }, + this.liAcc)); + this.eventSeq.push(new invokerChecker(EVENT_REORDER, this.liAcc)); + + this.invoke = function showBullet_invoke() + { + getNode("list").setAttribute("style", "list-style-type: circle;"); + } + + this.finalCheck = function showBullet_finalCheck() + { + is(this.liAcc.name, "◦ list element", + "Check that first child of LI is a circle bullet."); + } + + this.getID = function showBullet_getID() + { + return "Show bullet by setting style to circle"; + } + } + + var gQueue = null; + function doTest() + { + + var list = getNode("list"); + list.setAttribute("style", "list-style-type: circle;"); + + gQueue = new eventQueue(); + gQueue.push(new hideBullet()); + gQueue.push(new showBullet()); + gQueue.invoke(); // 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"> + <li id="list_element">list element</li> + </ol> + +</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 000000000..5dab9b5a8 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1175913.html @@ -0,0 +1,105 @@ +<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 type="application/javascript" + 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 000000000..9c995d49e --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1189277.html @@ -0,0 +1,86 @@ +<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 type="application/javascript" + 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"); + 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 000000000..5eceae9eb --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug1276857.html @@ -0,0 +1,143 @@ +<!DOCTYPE html> +<html> + +<head> + <title>DOM mutations test</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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() + { + // children change will recreate the table + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, getNode('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('c1', tree); + + getNode('c1_t').querySelector('span').remove(); + }; + + this.finalCheck = function runTest_finalCheck() { + var tree = { + SECTION: [ // c1 + { TEXT_LEAF: [] }, // Some text + { TEXT_LEAF: [] } // More text + ] + }; + testAccessibleTree('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, 'c2') + ]; + + this.invoke = function runShadowTest_invoke() { + var tree = { + SECTION: [ // c2 + { TEXT_LEAF: [] }, // Some text + { TEXT_CONTAINER: [ + { TEXT_LEAF: [] } // something with .. + ] }, + { TEXT_LEAF: [] } // More text + ] + }; + testAccessibleTree('c2', tree); + + gShadowRoot.firstElementChild.querySelector('span').remove(); + }; + + this.finalCheck = function runShadowTest_finalCheck() { + var tree = { + SECTION: [ // c2 + { TEXT_LEAF: [] }, // Some text + { TEXT_LEAF: [] } // More text + ] + }; + testAccessibleTree('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(); + 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_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').createShadowRoot(); + 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 000000000..094d148a0 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug852150.xhtml @@ -0,0 +1,59 @@ +<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 type="application/javascript" + 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 000000000..6265a1c7f --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug883708.xhtml @@ -0,0 +1,33 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> + +function boom() +{ + var newSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "span"); + c.insertBefore(newSpan, d); + 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 000000000..d1821188b --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug884251.xhtml @@ -0,0 +1,21 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <script type="application/javascript" + 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 000000000..aaefdc46c --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_bug895082.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> +<title>Replace body test</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 000000000..6ae129a67 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_canvas.html @@ -0,0 +1,92 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Canvas subdom mutation</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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_colorpicker.xul b/accessible/tests/mochitest/treeupdate/test_colorpicker.xul new file mode 100644 index 000000000..9b15c9174 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_colorpicker.xul @@ -0,0 +1,150 @@ +<?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 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="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function openColorpicker(aColorpickerID) + { + this.node = getNode(aColorpickerID); + + this.eventSeq = []; + + var it = new colorButtonIterator(this.node); + for (var btnNode = it.next(); btnNode = it.next(); btnNode) + this.eventSeq.push(new invokerChecker(EVENT_SHOW, btnNode)); + + var popupNode = getPopupNode(this.node); + this.eventSeq.push(new invokerChecker(EVENT_REORDER, popupNode)); + + this.invoke = function openColorpicker_invoke() + { + var tree = + { BUTTONDROPDOWNGRID: [ + { ALERT: [ ] } + ] }; + testAccessibleTree(this.node, tree); + + this.node.showPopup(); + } + + this.finalCheck = function openColorpicker_finalCheck() + { + var tree = + { BUTTONDROPDOWNGRID: [ + { ALERT: [ ] } + ] }; + + var colorButtons = tree.BUTTONDROPDOWNGRID[0].ALERT; + var it = new colorButtonIterator(this.node); + while (it.next()) { + var obj = { PUSHBUTTON: [ ] }; + colorButtons.push(obj); + } + + testAccessibleTree(this.node, tree); + } + + this.getID = function openColorpicker_getID() + { + return "open colorpicker " + prettyName(aColorpickerID); + } + } + + function getPopupNode(aColorpickerNode) + { + return aColorpickerNode.mPicker.parentNode; + } + + function colorButtonIterator(aColorpickerNode) + { + this.container = aColorpickerNode.mPicker.mBox; + this.group = this.container.firstChild; + this.item = null; + + this.next = function colorButtonIterator_next() + { + if (!this.group) + return null; + + if (!this.item) { + this.item = this.group.firstChild; + return this.item; + } + + if (this.item.nextSibling) { + this.item = this.item.nextSibling; + return this.item; + } + + if (this.group.nextSibling) { + this.group = this.group.nextSibling; + this.item = this.group.firstChild; + return this.item; + } + + this.group = null; + this.item = null; + return null; + } + } + + //gA11yEventDumpToConsole = true; // debug stuff + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + gQueue.push(new openColorpicker("colorpicker")); + 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' and 'menu-button'"> + 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"> + <colorpicker id="colorpicker" type="button"/> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_contextmenu.xul b/accessible/tests/mochitest/treeupdate/test_contextmenu.xul new file mode 100644 index 000000000..5b31e0136 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_contextmenu.xul @@ -0,0 +1,317 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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("VK_DOWN", { }); + } + + 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("VK_RETURN", { }); + } + + 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("VK_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("VK_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) { + var 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 + var 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) { + var submenuTree = + { + name: "item2.0", + role: ROLE_PARENT_MENUITEM, + children: [ ] + }; + tree.children[2].children.push(submenuTree); + return tree; + } + + // Windows + var 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 000000000..ed9edd66e --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_cssoverflow.html @@ -0,0 +1,143 @@ +<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 type="application/javascript" + 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 + //////////////////////////////////////////////////////////////////////////// + + /** + * 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 hidden to auto. That makes us to create an + * accessible for scroll area. + */ + function changeScrollbarStyles(aContainerID, aScrollAreaID) + { + this.container = getAccessible(aContainerID); + this.scrollAreaNode = getNode(aScrollAreaID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, getAccessible, this.scrollAreaNode), + new invokerChecker(EVENT_REORDER, this.container) + ]; + + this.invoke = function changeScrollbarStyles_invoke() + { + var accTree = + { SECTION: [] }; + testAccessibleTree(this.container, accTree); + + this.scrollAreaNode.style.overflow = "auto"; + } + + this.finalCheck = function changeScrollbarStyles_finalCheck() + { + var accTree = + { SECTION: [ // container + { SECTION: [] } // scroll area + ] }; + testAccessibleTree(this.container, accTree); + } + + this.getID = function changeScrollbarStyles_getID() + { + return "change scrollbar styles " + prettyName(aScrollAreaID); + } + } + + //////////////////////////////////////////////////////////////////////////// + // 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 changeScrollbarStyles("container2", "scrollarea2")); + + 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 id="scrollarea2" style="overflow:hidden;"></div></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeupdate/test_deck.xul b/accessible/tests/mochitest/treeupdate/test_deck.xul new file mode 100644 index 000000000..13561abab --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_deck.xul @@ -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="Tree update on XUL deck panel switching"> + + <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" + 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"; + } + } + + var gQueue = null; + function doTest() + { + gQueue = new eventQueue(); + 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> + </deck> + + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_doc.html b/accessible/tests/mochitest/treeupdate/test_doc.html new file mode 100644 index 000000000..05896e7b4 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_doc.html @@ -0,0 +1,466 @@ +<!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 type="application/javascript" + 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_PUSHBUTTON); + + 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", "button"); + 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.removeChild(docNode.firstChild); + } + + 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 000000000..1cf22b36f --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_gencontent.html @@ -0,0 +1,160 @@ +<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 type="application/javascript" + 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.node = getNode(aNodeID); + + this.eventSeq = [ + new invokerChecker(EVENT_HIDE, this.container.firstChild), + new invokerChecker(EVENT_SHOW, getFirstChild, this.container), + new invokerChecker(EVENT_REORDER, this.container) + ]; + + this.invoke = function addGenContent_invoke() + { + this.node.setAttribute("class", "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); + } + } + + /** + * Target getters. + */ + function getFirstChild(aAcc) + { + try { return aAcc.getChildAt(0); } + 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.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> +</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 000000000..9a8862bea --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_general.html @@ -0,0 +1,150 @@ +<html> + +<head> + <title>Testing the tree updates</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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).removeChild(getNode(aContainer).firstChild); + } + + 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"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // 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.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> +</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 000000000..2adb9efeb --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_hidden.html @@ -0,0 +1,135 @@ +<!DOCTYPE html> +<html> + +<head> + <title>@hidden attribute testing</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 000000000..c7c06b3d1 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_imagemap.html @@ -0,0 +1,442 @@ +<!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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <script type="application/javascript" + src="../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", + "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", + "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.parentNode.removeChild(this.mapNode); + } + + 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", + "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_list.html b/accessible/tests/mochitest/treeupdate/test_list.html new file mode 100644 index 000000000..9196142d9 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_list.html @@ -0,0 +1,152 @@ +<!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 type="application/javascript" + 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_STATICTEXT, + 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: [ + { STATICTEXT: [] }, + { 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 000000000..d4c178cb9 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html @@ -0,0 +1,106 @@ +<!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 type="application/javascript" + 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_STATICTEXT, + 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_listbox.xul b/accessible/tests/mochitest/treeupdate/test_listbox.xul new file mode 100644 index 000000000..862b4dde8 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_listbox.xul @@ -0,0 +1,180 @@ +<?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 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="../events.js" /> + + <script type="application/javascript"> + <![CDATA[ + //////////////////////////////////////////////////////////////////////////// + // Test + + function insertListitem(aListboxID) + { + this.listboxNode = getNode(aListboxID); + + this.listitemNode = document.createElement("listitem"); + this.listitemNode.setAttribute("label", "item1"); + + 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"> + <listbox id="listbox" rows="2"> + <listitem label="item2"/> + <listitem label="item3"/> + <listitem label="item4"/> + </listbox> + </vbox> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/treeupdate/test_menu.xul b/accessible/tests/mochitest/treeupdate/test_menu.xul new file mode 100644 index 000000000..abdea217e --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_menu.xul @@ -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="Accessible XUL menu hierarchy 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="../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' and 'menu-button'"> + 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.xul b/accessible/tests/mochitest/treeupdate/test_menubutton.xul new file mode 100644 index 000000000..4821a265b --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_menubutton.xul @@ -0,0 +1,198 @@ +<?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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + 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, aMenuItemRole) + { + var menuItemRole = aMenuItemRole || ROLE_MENUITEM; + this.button = getAccessible(aButtonID); + this.menupopup = this.button.firstChild; + + var checker = new invokerChecker(EVENT_REORDER, this.menupopup); + this.__proto__ = new synthClick(aButtonID, checker); + + this.invoke = function openMenu_invoke() + { + var tree = + { PUSHBUTTON: [ + { MENUPOPUP: [ ] } + ] }; + testAccessibleTree(this.button, tree); + + this.__proto__.invoke(); + } + + this.finalCheck = function openMenu_finalCheck() + { + var tree = + { PUSHBUTTON: [ + { MENUPOPUP: [ + { role: menuItemRole, children: [ ] }, + { role: menuItemRole, children: [ ] } + ] } + ] }; + testAccessibleTree(this.button, tree); + + synthesizeKey("VK_ESCAPE", { }); + } + + this.getID = function openMenu_getID() + { + return "open menu of the button " + prettyName(aButtonID); + } + } + + function openMenuButton(aButtonID) + { + this.buttonNode = getNode(aButtonID); + this.menupoupNode = this.buttonNode.firstChild; + + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, this.menupoupNode) + ]; + + this.invoke = function openMenu_invoke() + { + var tree = + { PUSHBUTTON: [ + { MENUPOPUP: [ ] }, + { PUSHBUTTON: [ ] } + ] }; + testAccessibleTree(this.buttonNode, tree); + + this.buttonNode.open = true; + } + + this.finalCheck = function openMenu_finalCheck() + { + var tree = + { PUSHBUTTON: [ + { MENUPOPUP: [ + { MENUITEM: [ ] }, + { MENUITEM: [ ] } + ] }, + { PUSHBUTTON: [ ] } + ] }; + testAccessibleTree(this.buttonNode, tree); + + this.buttonNode.open = false; + } + + this.getID = function openMenu_getID() + { + return "open menu for menu 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 openMenuButton("button2")); + gQueue.push(new openMenu("button3")); + gQueue.push(new openMenuButton("button4")); + + var columnPickerBtn = getAccessible("tree").firstChild.lastChild; + gQueue.push(new openMenu(columnPickerBtn, ROLE_CHECK_MENU_ITEM)); + 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' and 'menu-button'"> + 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> + <button id="button2" type="menu-button" label="menu 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> + <toolbarbutton id="button4" type="menu-button" label="menu 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 000000000..27323bbc3 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_optgroup.html @@ -0,0 +1,137 @@ +<!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 type="application/javascript" + 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 (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: [ + { TEXT_LEAF: [] } + ] }, + { COMBOBOX_OPTION: [ + { TEXT_LEAF: [] } + ] }, + ]}, + { 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.removeChild(this.selectNode.firstChild); + } + + 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() + { + 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 000000000..7754eb703 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_recreation.html @@ -0,0 +1,155 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test accessible recreation</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 recreateAccessible(aID, aWontBeAccessible) + { + this.node = getNode(aID); + this.accessible = + isAccessible(this.node) ? getAccessible(this.node) : null; + + this.eventSeq = [ ]; + + if (this.accessible) + this.eventSeq.push(new invokerChecker(EVENT_HIDE, + this.accessible)); + + if (!aWontBeAccessible) + this.eventSeq.push(new invokerChecker(EVENT_SHOW, getAccessible, + this.node)); + + this.eventSeq.push(new invokerChecker(EVENT_REORDER, + getContainerAccessible(this.node))); + + if (this.accessible) { + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_SHOW, this.accessible) + ]; + } + } + + function changeAttr(aID, aAttr, aValue) + { + this.__proto__ = new recreateAccessible(aID); + + this.invoke = function changeAttr_invoke() + { + this.node.setAttribute(aAttr, aValue); + } + + this.getID = function changeAttr_getID() + { + return "change " + aAttr + "attribute for " + aID; + } + } + + function removeAttr(aID, aAttr) + { + this.__proto__ = new recreateAccessible(aID, true); + + this.invoke = function remvoeAttr_invoke() + { + this.node.removeAttribute(aAttr); + } + + this.getID = function remvoeAttr_getID() + { + return "remove " + aAttr + "attribute for " + aID; + } + } + + function changeRole(aID, aHasAccessible) + { + this.__proto__ = new changeAttr(aID, "role", "button"); + } + + function removeRole(aID) + { + this.__proto__ = new removeAttr(aID, "role"); + } + + function changeHref(aID) + { + this.__proto__ = new changeAttr(aID, "href", "www"); + } + + function changeMultiselectable(aID) + { + this.__proto__ = new changeAttr(aID, "aria-multiselectable", "true"); + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() + { + gQueue = new eventQueue(); + + // make the accessible an inaccessible + gQueue.push(new changeRole("span")); + + // make the inaccessible an accessible + gQueue.push(new removeRole("span")); + + // recreate an accessible by role change + gQueue.push(new changeRole("div1")); + + // recreate an accessible by href change + gQueue.push(new changeHref("anchor")); + + // recreate an accessible by aria-multiselectable change + gQueue.push(new changeMultiselectable("div3")); + + 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> + + <span id="span">span</span> + <div id="div1">div</div> + <a id="anchor">anchor</a> + <div id="div3" role="listbox">list</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 000000000..006618b80 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_select.html @@ -0,0 +1,130 @@ +<!DOCTYPE html> +<html> +<head> + <title>Add select options test</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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 (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: [ + { TEXT_LEAF: [] } + ] }, + { COMBOBOX_OPTION: [ + { TEXT_LEAF: [] } + ] } + ] } + ] }; + 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"; + } + } + + //gA11yEventDumpID = "debug"; + + function doTest() + { + gQueue = new eventQueue(); + + gQueue.push(new addOptions("select")); + gQueue.push(new removeOptions("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"> + Mozilla Bug 616452</a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=616940" + title="Removed select option accessibles aren't removed until hide event is fired"> + Mozilla Bug 616940</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_shutdown.xul b/accessible/tests/mochitest/treeupdate/test_shutdown.xul new file mode 100644 index 000000000..2e6f7a7b3 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_shutdown.xul @@ -0,0 +1,132 @@ +<?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 type="application/javascript" + 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).parentNode.removeChild(getNode(aID)); + }; + + 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 000000000..abadefdb0 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_table.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<html> +<head> + <title>Table update tests</title> + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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() + { + 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 000000000..16d3a1a2b --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_textleaf.html @@ -0,0 +1,180 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test accessible recreation</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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_visibility.html b/accessible/tests/mochitest/treeupdate/test_visibility.html new file mode 100644 index 000000000..a1c130fb6 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_visibility.html @@ -0,0 +1,437 @@ +<!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 type="application/javascript" + 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> + <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 000000000..e7ba9b059 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_whitespace.html @@ -0,0 +1,187 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Whitespace text accessible creation/desctruction</title> + + <link rel="stylesheet" type="text/css" + href="chrome://mochikit/content/tests/SimpleTest/test.css" /> + + <script type="application/javascript" + 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"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() + { + 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> + + <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="eventdump"></div> +</body> +</html> diff --git a/accessible/tests/mochitest/treeview.css b/accessible/tests/mochitest/treeview.css new file mode 100644 index 000000000..1bffb1798 --- /dev/null +++ b/accessible/tests/mochitest/treeview.css @@ -0,0 +1,15 @@ +treechildren::-moz-tree-checkbox(checked) { + list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif"); +} + +treechildren::-moz-tree-image(cyclerState1) { + list-style-image: url("chrome://global/skin/console/bullet-question.png"); +} + +treechildren::-moz-tree-image(cyclerState2) { + list-style-image: url("chrome://global/skin/console/bullet-warning.png"); +} + +treechildren::-moz-tree-image(cyclerState3) { + list-style-image: url("chrome://global/skin/console/bullet-error.png"); +} diff --git a/accessible/tests/mochitest/treeview.js b/accessible/tests/mochitest/treeview.js new file mode 100644 index 000000000..869471c85 --- /dev/null +++ b/accessible/tests/mochitest/treeview.js @@ -0,0 +1,289 @@ +/** + * 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; + + function loadXULTree(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); + } + } + + gXULTreeLoadContext.queue = new eventQueue(); + gXULTreeLoadContext.queue.push(new loadXULTree(treeID, treeView)); + 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) {}, + getProgressMode: function getProgressMode(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; + }, + isSelectable: function isSelectable(aRow, aCol) {}, + 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); + }, + performAction: function performAction(aAction) {}, + performActionOnRow: function performActionOnRow(aAction, aRow) {}, + performActionOnCell: function performActionOnCell(aAction, 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 000000000..cc9dffe00 --- /dev/null +++ b/accessible/tests/mochitest/value.js @@ -0,0 +1,32 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 000000000..b93d9b180 --- /dev/null +++ b/accessible/tests/mochitest/value/a11y.ini @@ -0,0 +1,9 @@ +[DEFAULT] +support-files = + !/accessible/tests/mochitest/*.js + +[test_general.html] +[test_number.html] +[test_progress.html] +[test_progress.xul] +[test_range.html] diff --git a/accessible/tests/mochitest/value/test_general.html b/accessible/tests/mochitest/value/test_general.html new file mode 100644 index 000000000..12e718ba1 --- /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 type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <script type="application/javascript" + src="../common.js"></script> + + <script type="application/javascript" + 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 rootDir = getRootDirectory(window.location.href); + 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", ""); + + // roles that can live as HTMLLinkAccessibles + testValue("aria_link_link", href); + testValue("aria_main_link", href); + testValue("aria_navigation_link", href); + + ////////////////////////////////////////////////////////////////////////// + // ARIA textboxes + + testValue("aria_textbox1", "helo"); + + ////////////////////////////////////////////////////////////////////////// + // 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_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_number.html b/accessible/tests/mochitest/value/test_number.html new file mode 100644 index 000000000..68825b445 --- /dev/null +++ b/accessible/tests/mochitest/value/test_number.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 type="application/javascript" + 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 type="application/javascript" + 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 000000000..b99cce14c --- /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 type="application/javascript" + 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 type="application/javascript" + 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); + + 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> +</body> +</html> diff --git a/accessible/tests/mochitest/value/test_progress.xul b/accessible/tests/mochitest/value/test_progress.xul new file mode 100644 index 000000000..5ae4a358f --- /dev/null +++ b/accessible/tests/mochitest/value/test_progress.xul @@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="XUL progressmeter 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="../value.js" /> + + <script type="application/javascript"> + <![CDATA[ + function doTest() + { + // progressmeter + testValue("pm1", "50%", 50, 0, 100, 0); + testValue("pm2", "50%", 500, 0, 1000, 0); + testValue("pm3", "", 0, 0, 100, 0); + + // scale + testValue("sc1", "500", 500, 0, 1000, 10); + testValue("sc2", "", 0, 0, 0, 0); + + // aria progressbar + testValue("ariapb1", "500", 500, 0, 1000, 0); + testValue("ariapb2", "", 0, 0, 0, 0); + + 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=489551" + title="Values of sliders and progress bars in HTML 5 audio and video element's control sets are not percentages"> + Mozilla Bug 489551 + </a><br/> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <!-- progressmeter --> + <progressmeter id="pm1" value="50"/> + <progressmeter id="pm2" value="500" max="1000"/> + <progressmeter id="pm3"/> + + <!-- scale --> + <scale id="sc1" value="500" max="1000" increment="10"/> + <scale id="sc2"/> + + <!-- aria --> + <description id="ariapb1" role="progressbar" + aria-valuenow="500" aria-valuemin="0" aria-valuemax="1000"/> + <description id="ariapb2" role="progressbar"/> + </hbox> + +</window> + diff --git a/accessible/tests/mochitest/value/test_range.html b/accessible/tests/mochitest/value/test_range.html new file mode 100644 index 000000000..b795f7a79 --- /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 type="application/javascript" + 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 type="application/javascript" + 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); + + 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"> +</body> +</html> |