Skip to content

Commit

Permalink
feature: add drag-and-drop support. (#7150)
Browse files Browse the repository at this point in the history
This commit adds drag-and-drop support, leveraging new additions to the CDP
Input domain (Input.setInterceptDrags, Input.dispatchDragEvent, and
Input.dragIntercepted).
  • Loading branch information
danpark-salesforce committed Jun 4, 2021
1 parent 0295b1c commit 48954c6
Show file tree
Hide file tree
Showing 7 changed files with 591 additions and 4 deletions.
125 changes: 125 additions & 0 deletions docs/api.md
Expand Up @@ -155,6 +155,7 @@
* [page.goto(url[, options])](#pagegotourl-options)
* [page.hover(selector)](#pagehoverselector)
* [page.isClosed()](#pageisclosed)
* [page.isDragInterceptionEnabled](#pageisdraginterceptionenabled)
* [page.isJavaScriptEnabled()](#pageisjavascriptenabled)
* [page.keyboard](#pagekeyboard)
* [page.mainFrame()](#pagemainframe)
Expand All @@ -171,6 +172,7 @@
* [page.setCookie(...cookies)](#pagesetcookiecookies)
* [page.setDefaultNavigationTimeout(timeout)](#pagesetdefaultnavigationtimeouttimeout)
* [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout)
* [page.setDragInterception(enabled)](#pagesetdraginterceptionenabled)
* [page.setExtraHTTPHeaders(headers)](#pagesetextrahttpheadersheaders)
* [page.setGeolocation(options)](#pagesetgeolocationoptions)
* [page.setJavaScriptEnabled(enabled)](#pagesetjavascriptenabledenabled)
Expand Down Expand Up @@ -214,6 +216,11 @@
- [class: Mouse](#class-mouse)
* [mouse.click(x, y[, options])](#mouseclickx-y-options)
* [mouse.down([options])](#mousedownoptions)
* [mouse.drag(start, target)](#mousedragstart-target)
* [mouse.dragAndDrop(start, target[, options])](#mousedraganddropstart-target-options)
* [mouse.dragEnter(target, data)](#mousedragentertarget-data)
* [mouse.dragOver(target, data)](#mousedragovertarget-data)
* [mouse.drop(target, data)](#mousedroptarget-data)
* [mouse.move(x, y[, options])](#mousemovex-y-options)
* [mouse.up([options])](#mouseupoptions)
* [mouse.wheel([options])](#mousewheeloptions)
Expand Down Expand Up @@ -294,8 +301,14 @@
* [elementHandle.boundingBox()](#elementhandleboundingbox)
* [elementHandle.boxModel()](#elementhandleboxmodel)
* [elementHandle.click([options])](#elementhandleclickoptions)
* [elementHandle.clickablePoint()](#elementhandleclickablepoint)
* [elementHandle.contentFrame()](#elementhandlecontentframe)
* [elementHandle.dispose()](#elementhandledispose)
* [elementHandle.drag(target)](#elementhandledragtarget)
* [elementHandle.dragAndDrop(target[, options])](#elementhandledraganddroptarget-options)
* [elementHandle.dragEnter([data])](#elementhandledragenterdata)
* [elementHandle.dragOver([data])](#elementhandledragoverdata)
* [elementHandle.drop([data])](#elementhandledropdata)
* [elementHandle.evaluate(pageFunction[, ...args])](#elementhandleevaluatepagefunction-args)
* [elementHandle.evaluateHandle(pageFunction[, ...args])](#elementhandleevaluatehandlepagefunction-args)
* [elementHandle.executionContext()](#elementhandleexecutioncontext)
Expand Down Expand Up @@ -1919,6 +1932,12 @@ Shortcut for [page.mainFrame().hover(selector)](#framehoverselector).

Indicates that the page has been closed.

#### page.isDragInterceptionEnabled

- returns: <[boolean]>

Indicates that drag events are being intercepted.

#### page.isJavaScriptEnabled()

- returns: <[boolean]>
Expand Down Expand Up @@ -2183,6 +2202,13 @@ This setting will change the default maximum time for the following methods and

> **NOTE** [`page.setDefaultNavigationTimeout`](#pagesetdefaultnavigationtimeouttimeout) takes priority over [`page.setDefaultTimeout`](#pagesetdefaulttimeouttimeout)
#### page.setDragInterception(enabled)

- `enabled` <[boolean]>
- returns: <[Promise]>

Enables the Input.drag methods. This provides the capability to cpature drag events emitted on the page, which can then be used to simulate drag-and-drop.

#### page.setExtraHTTPHeaders(headers)

- `headers` <[Object]> An object containing additional HTTP headers to be sent with every request. All header values must be strings.
Expand Down Expand Up @@ -2958,6 +2984,62 @@ Shortcut for [`mouse.move`](#mousemovex-y-options), [`mouse.down`](#mousedownopt

Dispatches a `mousedown` event.

#### mouse.drag(start, target)

- `start` <[Object]> the position to start dragging from
- `x` <[number]> x coordinate
- `y` <[number]> y coordinate
- `target` <[Object]> the position to drag to
- `x` <[number]> x coordinate
- `y` <[number]> y coordinate
- returns: <[Promise<[DragData]>]>

This method creates and captures a dragevent from a given point.

#### mouse.dragAndDrop(start, target[, options])

- `start` <[Object]>
- `x` <[number]> x coordinate
- `y` <[number]> y coordinate
- `target` <[Object]>
- `x` <[number]> x coordinate
- `y` <[number]> y coordinate
- `options` <[Object]>
- `delay` <[number]> how long to delay before dropping onto the target point
- returns: <[Promise<[DragData]>]>

This method drags from a given start point and drops onto a target point.

#### mouse.dragEnter(target, data)

- `target` <[Object]>
- `x` <[number]> x coordinate
- `y` <[number]> y coordinate
- `data` <[Object]>
- returns: <[Promise]]>

This method triggers a dragenter event from the target point.

#### mouse.dragOver(target, data)

- `target` <[Object]>
- `x` <[number]> x coordinate
- `y` <[number]> y coordinate
- `data` <[Object]>
- returns: <[Promise]]>

This method triggers a dragover event from the target point.

#### mouse.drop(target, data)

- `target` <[Object]>
- `x` <[number]> x coordinate
- `y` <[number]> y coordinate
- `data` <[Object]>
- returns: <[Promise]]>

This method triggers a drop event from the target point.

#### mouse.move(x, y[, options])

- `x` <[number]>
Expand Down Expand Up @@ -4033,6 +4115,10 @@ This method returns boxes of the element, or `null` if the element is not visibl
This method scrolls element into view if needed, and then uses [page.mouse](#pagemouse) to click in the center of the element.
If the element is detached from DOM, the method throws an error.

#### elementHandle.clickablePoint()

- returns: <[Promise<[Point]>]> Resolves to the x, y point that describes the element's position.

#### elementHandle.contentFrame()

- returns: <[Promise]<?[Frame]>> Resolves to the content frame for element handles referencing iframe nodes, or null otherwise
Expand All @@ -4043,6 +4129,45 @@ If the element is detached from DOM, the method throws an error.

The `elementHandle.dispose` method stops referencing the element handle.

#### elementHandle.drag(target)

- `target` <[Object]>
- `x` <[number]> x coordinate
- `y` <[number]> y coordinate
- returns: <[Promise<[DragData]>]>

This method creates and captures a drag event from the element.

#### elementHandle.dragAndDrop(target[, options])

- `target` <[ElementHandle]>
- `options` <[Object]>
- `delay` <[number]> how long to delay before dropping onto the target element
- returns: <[Promise]>

This method will drag a given element and drop it onto a target element.

#### elementHandle.dragEnter([data])

- `data` <[Object]> drag data created from `element.drag`
- returns: <[Promise]>

This method will trigger a dragenter event from the given element.

#### elementHandle.dragOver([data])

- `data` <[Object]> drag data created from `element.drag`
- returns: <[Promise]>

This method will trigger a dragover event from the given element.

#### elementHandle.drop([data])

- `data` <[Object]> drag data created from `element.drag`
- returns: <[Promise]>

This method will trigger a drop event from the given element.

#### elementHandle.evaluate(pageFunction[, ...args])

- `pageFunction` <[function]\([Object]\)> Function to be evaluated in browser context
Expand Down
94 changes: 94 additions & 0 deletions src/common/Input.ts
Expand Up @@ -17,6 +17,8 @@
import { assert } from './assert.js';
import { CDPSession } from './Connection.js';
import { keyDefinitions, KeyDefinition, KeyInput } from './USKeyboardLayout.js';
import { Protocol } from 'devtools-protocol';
import { Point } from './JSHandle.js';

type KeyDescription = Required<
Pick<KeyDefinition, 'keyCode' | 'key' | 'text' | 'code' | 'location'>
Expand Down Expand Up @@ -485,6 +487,98 @@ export class Mouse {
pointerType: 'mouse',
});
}

/**
* Dispatches a `drag` event.
* @param start - starting point for drag
* @param target - point to drag to
* ```
*/
async drag(start: Point, target: Point): Promise<Protocol.Input.DragData> {
const promise = new Promise<Protocol.Input.DragData>((resolve) => {
this._client.once('Input.dragIntercepted', (event) =>
resolve(event.data)
);
});
await this.move(start.x, start.y);
await this.down();
await this.move(target.x, target.y);
return promise;
}

/**
* Dispatches a `dragenter` event.
* @param target - point for emitting `dragenter` event
* ```
*/
async dragEnter(target: Point, data: Protocol.Input.DragData): Promise<void> {
await this._client.send('Input.dispatchDragEvent', {
type: 'dragEnter',
x: target.x,
y: target.y,
modifiers: this._keyboard._modifiers,
data,
});
}

/**
* Dispatches a `dragover` event.
* @param target - point for emitting `dragover` event
* ```
*/
async dragOver(target: Point, data: Protocol.Input.DragData): Promise<void> {
await this._client.send('Input.dispatchDragEvent', {
type: 'dragOver',
x: target.x,
y: target.y,
modifiers: this._keyboard._modifiers,
data,
});
}

/**
* Performs a dragenter, dragover, and drop in sequence.
* @param target - point to drop on
* @param data - drag data containing items and operations mask
* @param options - An object of options. Accepts delay which,
* if specified, is the time to wait between `dragover` and `drop` in milliseconds.
* Defaults to 0.
* ```
*/
async drop(target: Point, data: Protocol.Input.DragData): Promise<void> {
await this._client.send('Input.dispatchDragEvent', {
type: 'drop',
x: target.x,
y: target.y,
modifiers: this._keyboard._modifiers,
data,
});
}

/**
* Performs a drag, dragenter, dragover, and drop in sequence.
* @param target - point to drag from
* @param target - point to drop on
* @param options - An object of options. Accepts delay which,
* if specified, is the time to wait between `dragover` and `drop` in milliseconds.
* Defaults to 0.
* ```
*/
async dragAndDrop(
start: Point,
target: Point,
options: { delay?: number } = {}
): Promise<void> {
const { delay = null } = options;
const data = await this.drag(start, target);
await this.dragEnter(target, data);
await this.dragOver(target, data);
if (delay) {
await new Promise((resolve) => setTimeout(resolve, delay));
}
await this.drop(target, data);
await this.up();
}
}

/**
Expand Down

0 comments on commit 48954c6

Please sign in to comment.