Skip to content

Commit

Permalink
change way elements are added/removed from focus for JAWS in Chrome, …
Browse files Browse the repository at this point in the history
…see #893
  • Loading branch information
jessegreenberg committed Nov 12, 2018
1 parent 077aae9 commit 8dce87d
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 6 deletions.
2 changes: 1 addition & 1 deletion js/accessibility/Accessibility.js
Original file line number Diff line number Diff line change
Expand Up @@ -2446,7 +2446,7 @@ define( function( require ) {

for ( var i = 0; i < this._accessibleInstances.length; i++ ) {
var peer = this._accessibleInstances[ i ].peer;
peer.setAttributeToElement( 'tabIndex', this.focusable ? 0 : -1 );
AccessibilityUtil.overrideFocusWithTabIndex( peer.primarySibling, focusable );
}
}
},
Expand Down
1 change: 0 additions & 1 deletion js/accessibility/AccessibilityTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,6 @@ define( function( require ) {
a.focus();
util.getNextFocusable( rootElement ).focus();
assert.ok( document.activeElement.id === aElement.id, 'a only element focusable' );

} );

QUnit.test( 'Remove accessibility subtree', function( assert ) {
Expand Down
38 changes: 35 additions & 3 deletions js/accessibility/AccessibilityUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,9 @@ define( function( require ) {
return false;
}

// if tabindex is greater than -1, the element is focusable so break
return domElement.tabIndex >= 0;
// focusable if flagged as such with data attribute - cannot check tabindex because IE11 and Edge assign
// tabIndex=0 internally for all HTML elements
return domElement.getAttribute( 'data-focusable' ) === 'true';
}

/**
Expand Down Expand Up @@ -472,7 +473,8 @@ define( function( require ) {
: document.createElement( tagName );
var upperCaseTagName = tagName.toUpperCase();

domElement.tabIndex = focusable ? 0 : -1;
// set tab index if we are overriding default browser behavior
AccessibilityUtil.overrideFocusWithTabIndex( domElement, focusable );

// Safari requires that certain input elements have dimension, otherwise it will not be keyboard accessible
if ( _.includes( ELEMENTS_REQUIRE_WIDTH, upperCaseTagName ) ) {
Expand All @@ -483,6 +485,36 @@ define( function( require ) {
return domElement;
},

/**
* Add a tab index to an element when overriding the default focus behavior for the element. Adding tabindex
* to an element can only be done when overriding the default browser behavior because tabindex interferes with
* the way JAWS reads through content on Chrome, see https://github.com/phetsims/scenery/issues/893
*
* If default behavior and focusable align, the tabindex attribute is removed so that can't interfere with a
* screen reader.
* @public (scenery-internal)
*
* @param {HTMLElement} element
* @param {boolean} focusable
*/
overrideFocusWithTabIndex: function( element, focusable ) {
var defaultFocusable = AccessibilityUtil.tagIsDefaultFocusable( element.tagName );

// only add a tabindex when we are overriding the default focusable bahvior of the browser for the tag name
// (logical xor)
if ( defaultFocusable ? !focusable : focusable ) {
element.tabIndex = focusable ? 0 : -1;
}
else {
element.removeAttribute( 'tabindex' );
}

// flag for IE11, so that we can track detect whether the element is focusable, since IE11 adds tabIndex=0 on
// ALL HTML elements, even those that are not focusable
element.setAttribute( 'data-focusable', focusable );

},

TAGS: {
INPUT: INPUT_TAG,
LABEL: LABEL_TAG,
Expand Down
52 changes: 51 additions & 1 deletion js/accessibility/AccessibilityUtilTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ define( function( require ) {

QUnit.module( 'AccessibilityUtilTests' );


// tests
QUnit.test( 'insertElements', function( assert ) {

var div1 = document.createElement( 'div1' );
Expand Down Expand Up @@ -42,4 +42,54 @@ define( function( require ) {
assert.ok( div1.childNodes[5] === div4, 'inserted div4 order of elements');
} );

QUnit.test( 'getNextPreviousFocusable', function( assert ) {
var parent = AccessibilityUtil.createElement( 'div', false );

var button = AccessibilityUtil.createElement( 'button', true ); // focusable
var div = AccessibilityUtil.createElement( 'div', true ); // focusable
var p = AccessibilityUtil.createElement( 'p', false ); // not focusable

// elements must be in DOM to be focusable
document.body.appendChild( parent );
parent.appendChild( button );
parent.appendChild( div );
parent.appendChild( p );

var firstFocusable = AccessibilityUtil.getNextFocusable( parent );
assert.ok( firstFocusable === button, 'first focusable found' );
firstFocusable.focus();

var secondFocusable = AccessibilityUtil.getNextFocusable( parent );
assert.ok( secondFocusable === div, 'second focusable found' );
secondFocusable.focus();

// should still return the div because the p isn't focusable
var thirdFocusable = AccessibilityUtil.getNextFocusable( parent );
assert.ok( thirdFocusable === div, 'no more focusables after div' );

// remove the DOM nodes so they don't clutter the tests
document.body.removeChild( parent );
} );

QUnit.test( 'overrideFocusWithTabIndex', function ( assert ) {

// tab index should only be set on elements where we are overriding what is being done natively in the browser
var defaultButton = AccessibilityUtil.createElement( 'button', true ); // focusable
var defaultParagraph = AccessibilityUtil.createElement( 'p', false ); // not focusable
var defaultDiv = AccessibilityUtil.createElement( 'div', false ); // not focusable

// use getAttribute because tabIndex DOM property is always provided by default
assert.ok( defaultButton.getAttribute( 'tabindex' ) === null, 'default button has no tab index' );
assert.ok( defaultParagraph.getAttribute( 'tabindex' ) === null, 'default paragraph has no tab index' );
assert.ok( defaultDiv.getAttribute( 'tabindex' ) === null, 'default div has no tab index' );

// custom focusability should all have tab indices, even those that are being removed from the document
var customButton = AccessibilityUtil.createElement( 'button', false ); // not focusable
var customParagraph = AccessibilityUtil.createElement( 'p', true ); // focusable
var customDiv = AccessibilityUtil.createElement( 'div', true ); // focusable

assert.ok( customButton.getAttribute( 'tabindex' ) === '-1', 'custom button removed from focus' );
assert.ok( customParagraph.getAttribute( 'tabindex' ) === '0', 'custom paragraph added to focus' );
assert.ok( customDiv.getAttribute( 'tabindex' ) === '0', 'custom button removed from focus' );
} );
} );

0 comments on commit 8dce87d

Please sign in to comment.