Skip to content

Commit

Permalink
fix(cdk/drag-drop): don't start dragging on fake screen reader events (
Browse files Browse the repository at this point in the history
…#23126)

Fixes that the CDK drag&drop could be triggered by fake `mousedown` or `touchstart` events dispatched by a screen reader.
  • Loading branch information
crisbeto committed Jul 12, 2021
1 parent 09e3411 commit 88d68ff
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/cdk/drag-drop/BUILD.bazel
Expand Up @@ -17,6 +17,7 @@ ng_module(
module_name = "@angular/cdk/drag-drop",
deps = [
"//src:dev_mode_types",
"//src/cdk/a11y",
"//src/cdk/bidi",
"//src/cdk/coercion",
"//src/cdk/platform",
Expand Down
55 changes: 55 additions & 0 deletions src/cdk/drag-drop/directives/drag.spec.ts
Expand Up @@ -253,6 +253,33 @@ describe('CdkDrag', () => {
expect(mousedownEvent.preventDefault).toHaveBeenCalled();
}));

it('should not start dragging an element with a fake mousedown event', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;
const event = createMouseEvent('mousedown', 0, 0);

Object.defineProperties(event, {
offsetX: {get: () => 0},
offsetY: {get: () => 0}
});

expect(dragElement.style.transform).toBeFalsy();

dispatchEvent(dragElement, event);
fixture.detectChanges();

dispatchMouseEvent(document, 'mousemove', 20, 100);
fixture.detectChanges();
dispatchMouseEvent(document, 'mousemove', 50, 100);
fixture.detectChanges();

dispatchMouseEvent(document, 'mouseup');
fixture.detectChanges();

expect(dragElement.style.transform).toBeFalsy();
}));

});

describe('touch dragging', () => {
Expand Down Expand Up @@ -350,6 +377,34 @@ describe('CdkDrag', () => {

expect(touchstartEvent.preventDefault).not.toHaveBeenCalled();
}));

it('should not start dragging an element with a fake touchstart event', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;
const event = createTouchEvent('touchstart', 50, 50) as TouchEvent;

Object.defineProperties(event.touches[0], {
identifier: {get: () => -1},
radiusX: {get: () => null},
radiusY: {get: () => null}
});

expect(dragElement.style.transform).toBeFalsy();

dispatchEvent(dragElement, event);
fixture.detectChanges();

dispatchTouchEvent(document, 'touchmove', 20, 100);
fixture.detectChanges();
dispatchTouchEvent(document, 'touchmove', 50, 100);
fixture.detectChanges();

dispatchTouchEvent(document, 'touchend');
fixture.detectChanges();

expect(dragElement.style.transform).toBeFalsy();
}));
});

it('should dispatch an event when the user has started dragging', fakeAsync(() => {
Expand Down
8 changes: 7 additions & 1 deletion src/cdk/drag-drop/drag-ref.ts
Expand Up @@ -15,6 +15,10 @@ import {
_getShadowRoot,
} from '@angular/cdk/platform';
import {coerceBooleanProperty, coerceElement} from '@angular/cdk/coercion';
import {
isFakeMousedownFromScreenReader,
isFakeTouchstartFromScreenReader,
} from '@angular/cdk/a11y';
import {Subscription, Subject, Observable} from 'rxjs';
import {DropListRefInternal as DropListRef} from './drop-list-ref';
import {DragDropRegistry} from './drag-drop-registry';
Expand Down Expand Up @@ -864,6 +868,8 @@ export class DragRef<T = any> {
const target = _getEventTarget(event);
const isSyntheticEvent = !isTouchSequence && this._lastTouchEventTime &&
this._lastTouchEventTime + MOUSE_EVENT_IGNORE_TIME > Date.now();
const isFakeEvent = isTouchSequence ? isFakeTouchstartFromScreenReader(event as TouchEvent) :
isFakeMousedownFromScreenReader(event as MouseEvent);

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

// Abort if the user is already dragging or is using a mouse button other than the primary one.
if (isDragging || isAuxiliaryMouseButton || isSyntheticEvent) {
if (isDragging || isAuxiliaryMouseButton || isSyntheticEvent || isFakeEvent) {
return;
}

Expand Down

0 comments on commit 88d68ff

Please sign in to comment.