Skip to content

Commit a18d26f

Browse files
authored
fix(cdk/a11y): fake mousedown detection not working (#23029)
Our approach to detecting fake `mousedown` events from screen readers doesn't appear to work with a recent version of NVDA. These changes switch to using `offsetX` and `offsetY` instead. Fixes #22549.
1 parent 72ae5a2 commit a18d26f

File tree

5 files changed

+13
-10
lines changed

5 files changed

+13
-10
lines changed

src/cdk/a11y/fake-event-detection.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88

99
/** Gets whether an event could be a faked `mousedown` event dispatched by a screen reader. */
1010
export function isFakeMousedownFromScreenReader(event: MouseEvent): boolean {
11-
// We can typically distinguish between these faked mousedown events and real mousedown events
12-
// using the "buttons" property. While real mousedowns will indicate the mouse button that was
13-
// pressed (e.g. "1" for the left mouse button), faked mousedowns will usually set the property
14-
// value to 0.
15-
return event.buttons === 0;
11+
// Some screen readers will dispatch a fake `mousedown` event when pressing enter or space on
12+
// a clickable element. We can distinguish these events when both `offsetX` and `offsetY` are
13+
// zero. Note that there's an edge case where the user could click the 0x0 spot of the screen
14+
// themselves, but that is unlikely to contain interaction elements. Historially we used to check
15+
// `event.buttons === 0`, however that no longer works on recent versions of NVDA.
16+
return event.offsetX === 0 && event.offsetY === 0;
1617
}
1718

1819
/** Gets whether an event could be a faked `touchstart` event dispatched by a screen reader. */

src/cdk/a11y/focus-monitor/focus-monitor.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ describe('FocusMonitor', () => {
151151
// Simulate focus via a fake mousedown from a screen reader.
152152
dispatchMouseEvent(buttonElement, 'mousedown');
153153
const event = createMouseEvent('mousedown');
154-
Object.defineProperty(event, 'buttons', {get: () => 0});
154+
Object.defineProperties(event, {offsetX: {get: () => 0}, offsetY: {get: () => 0}});
155155
dispatchEvent(buttonElement, event);
156156

157157
buttonElement.focus();

src/cdk/a11y/input-modality/input-modality-detector.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ describe('InputModalityDetector', () => {
143143

144144
// Create a fake screen-reader mouse event.
145145
const event = createMouseEvent('mousedown');
146-
Object.defineProperty(event, 'buttons', {get: () => 0});
146+
Object.defineProperties(event, {offsetX: {get: () => 0}, offsetY: {get: () => 0}});
147147
dispatchEvent(document, event);
148148

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

src/cdk/testing/testbed/fake-events/event-objects.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ export function createMouseEvent(
4343
/* button */ button,
4444
/* relatedTarget */ null);
4545

46-
// `initMouseEvent` doesn't allow us to pass the `buttons` and
47-
// defaults it to 0 which looks like a fake event.
46+
// `initMouseEvent` doesn't allow us to pass these properties into the constructor.
47+
// Override them to 1, because they're used for fake screen reader event detection.
4848
defineReadonlyEventProperty(event, 'buttons', 1);
49+
defineReadonlyEventProperty(event, 'offsetX', 1);
50+
defineReadonlyEventProperty(event, 'offsetY', 1);
4951

5052
// IE won't set `defaultPrevented` on synthetic events so we need to do it manually.
5153
event.preventDefault = function() {

src/material/core/ripple/ripple.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ describe('MatRipple', () => {
181181

182182
it('should ignore fake mouse events from screen readers', fakeAsync(() => {
183183
const event = createMouseEvent('mousedown');
184-
Object.defineProperty(event, 'buttons', {get: () => 0});
184+
Object.defineProperties(event, {offsetX: {get: () => 0}, offsetY: {get: () => 0}});
185185

186186
dispatchEvent(rippleTarget, event);
187187
tick(enterDuration);

0 commit comments

Comments
 (0)