diff --git a/src/cdk/a11y/fake-event-detection.ts b/src/cdk/a11y/fake-event-detection.ts index 6835b6885c49..b5fe69c52003 100644 --- a/src/cdk/a11y/fake-event-detection.ts +++ b/src/cdk/a11y/fake-event-detection.ts @@ -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. */ diff --git a/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts b/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts index 09a3f71ed27e..11708fd777c4 100644 --- a/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts +++ b/src/cdk/a11y/focus-monitor/focus-monitor.spec.ts @@ -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(); 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 f379ca8f6b53..c83501b1b696 100644 --- a/src/cdk/a11y/input-modality/input-modality-detector.spec.ts +++ b/src/cdk/a11y/input-modality/input-modality-detector.spec.ts @@ -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'); diff --git a/src/cdk/testing/testbed/fake-events/event-objects.ts b/src/cdk/testing/testbed/fake-events/event-objects.ts index 63687ab94560..a4a065280b33 100644 --- a/src/cdk/testing/testbed/fake-events/event-objects.ts +++ b/src/cdk/testing/testbed/fake-events/event-objects.ts @@ -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() { diff --git a/src/material/core/ripple/ripple.spec.ts b/src/material/core/ripple/ripple.spec.ts index 2bb554392b25..22a643a60522 100644 --- a/src/material/core/ripple/ripple.spec.ts +++ b/src/material/core/ripple/ripple.spec.ts @@ -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);