From c7c3ddedabcc1ca364899fcf4f7ec83f20467de5 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 30 Aug 2021 22:02:48 +0200 Subject: [PATCH] fix(cdk/a11y): not detecting fake mousedown on firefox 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. --- src/cdk/a11y/fake-event-detection.ts | 10 +++++---- .../a11y/focus-monitor/focus-monitor.spec.ts | 22 ++++++++++++++++++- .../input-modality-detector.spec.ts | 13 ++++++++++- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/cdk/a11y/fake-event-detection.ts b/src/cdk/a11y/fake-event-detection.ts index 2cb20bc02049..bffe2cf5fc3d 100644 --- a/src/cdk/a11y/fake-event-detection.ts +++ b/src/cdk/a11y/fake-event-detection.ts @@ -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. */ diff --git a/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts b/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts index c4378b5dd100..bb9ed2f92b77 100644 --- a/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts +++ b/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts @@ -147,7 +147,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'); @@ -167,6 +167,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(); diff --git a/src/cdk/a11y/input-modality/input-modality-detector.spec.ts b/src/cdk/a11y/input-modality/input-modality-detector.spec.ts index b582eb33223b..044e45a24ea7 100644 --- a/src/cdk/a11y/input-modality/input-modality-detector.spec.ts +++ b/src/cdk/a11y/input-modality/input-modality-detector.spec.ts @@ -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. @@ -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();