Skip to content

Commit

Permalink
fix(cdk/a11y): not detecting fake mousedown on firefox (#23493)
Browse files Browse the repository at this point in the history
A few months ago we changed the fake `mousedown` detection to use coordinates instead of the `buttons` property. This seems to have broken the detection on NVDA + Firefox on Windows where `buttons === 0`, but `offsetX` and `offsetY` are simulated to non-zero numbers.

These changes re-add the `buttons` check and keep the `offsetX` and `offsetY` checks for Webkit-based browsers.

(cherry picked from commit 33a07d2)
  • Loading branch information
crisbeto authored and andrewseguin committed Jan 18, 2022
1 parent 761f9f2 commit 109d5a1
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 6 deletions.
10 changes: 6 additions & 4 deletions src/cdk/a11y/fake-event-detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
export function isFakeMousedownFromScreenReader(event: MouseEvent): boolean {
// Some screen readers will dispatch a fake `mousedown` event when pressing enter or space on
// a clickable element. We can distinguish these events when both `offsetX` and `offsetY` are
// zero. Note that there's an edge case where the user could click the 0x0 spot of the screen
// themselves, but that is unlikely to contain interaction elements. Historically we used to
// check `event.buttons === 0`, however that no longer works on recent versions of NVDA.
return event.offsetX === 0 && event.offsetY === 0;
// zero or `event.buttons` is zero, depending on the browser:
// - `event.buttons` works on Firefox, but fails on Chrome.
// - `offsetX` and `offsetY` work on Chrome, but fail on Firefox.
// Note that there's an edge case where the user could click the 0x0 spot of the
// screen themselves, but that is unlikely to contain interactive elements.
return event.buttons === 0 || (event.offsetX === 0 && event.offsetY === 0);
}

/** Gets whether an event could be a faked `touchstart` event dispatched by a screen reader. */
Expand Down
22 changes: 21 additions & 1 deletion src/cdk/a11y/focus-monitor/focus-monitor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ describe('FocusMonitor', () => {
expect(changeHandler).toHaveBeenCalledWith('program');
}));

it('should detect fake mousedown from a screen reader', fakeAsync(() => {
it('should detect fake mousedown from a screen reader on Chrome', fakeAsync(() => {
// Simulate focus via a fake mousedown from a screen reader.
dispatchMouseEvent(buttonElement, 'mousedown');
const event = createMouseEvent('mousedown');
Expand All @@ -184,6 +184,26 @@ describe('FocusMonitor', () => {
expect(changeHandler).toHaveBeenCalledWith('keyboard');
}));

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

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

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

it('focusVia keyboard should simulate keyboard focus', fakeAsync(() => {
focusMonitor.focusVia(buttonElement, 'keyboard');
flush();
Expand Down
13 changes: 12 additions & 1 deletion src/cdk/a11y/input-modality/input-modality-detector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ describe('InputModalityDetector', () => {
expect(emitted).toEqual(['keyboard', 'mouse', 'touch', 'keyboard']);
});

it('should detect fake screen reader mouse events as keyboard input modality', () => {
it('should detect fake screen reader mouse events as keyboard input modality on Chrome', () => {
setupTest();

// Create a fake screen-reader mouse event.
Expand All @@ -144,6 +144,17 @@ describe('InputModalityDetector', () => {
expect(detector.mostRecentModality).toBe('keyboard');
});

it('should detect fake screen reader mouse events as keyboard input modality on Firefox', () => {
setupTest();

// Create a fake screen-reader mouse event.
const event = createMouseEvent('mousedown');
Object.defineProperties(event, {buttons: {get: () => 0}});
dispatchEvent(document, event);

expect(detector.mostRecentModality).toBe('keyboard');
});

it('should detect fake screen reader touch events as keyboard input modality', () => {
setupTest();

Expand Down

0 comments on commit 109d5a1

Please sign in to comment.