Skip to content

Commit 88d68ff

Browse files
authored
fix(cdk/drag-drop): don't start dragging on fake screen reader events (#23126)
Fixes that the CDK drag&drop could be triggered by fake `mousedown` or `touchstart` events dispatched by a screen reader.
1 parent 09e3411 commit 88d68ff

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-1
lines changed

src/cdk/drag-drop/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ ng_module(
1717
module_name = "@angular/cdk/drag-drop",
1818
deps = [
1919
"//src:dev_mode_types",
20+
"//src/cdk/a11y",
2021
"//src/cdk/bidi",
2122
"//src/cdk/coercion",
2223
"//src/cdk/platform",

src/cdk/drag-drop/directives/drag.spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,33 @@ describe('CdkDrag', () => {
253253
expect(mousedownEvent.preventDefault).toHaveBeenCalled();
254254
}));
255255

256+
it('should not start dragging an element with a fake mousedown event', fakeAsync(() => {
257+
const fixture = createComponent(StandaloneDraggable);
258+
fixture.detectChanges();
259+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
260+
const event = createMouseEvent('mousedown', 0, 0);
261+
262+
Object.defineProperties(event, {
263+
offsetX: {get: () => 0},
264+
offsetY: {get: () => 0}
265+
});
266+
267+
expect(dragElement.style.transform).toBeFalsy();
268+
269+
dispatchEvent(dragElement, event);
270+
fixture.detectChanges();
271+
272+
dispatchMouseEvent(document, 'mousemove', 20, 100);
273+
fixture.detectChanges();
274+
dispatchMouseEvent(document, 'mousemove', 50, 100);
275+
fixture.detectChanges();
276+
277+
dispatchMouseEvent(document, 'mouseup');
278+
fixture.detectChanges();
279+
280+
expect(dragElement.style.transform).toBeFalsy();
281+
}));
282+
256283
});
257284

258285
describe('touch dragging', () => {
@@ -350,6 +377,34 @@ describe('CdkDrag', () => {
350377

351378
expect(touchstartEvent.preventDefault).not.toHaveBeenCalled();
352379
}));
380+
381+
it('should not start dragging an element with a fake touchstart event', fakeAsync(() => {
382+
const fixture = createComponent(StandaloneDraggable);
383+
fixture.detectChanges();
384+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
385+
const event = createTouchEvent('touchstart', 50, 50) as TouchEvent;
386+
387+
Object.defineProperties(event.touches[0], {
388+
identifier: {get: () => -1},
389+
radiusX: {get: () => null},
390+
radiusY: {get: () => null}
391+
});
392+
393+
expect(dragElement.style.transform).toBeFalsy();
394+
395+
dispatchEvent(dragElement, event);
396+
fixture.detectChanges();
397+
398+
dispatchTouchEvent(document, 'touchmove', 20, 100);
399+
fixture.detectChanges();
400+
dispatchTouchEvent(document, 'touchmove', 50, 100);
401+
fixture.detectChanges();
402+
403+
dispatchTouchEvent(document, 'touchend');
404+
fixture.detectChanges();
405+
406+
expect(dragElement.style.transform).toBeFalsy();
407+
}));
353408
});
354409

355410
it('should dispatch an event when the user has started dragging', fakeAsync(() => {

src/cdk/drag-drop/drag-ref.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import {
1515
_getShadowRoot,
1616
} from '@angular/cdk/platform';
1717
import {coerceBooleanProperty, coerceElement} from '@angular/cdk/coercion';
18+
import {
19+
isFakeMousedownFromScreenReader,
20+
isFakeTouchstartFromScreenReader,
21+
} from '@angular/cdk/a11y';
1822
import {Subscription, Subject, Observable} from 'rxjs';
1923
import {DropListRefInternal as DropListRef} from './drop-list-ref';
2024
import {DragDropRegistry} from './drag-drop-registry';
@@ -864,6 +868,8 @@ export class DragRef<T = any> {
864868
const target = _getEventTarget(event);
865869
const isSyntheticEvent = !isTouchSequence && this._lastTouchEventTime &&
866870
this._lastTouchEventTime + MOUSE_EVENT_IGNORE_TIME > Date.now();
871+
const isFakeEvent = isTouchSequence ? isFakeTouchstartFromScreenReader(event as TouchEvent) :
872+
isFakeMousedownFromScreenReader(event as MouseEvent);
867873

868874
// If the event started from an element with the native HTML drag&drop, it'll interfere
869875
// with our own dragging (e.g. `img` tags do it by default). Prevent the default action
@@ -876,7 +882,7 @@ export class DragRef<T = any> {
876882
}
877883

878884
// Abort if the user is already dragging or is using a mouse button other than the primary one.
879-
if (isDragging || isAuxiliaryMouseButton || isSyntheticEvent) {
885+
if (isDragging || isAuxiliaryMouseButton || isSyntheticEvent || isFakeEvent) {
880886
return;
881887
}
882888

0 commit comments

Comments
 (0)