Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add drag-and-drop support. #7150

Merged
merged 13 commits into from Jun 4, 2021
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 @@ -2960,6 +2986,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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what target is used for in this context?


#### 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 @@ -4035,6 +4117,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 @@ -4045,6 +4131,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