123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- /* 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 PrefCache, Roles, Prefilters, States, Filters, Utils,
- TraversalRules, Components, XPCOMUtils */
- /* exported TraversalRules, TraversalHelper */
- 'use strict';
- const Ci = Components.interfaces;
- const Cu = Components.utils;
- this.EXPORTED_SYMBOLS = ['TraversalRules', 'TraversalHelper']; // jshint ignore:line
- Cu.import('resource://gre/modules/accessibility/Utils.jsm');
- Cu.import('resource://gre/modules/XPCOMUtils.jsm');
- XPCOMUtils.defineLazyModuleGetter(this, 'Roles', // jshint ignore:line
- 'resource://gre/modules/accessibility/Constants.jsm');
- XPCOMUtils.defineLazyModuleGetter(this, 'Filters', // jshint ignore:line
- 'resource://gre/modules/accessibility/Constants.jsm');
- XPCOMUtils.defineLazyModuleGetter(this, 'States', // jshint ignore:line
- 'resource://gre/modules/accessibility/Constants.jsm');
- XPCOMUtils.defineLazyModuleGetter(this, 'Prefilters', // jshint ignore:line
- 'resource://gre/modules/accessibility/Constants.jsm');
- var gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images');
- function BaseTraversalRule(aRoles, aMatchFunc, aPreFilter, aContainerRule) {
- this._explicitMatchRoles = new Set(aRoles);
- this._matchRoles = aRoles;
- if (aRoles.length) {
- if (aRoles.indexOf(Roles.LABEL) < 0) {
- this._matchRoles.push(Roles.LABEL);
- }
- if (aRoles.indexOf(Roles.INTERNAL_FRAME) < 0) {
- // Used for traversing in to child OOP frames.
- this._matchRoles.push(Roles.INTERNAL_FRAME);
- }
- }
- this._matchFunc = aMatchFunc || function() { return Filters.MATCH; };
- this.preFilter = aPreFilter || gSimplePreFilter;
- this.containerRule = aContainerRule;
- }
- BaseTraversalRule.prototype = {
- getMatchRoles: function BaseTraversalRule_getmatchRoles(aRoles) {
- aRoles.value = this._matchRoles;
- return aRoles.value.length;
- },
- match: function BaseTraversalRule_match(aAccessible)
- {
- let role = aAccessible.role;
- if (role == Roles.INTERNAL_FRAME) {
- return (Utils.getMessageManager(aAccessible.DOMNode)) ?
- Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
- }
- let matchResult =
- (this._explicitMatchRoles.has(role) || !this._explicitMatchRoles.size) ?
- this._matchFunc(aAccessible) : Filters.IGNORE;
- // If we are on a label that nests a checkbox/radio we should land on it.
- // It is a bigger touch target, and it reduces clutter.
- if (role == Roles.LABEL && !(matchResult & Filters.IGNORE_SUBTREE)) {
- let control = Utils.getEmbeddedControl(aAccessible);
- if (control && this._explicitMatchRoles.has(control.role)) {
- matchResult = this._matchFunc(control) | Filters.IGNORE_SUBTREE;
- }
- }
- return matchResult;
- },
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIAccessibleTraversalRule])
- };
- var gSimpleTraversalRoles =
- [Roles.MENUITEM,
- Roles.LINK,
- Roles.PAGETAB,
- Roles.GRAPHIC,
- Roles.STATICTEXT,
- Roles.TEXT_LEAF,
- Roles.PUSHBUTTON,
- Roles.CHECKBUTTON,
- Roles.RADIOBUTTON,
- Roles.COMBOBOX,
- Roles.PROGRESSBAR,
- Roles.BUTTONDROPDOWN,
- Roles.BUTTONMENU,
- Roles.CHECK_MENU_ITEM,
- Roles.PASSWORD_TEXT,
- Roles.RADIO_MENU_ITEM,
- Roles.TOGGLE_BUTTON,
- Roles.ENTRY,
- Roles.KEY,
- Roles.HEADER,
- Roles.HEADING,
- Roles.SLIDER,
- Roles.SPINBUTTON,
- Roles.OPTION,
- Roles.LISTITEM,
- Roles.GRID_CELL,
- Roles.COLUMNHEADER,
- Roles.ROWHEADER,
- Roles.STATUSBAR,
- Roles.SWITCH,
- Roles.MATHML_MATH];
- var gSimpleMatchFunc = function gSimpleMatchFunc(aAccessible) {
- // An object is simple, if it either has a single child lineage,
- // or has a flat subtree.
- function isSingleLineage(acc) {
- for (let child = acc; child; child = child.firstChild) {
- if (Utils.visibleChildCount(child) > 1) {
- return false;
- }
- }
- return true;
- }
- function isFlatSubtree(acc) {
- for (let child = acc.firstChild; child; child = child.nextSibling) {
- // text leafs inherit the actionCount of any ancestor that has a click
- // listener.
- if ([Roles.TEXT_LEAF, Roles.STATICTEXT].indexOf(child.role) >= 0) {
- continue;
- }
- if (Utils.visibleChildCount(child) > 0 || child.actionCount > 0) {
- return false;
- }
- }
- return true;
- }
- switch (aAccessible.role) {
- case Roles.COMBOBOX:
- // We don't want to ignore the subtree because this is often
- // where the list box hangs out.
- return Filters.MATCH;
- case Roles.TEXT_LEAF:
- {
- // Nameless text leaves are boring, skip them.
- let name = aAccessible.name;
- return (name && name.trim()) ? Filters.MATCH : Filters.IGNORE;
- }
- case Roles.STATICTEXT:
- // Ignore prefix static text in list items. They are typically bullets or numbers.
- return Utils.isListItemDecorator(aAccessible) ?
- Filters.IGNORE : Filters.MATCH;
- case Roles.GRAPHIC:
- return TraversalRules._shouldSkipImage(aAccessible);
- case Roles.HEADER:
- case Roles.HEADING:
- case Roles.COLUMNHEADER:
- case Roles.ROWHEADER:
- case Roles.STATUSBAR:
- if ((aAccessible.childCount > 0 || aAccessible.name) &&
- (isSingleLineage(aAccessible) || isFlatSubtree(aAccessible))) {
- return Filters.MATCH | Filters.IGNORE_SUBTREE;
- }
- return Filters.IGNORE;
- case Roles.GRID_CELL:
- return isSingleLineage(aAccessible) || isFlatSubtree(aAccessible) ?
- Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
- case Roles.LISTITEM:
- {
- let item = aAccessible.childCount === 2 &&
- aAccessible.firstChild.role === Roles.STATICTEXT ?
- aAccessible.lastChild : aAccessible;
- return isSingleLineage(item) || isFlatSubtree(item) ?
- Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
- }
- default:
- // Ignore the subtree, if there is one. So that we don't land on
- // the same content that was already presented by its parent.
- return Filters.MATCH |
- Filters.IGNORE_SUBTREE;
- }
- };
- var gSimplePreFilter = Prefilters.DEFUNCT |
- Prefilters.INVISIBLE |
- Prefilters.ARIA_HIDDEN |
- Prefilters.TRANSPARENT;
- this.TraversalRules = { // jshint ignore:line
- Simple: new BaseTraversalRule(gSimpleTraversalRoles, gSimpleMatchFunc),
- SimpleOnScreen: new BaseTraversalRule(
- gSimpleTraversalRoles, gSimpleMatchFunc,
- Prefilters.DEFUNCT | Prefilters.INVISIBLE | Prefilters.ARIA_HIDDEN |
- Prefilters.TRANSPARENT | Prefilters.OFFSCREEN),
- Anchor: new BaseTraversalRule(
- [Roles.LINK],
- function Anchor_match(aAccessible)
- {
- // We want to ignore links, only focus named anchors.
- if (Utils.getState(aAccessible).contains(States.LINKED)) {
- return Filters.IGNORE;
- } else {
- return Filters.MATCH;
- }
- }),
- Button: new BaseTraversalRule(
- [Roles.PUSHBUTTON,
- Roles.SPINBUTTON,
- Roles.TOGGLE_BUTTON,
- Roles.BUTTONDROPDOWN,
- Roles.BUTTONDROPDOWNGRID]),
- Combobox: new BaseTraversalRule(
- [Roles.COMBOBOX,
- Roles.LISTBOX]),
- Landmark: new BaseTraversalRule(
- [],
- function Landmark_match(aAccessible) {
- return Utils.getLandmarkName(aAccessible) ? Filters.MATCH :
- Filters.IGNORE;
- }, null, true),
- /* A rule for Android's section navigation, lands on landmarks, regions, and
- on headings to aid navigation of traditionally structured documents */
- Section: new BaseTraversalRule(
- [],
- function Section_match(aAccessible) {
- if (aAccessible.role === Roles.HEADING) {
- return Filters.MATCH;
- }
- let matchedRole = Utils.matchRoles(aAccessible, [
- 'banner',
- 'complementary',
- 'contentinfo',
- 'main',
- 'navigation',
- 'search',
- 'region'
- ]);
- return matchedRole ? Filters.MATCH : Filters.IGNORE;
- }, null, true),
- Entry: new BaseTraversalRule(
- [Roles.ENTRY,
- Roles.PASSWORD_TEXT]),
- FormElement: new BaseTraversalRule(
- [Roles.PUSHBUTTON,
- Roles.SPINBUTTON,
- Roles.TOGGLE_BUTTON,
- Roles.BUTTONDROPDOWN,
- Roles.BUTTONDROPDOWNGRID,
- Roles.COMBOBOX,
- Roles.LISTBOX,
- Roles.ENTRY,
- Roles.PASSWORD_TEXT,
- Roles.PAGETAB,
- Roles.RADIOBUTTON,
- Roles.RADIO_MENU_ITEM,
- Roles.SLIDER,
- Roles.CHECKBUTTON,
- Roles.CHECK_MENU_ITEM,
- Roles.SWITCH]),
- Graphic: new BaseTraversalRule(
- [Roles.GRAPHIC],
- function Graphic_match(aAccessible) {
- return TraversalRules._shouldSkipImage(aAccessible);
- }),
- Heading: new BaseTraversalRule(
- [Roles.HEADING],
- function Heading_match(aAccessible) {
- return aAccessible.childCount > 0 ? Filters.MATCH : Filters.IGNORE;
- }),
- ListItem: new BaseTraversalRule(
- [Roles.LISTITEM,
- Roles.TERM]),
- Link: new BaseTraversalRule(
- [Roles.LINK],
- function Link_match(aAccessible)
- {
- // We want to ignore anchors, only focus real links.
- if (Utils.getState(aAccessible).contains(States.LINKED)) {
- return Filters.MATCH;
- } else {
- return Filters.IGNORE;
- }
- }),
- /* For TalkBack's "Control" granularity. Form conrols and links */
- Control: new BaseTraversalRule(
- [Roles.PUSHBUTTON,
- Roles.SPINBUTTON,
- Roles.TOGGLE_BUTTON,
- Roles.BUTTONDROPDOWN,
- Roles.BUTTONDROPDOWNGRID,
- Roles.COMBOBOX,
- Roles.LISTBOX,
- Roles.ENTRY,
- Roles.PASSWORD_TEXT,
- Roles.PAGETAB,
- Roles.RADIOBUTTON,
- Roles.RADIO_MENU_ITEM,
- Roles.SLIDER,
- Roles.CHECKBUTTON,
- Roles.CHECK_MENU_ITEM,
- Roles.SWITCH,
- Roles.LINK,
- Roles.MENUITEM],
- function Control_match(aAccessible)
- {
- // We want to ignore anchors, only focus real links.
- if (aAccessible.role == Roles.LINK &&
- !Utils.getState(aAccessible).contains(States.LINKED)) {
- return Filters.IGNORE;
- }
- return Filters.MATCH;
- }),
- List: new BaseTraversalRule(
- [Roles.LIST,
- Roles.DEFINITION_LIST],
- null, null, true),
- PageTab: new BaseTraversalRule(
- [Roles.PAGETAB]),
- Paragraph: new BaseTraversalRule(
- [Roles.PARAGRAPH,
- Roles.SECTION],
- function Paragraph_match(aAccessible) {
- for (let child = aAccessible.firstChild; child; child = child.nextSibling) {
- if (child.role === Roles.TEXT_LEAF) {
- return Filters.MATCH | Filters.IGNORE_SUBTREE;
- }
- }
- return Filters.IGNORE;
- }),
- RadioButton: new BaseTraversalRule(
- [Roles.RADIOBUTTON,
- Roles.RADIO_MENU_ITEM]),
- Separator: new BaseTraversalRule(
- [Roles.SEPARATOR]),
- Table: new BaseTraversalRule(
- [Roles.TABLE]),
- Checkbox: new BaseTraversalRule(
- [Roles.CHECKBUTTON,
- Roles.CHECK_MENU_ITEM,
- Roles.SWITCH /* A type of checkbox that represents on/off values */]),
- _shouldSkipImage: function _shouldSkipImage(aAccessible) {
- if (gSkipEmptyImages.value && aAccessible.name === '') {
- return Filters.IGNORE;
- }
- return Filters.MATCH;
- }
- };
- this.TraversalHelper = {
- _helperPivotCache: null,
- get helperPivotCache() {
- delete this.helperPivotCache;
- this.helperPivotCache = new WeakMap();
- return this.helperPivotCache;
- },
- getHelperPivot: function TraversalHelper_getHelperPivot(aRoot) {
- let pivot = this.helperPivotCache.get(aRoot.DOMNode);
- if (!pivot) {
- pivot = Utils.AccService.createAccessiblePivot(aRoot);
- this.helperPivotCache.set(aRoot.DOMNode, pivot);
- }
- return pivot;
- },
- move: function TraversalHelper_move(aVirtualCursor, aMethod, aRule) {
- let rule = TraversalRules[aRule];
- if (rule.containerRule) {
- let moved = false;
- let helperPivot = this.getHelperPivot(aVirtualCursor.root);
- helperPivot.position = aVirtualCursor.position;
- // We continue to step through containers until there is one with an
- // atomic child (via 'Simple') on which we could land.
- while (!moved) {
- if (helperPivot[aMethod](rule)) {
- aVirtualCursor.modalRoot = helperPivot.position;
- moved = aVirtualCursor.moveFirst(TraversalRules.Simple);
- aVirtualCursor.modalRoot = null;
- } else {
- // If we failed to step to another container, break and return false.
- break;
- }
- }
- return moved;
- } else {
- return aVirtualCursor[aMethod](rule);
- }
- }
- };
|