diff options
Diffstat (limited to 'accessible/tests/browser')
48 files changed, 4299 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); + } +} |