Skip to content

Commit

Permalink
fix(cdk/a11y): fake mousedown detection not working (#23029)
Browse files Browse the repository at this point in the history
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.

(cherry picked from commit a18d26f)
  • Loading branch information
crisbeto authored and andrewseguin committed Jul 2, 2021
1 parent ebb1c73 commit 4de15ea
Show file tree
Hide file tree
Showing 5 changed files with 13 additions and 10 deletions.
11 changes: 6 additions & 5 deletions src/cdk/a11y/fake-event-detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@

/** Gets whether an event could be a faked `mousedown` event dispatched by a screen reader. */
export function isFakeMousedownFromScreenReader(event: MouseEvent): boolean {
// We can typically distinguish between these faked mousedown events and real mousedown events
// using the "buttons" property. While real mousedowns will indicate the mouse button that was
// pressed (e.g. "1" for the left mouse button), faked mousedowns will usually set the property
// value to 0.
return event.buttons === 0;
// 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. Historially we used to check
// `event.buttons === 0`, however that no longer works on recent versions of NVDA.
return event.offsetX === 0 && event.offsetY === 0;
}

/** Gets whether an event could be a faked `touchstart` event dispatched by a screen reader. */
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/a11y/focus-monitor/focus-monitor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ describe('FocusMonitor', () => {
// Simulate focus via a fake mousedown from a screen reader.
dispatchMouseEvent(buttonElement, 'mousedown');
const event = createMouseEvent('mousedown');
Object.defineProperty(event, 'buttons', {get: () => 0});
Object.defineProperties(event, {offsetX: {get: () => 0}, offsetY: {get: () => 0}});
dispatchEvent(buttonElement, event);

buttonElement.focus();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ describe('InputModalityDetector', () => {

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

expect(detector.mostRecentModality).toBe('keyboard');
Expand Down
6 changes: 4 additions & 2 deletions src/cdk/testing/testbed/fake-events/event-objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ export function createMouseEvent(
/* button */ button,
/* relatedTarget */ null);

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

// IE won't set `defaultPrevented` on synthetic events so we need to do it manually.
event.preventDefault = function() {
Expand Down
2 changes: 1 addition & 1 deletion src/material/core/ripple/ripple.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ describe('MatRipple', () => {

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

dispatchEvent(rippleTarget, event);
tick(enterDuration);
Expand Down

0 comments on commit 4de15ea

Please sign in to comment.