Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ page.removeListener('request', logRequest);
- [page.context()](#pagecontext)
- [page.coverage](#pagecoverage)
- [page.dblclick(selector[, options])](#pagedblclickselector-options)
- [page.dispatchEvent(selector, type[, eventInit, options])](#pagedispatcheventselector-type-eventinit-options)
- [page.emulateMedia(options)](#pageemulatemediaoptions)
- [page.evaluate(pageFunction[, arg])](#pageevaluatepagefunction-arg)
- [page.evaluateHandle(pageFunction[, arg])](#pageevaluatehandlepagefunction-arg)
Expand Down Expand Up @@ -1040,6 +1041,40 @@ Bear in mind that if the first click of the `dblclick()` triggers a navigation e

Shortcut for [page.mainFrame().dblclick(selector[, options])](#framedblclickselector-options).


#### page.dispatchEvent(selector, type[, eventInit, options])
- `selector` <[string]> A selector to search for element to use. If there are multiple elements satisfying the selector, the first will be used.
- `type` <[string]> DOM event type: `"click"`, `"dragstart"`, etc.
- `eventInit` <[Object]> event-specific initialization properties.
- `options` <[Object]>
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]>

The snippet below dispatches the `click` event on the element. Regardless of the visibility state of the elment, `click` is dispatched. This is equivalend to calling [`element.click()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click).

```js
await page.dispatchEvent('button#submit', 'click');
```

Under the hood, it creates an instance of an event based on the given `type`, initializes it with `eventInit` properties and dispatches it on the element. Events are `composed`, `cancelable` and bubble by default.

Since `eventInit` is event-specific, please refer to the events documentation for the lists of initial properties:
- [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent)
- [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent)
- [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent)
- [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent)
- [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent)
- [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent)
- [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event)

You can also specify `JSHandle` as the property value if you want live objects to be passed into the event:

```js
// Note you can only create DataTransfer in Chromium and Firefox
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
await page.dispatchEvent('#source', 'dragstart', { dataTransfer });
```

#### page.emulateMedia(options)
- `options` <[Object]>
- `media` <"screen"|"print"> Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation.
Expand Down Expand Up @@ -1894,6 +1929,7 @@ An example of getting text from an iframe element:
- [frame.click(selector[, options])](#frameclickselector-options)
- [frame.content()](#framecontent)
- [frame.dblclick(selector[, options])](#framedblclickselector-options)
- [frame.dispatchEvent(selector, type[, eventInit, options])](#framedispatcheventselector-type-eventinit-options)
- [frame.evaluate(pageFunction[, arg])](#frameevaluatepagefunction-arg)
- [frame.evaluateHandle(pageFunction[, arg])](#frameevaluatehandlepagefunction-arg)
- [frame.fill(selector, value[, options])](#framefillselector-value-options)
Expand Down Expand Up @@ -2052,6 +2088,39 @@ Bear in mind that if the first click of the `dblclick()` triggers a navigation e

> **NOTE** `frame.dblclick()` dispatches two `click` events and a single `dblclick` event.

#### frame.dispatchEvent(selector, type[, eventInit, options])
- `selector` <[string]> A selector to search for element to double click. If there are multiple elements satisfying the selector, the first will be double clicked.
- `type` <[string]> DOM event type: `"click"`, `"dragstart"`, etc.
- `eventInit` <[Object]> event-specific initialization properties.
- `options` <[Object]>
- `timeout` <[number]> Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the [browserContext.setDefaultTimeout(timeout)](#browsercontextsetdefaulttimeouttimeout) or [page.setDefaultTimeout(timeout)](#pagesetdefaulttimeouttimeout) methods.
- returns: <[Promise]>

The snippet below dispatches the `click` event on the element. Regardless of the visibility state of the elment, `click` is dispatched. This is equivalend to calling [`element.click()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click).

```js
await frame.dispatchEvent('button#submit', 'click');
```

Under the hood, it creates an instance of an event based on the given `type`, initializes it with `eventInit` properties and dispatches it on the element. Events are `composed`, `cancelable` and bubble by default.

Since `eventInit` is event-specific, please refer to the events documentation for the lists of initial properties:
- [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent)
- [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent)
- [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent)
- [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent)
- [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent)
- [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent)
- [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event)

You can also specify `JSHandle` as the property value if you want live objects to be passed into the event:

```js
// Note you can only create DataTransfer in Chromium and Firefox
const dataTransfer = await frame.evaluateHandle(() => new DataTransfer());
await frame.dispatchEvent('#source', 'dragstart', { dataTransfer });
```

#### frame.evaluate(pageFunction[, arg])
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context
- `arg` <[Serializable]|[JSHandle]> Optional argument to pass to `pageFunction`
Expand Down Expand Up @@ -2477,6 +2546,7 @@ ElementHandle instances can be used as an argument in [`page.$eval()`](#pageeval
- [elementHandle.click([options])](#elementhandleclickoptions)
- [elementHandle.contentFrame()](#elementhandlecontentframe)
- [elementHandle.dblclick([options])](#elementhandledblclickoptions)
- [elementHandle.dispatchEvent(type[, eventInit])](#elementhandledispatcheventtype-eventinit)
- [elementHandle.fill(value[, options])](#elementhandlefillvalue-options)
- [elementHandle.focus()](#elementhandlefocus)
- [elementHandle.getAttribute(name)](#elementhandlegetattributename)
Expand Down Expand Up @@ -2626,6 +2696,36 @@ Bear in mind that if the first click of the `dblclick()` triggers a navigation e

> **NOTE** `elementHandle.dblclick()` dispatches two `click` events and a single `dblclick` event.

#### elementHandle.dispatchEvent(type[, eventInit])
- `type` <[string]> DOM event type: `"click"`, `"dragstart"`, etc.
- `eventInit` <[Object]> event-specific initialization properties.
- returns: <[Promise]>

The snippet below dispatches the `click` event on the element. Regardless of the visibility state of the elment, `click` is dispatched. This is equivalend to calling [`element.click()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/click).

```js
await elementHandle.dispatchEvent('click');
```

Under the hood, it creates an instance of an event based on the given `type`, initializes it with `eventInit` properties and dispatches it on the element. Events are `composed`, `cancelable` and bubble by default.

Since `eventInit` is event-specific, please refer to the events documentation for the lists of initial properties:
- [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent)
- [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent)
- [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent)
- [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent)
- [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent)
- [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent)
- [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event)

You can also specify `JSHandle` as the property value if you want live objects to be passed into the event:

```js
// Note you can only create DataTransfer in Chromium and Firefox
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
await elementHandle.dispatchEvent('dragstart', { dataTransfer });
```

#### elementHandle.fill(value[, options])
- `value` <[string]> Value to set for the `<input>`, `<textarea>` or `[contenteditable]` element.
- `options` <[Object]>
Expand Down
10 changes: 10 additions & 0 deletions src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return this;
}

async _evaluateInMain<R, Arg>(pageFunction: types.FuncOn<{ injected: Injected, node: T }, Arg, R>, arg: Arg): Promise<R> {
const main = await this._context.frame._mainContext();
return main._doEvaluateInternal(true /* returnByValue */, true /* waitForNavigations */, pageFunction, { injected: await main._injected(), node: this }, arg);
}

async _evaluateInUtility<R, Arg>(pageFunction: types.FuncOn<{ injected: Injected, node: T }, Arg, R>, arg: Arg): Promise<R> {
const utility = await this._context.frame._utilityContext();
return utility._doEvaluateInternal(true /* returnByValue */, true /* waitForNavigations */, pageFunction, { injected: await utility._injected(), node: this }, arg);
Expand Down Expand Up @@ -152,6 +157,11 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}, {});
}

async dispatchEvent(type: string, eventInit: Object = {}) {
await this._evaluateInMain(({ injected, node }, { type, eventInit }) =>
injected.dispatchEvent(node, type, eventInit), { type, eventInit });
}

async _scrollRectIntoViewIfNeeded(rect?: types.Rect): Promise<void> {
this._page._log(inputLog, 'scrolling into view if needed...');
await this._page._delegate.scrollRectIntoViewIfNeeded(this, rect);
Expand Down
7 changes: 7 additions & 0 deletions src/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,13 @@ export class Frame {
return handle;
}

async dispatchEvent(selector: string, type: string, eventInit?: Object, options?: types.TimeoutOptions): Promise<void> {
const deadline = this._page._timeoutSettings.computeDeadline(options);
const task = selectors._dispatchEventTask(selector, type, eventInit || {}, deadline);
const result = await this._scheduleRerunnableTask(task, 'main', deadline, `selector "${selectorToString(selector, 'attached')}"`);
result.dispose();
}

async $eval<R, Arg>(selector: string, pageFunction: types.FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
async $eval<R>(selector: string, pageFunction: types.FuncOn<Element, void, R>, arg?: any): Promise<R>;
async $eval<R, Arg>(selector: string, pageFunction: types.FuncOn<Element, Arg, R>, arg: Arg): Promise<R> {
Expand Down
63 changes: 63 additions & 0 deletions src/injected/injected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,21 @@ export class Injected {
return { status: result === 'notconnected' ? 'notconnected' : (result ? 'success' : 'timeout') };
}

dispatchEvent(node: Node, type: string, eventInit: Object) {
let event;
eventInit = { bubbles: true, cancelable: true, composed: true, ...eventInit };
switch (eventType.get(type)) {
case 'mouse': event = new MouseEvent(type, eventInit); break;
case 'keyboard': event = new KeyboardEvent(type, eventInit); break;
case 'touch': event = new TouchEvent(type, eventInit); break;
case 'pointer': event = new PointerEvent(type, eventInit); break;
case 'focus': event = new FocusEvent(type, eventInit); break;
case 'drag': event = new DragEvent(type, eventInit); break;
default: event = new Event(type, eventInit); break;
}
node.dispatchEvent(event);
}

private _parentElementOrShadowHost(element: Element): Element | undefined {
if (element.parentElement)
return element.parentElement;
Expand All @@ -359,3 +374,51 @@ export class Injected {
return element;
}
}

const eventType = new Map<string, 'mouse'|'keyboard'|'touch'|'pointer'|'focus'|'drag'>([
['auxclick', 'mouse'],
['click', 'mouse'],
['dblclick', 'mouse'],
['mousedown','mouse'],
['mouseeenter', 'mouse'],
['mouseleave', 'mouse'],
['mousemove', 'mouse'],
['mouseout', 'mouse'],
['mouseover', 'mouse'],
['mouseup', 'mouse'],
['mouseleave', 'mouse'],
['mousewheel', 'mouse'],

['keydown', 'keyboard'],
['keyup', 'keyboard'],
['keypress', 'keyboard'],
['textInput', 'keyboard'],

['touchstart', 'touch'],
['touchmove', 'touch'],
['touchend', 'touch'],
['touchcancel', 'touch'],

['pointerover', 'pointer'],
['pointerout', 'pointer'],
['pointerenter', 'pointer'],
['pointerleave', 'pointer'],
['pointerdown', 'pointer'],
['pointerup', 'pointer'],
['pointermove', 'pointer'],
['pointercancel', 'pointer'],
['gotpointercapture', 'pointer'],
['lostpointercapture', 'pointer'],

['focus', 'focus'],
['blur', 'focus'],

['drag', 'drag'],
['dragstart', 'drag'],
['dragend', 'drag'],
['dragover', 'drag'],
['dragenter', 'drag'],
['dragleave', 'drag'],
['dragexit', 'drag'],
['drop', 'drag'],
]);
4 changes: 4 additions & 0 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ export class Page extends ExtendedEventEmitter implements InnerLogger {
return this.mainFrame().waitForSelector(selector, options);
}

async dispatchEvent(selector: string, type: string, eventInit?: Object, options?: types.TimeoutOptions): Promise<void> {
return this.mainFrame().dispatchEvent(selector, type, eventInit, options);
}

async evaluateHandle<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg): Promise<types.SmartHandle<R>>;
async evaluateHandle<R>(pageFunction: types.Func1<void, R>, arg?: any): Promise<types.SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg): Promise<types.SmartHandle<R>> {
Expand Down
13 changes: 13 additions & 0 deletions src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,19 @@ export class Selectors {
return { world: this._needsMainContext(parsed) ? 'main' : 'utility', task };
}

_dispatchEventTask(selector: string, type: string, eventInit: Object, deadline: number): (context: dom.FrameExecutionContext) => Promise<js.JSHandle> {
const parsed = this._parseSelector(selector);
const task = async (context: dom.FrameExecutionContext) => context.evaluateHandleInternal(({ evaluator, parsed, type, eventInit, timeout }) => {
return evaluator.injected.poll('mutation', timeout, () => {
const element = evaluator.querySelector(parsed, document);
if (element)
evaluator.injected.dispatchEvent(element, type, eventInit);
return element || false;
});
}, { evaluator: await this._prepareEvaluator(context), parsed, type, eventInit, timeout: helper.timeUntilDeadline(deadline) });
return task;
}

async _createSelector(name: string, handle: dom.ElementHandle<Element>): Promise<string | undefined> {
const mainContext = await handle._page.mainFrame()._mainContext();
return mainContext.evaluateInternal(({ evaluator, target, name }) => {
Expand Down
64 changes: 27 additions & 37 deletions test/assets/drag-n-drop.html
Original file line number Diff line number Diff line change
@@ -1,51 +1,41 @@
<!DOCTYPE html>
<html lang=en>
<title>Examples of DataTransfer's setData(), getData() and clearData()</title>
<meta content="width=device-width">
<style>
div:not(.mouse-helper) {
margin: 0em;
padding: 2em;
}
#source {
color: blue;
border: 1px solid black;
}
#target {
border: 1px solid black;
}
div:not(.mouse-helper) {
margin: 0em;
padding: 2em;
}
#source {
color: blue;
border: 1px solid black;
}
#target {
border: 1px solid black;
}
</style>

<script>

function dragstart_handler(ev) {
console.log("dragStart");
// Change the source element's background color to signify drag has started
ev.currentTarget.style.border = "dashed";
// Set the drag's format and data. Use the event target's id for the data
ev.dataTransfer.setData("text/plain", ev.target.id);
ev.currentTarget.style.border = "dashed";
ev.dataTransfer.setData("text/plain", ev.target.id);
}

function dragover_handler(ev) {
console.log("dragOver");
ev.preventDefault();
ev.preventDefault();
}

function drop_handler(ev) {
console.log("Drop");
ev.preventDefault();
// Get the data, which is the id of the drop target
var data = ev.dataTransfer.getData("text");
ev.target.appendChild(document.getElementById(data));
// Clear the drag data cache (for all formats/types)
ev.dataTransfer.clearData();
console.log("Drop");
ev.preventDefault();
var data = ev.dataTransfer.getData("text");
ev.target.appendChild(document.getElementById(data));
ev.dataTransfer.clearData();
}
</script>

<body>
<script src="input/mouse-helper.js"></script>
<h1>Examples of <code>DataTransfer</code>: <code>setData()</code>, <code>getData()</code>, <code>clearData()</code></h1>
<div>
<p id="source" ondragstart="dragstart_handler(event);" draggable="true">
Select this element, drag it to the Drop Zone and then release the selection to move the element.</p>
</div>
<div id="target" ondrop="drop_handler(event);" ondragover="dragover_handler(event);">Drop Zone</div>
<div>
<p id="source" ondragstart="dragstart_handler(event);" draggable="true">
Select this element, drag it to the Drop Zone and then release the selection to move the element.</p>
</div>
<div id="target" ondrop="drop_handler(event);" ondragover="dragover_handler(event);">Drop Zone</div>
</body>
</html>
4 changes: 4 additions & 0 deletions test/assets/input/button.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
window.shiftKey = undefined;
window.pageX = undefined;
window.pageY = undefined;
window.bubbles = undefined;
document.querySelector('button').addEventListener('click', e => {
result = 'Clicked';
offsetX = e.offsetX;
offsetY = e.offsetY;
pageX = e.pageX;
pageY = e.pageY;
shiftKey = e.shiftKey;
bubbles = e.bubbles;
cancelable = e.cancelable;
composed = e.composed;
}, false);
</script>
</body>
Expand Down
Loading