Skip to content

fix(a11y): focus monitor incorrectly detecting fake mousedown from screen reader #15214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/cdk/a11y/focus-monitor/focus-monitor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {
dispatchKeyboardEvent,
dispatchMouseEvent,
patchElementFocus,
createMouseEvent,
dispatchEvent,
} from '@angular/cdk/testing/private';
import {Component, NgZone} from '@angular/core';
import {ComponentFixture, fakeAsync, flush, inject, TestBed, tick} from '@angular/core/testing';
Expand Down Expand Up @@ -112,6 +114,26 @@ describe('FocusMonitor', () => {
expect(changeHandler).toHaveBeenCalledWith('program');
}));

it('should detect fake mousedown from a screen reader', fakeAsync(() => {
// Simulate focus via a fake mousedown from a screen reader.
dispatchMouseEvent(buttonElement, 'mousedown');
const event = createMouseEvent('mousedown');
Object.defineProperty(event, 'buttons', {get: () => 0});
dispatchEvent(buttonElement, event);

buttonElement.focus();
fixture.detectChanges();
flush();

expect(buttonElement.classList.length)
.toBe(2, 'button should have exactly 2 focus classes');
expect(buttonElement.classList.contains('cdk-focused'))
.toBe(true, 'button should have cdk-focused class');
expect(buttonElement.classList.contains('cdk-keyboard-focused'))
.toBe(true, 'button should have cdk-keyboard-focused class');
expect(changeHandler).toHaveBeenCalledWith('keyboard');
}));

it('focusVia keyboard should simulate keyboard focus', fakeAsync(() => {
focusMonitor.focusVia(buttonElement, 'keyboard');
flush();
Expand Down
8 changes: 6 additions & 2 deletions src/cdk/a11y/focus-monitor/focus-monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import {Observable, of as observableOf, Subject, Subscription} from 'rxjs';
import {coerceElement} from '@angular/cdk/coercion';
import {DOCUMENT} from '@angular/common';
import {isFakeMousedownFromScreenReader} from '../fake-mousedown';


// This is the value used by AngularJS Material. Through trial and error (on iPhone 6S) they found
Expand Down Expand Up @@ -99,11 +100,14 @@ export class FocusMonitor implements OnDestroy {
* Event listener for `mousedown` events on the document.
* Needs to be an arrow function in order to preserve the context when it gets bound.
*/
private _documentMousedownListener = () => {
private _documentMousedownListener = (event: MouseEvent) => {
// On mousedown record the origin only if there is not touch
// target, since a mousedown can happen as a result of a touch event.
if (!this._lastTouchTarget) {
this._setOriginForCurrentEventQueue('mouse');
// In some cases screen readers fire fake `mousedown` events instead of `keydown`.
// Resolve the focus source to `keyboard` if we detect one of them.
const source = isFakeMousedownFromScreenReader(event) ? 'keyboard' : 'mouse';
this._setOriginForCurrentEventQueue(source);
}
}

Expand Down