Skip to content

Commit c1c0237

Browse files
authored
api(dispatchEvent): page, frame and handle versions added (#1932)
1 parent 671cfa0 commit c1c0237

File tree

10 files changed

+332
-37
lines changed

10 files changed

+332
-37
lines changed

docs/api.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@ page.removeListener('request', logRequest);
674674
- [page.context()](#pagecontext)
675675
- [page.coverage](#pagecoverage)
676676
- [page.dblclick(selector[, options])](#pagedblclickselector-options)
677+
- [page.dispatchEvent(selector, type[, eventInit, options])](#pagedispatcheventselector-type-eventinit-options)
677678
- [page.emulateMedia(options)](#pageemulatemediaoptions)
678679
- [page.evaluate(pageFunction[, arg])](#pageevaluatepagefunction-arg)
679680
- [page.evaluateHandle(pageFunction[, arg])](#pageevaluatehandlepagefunction-arg)
@@ -1040,6 +1041,40 @@ Bear in mind that if the first click of the `dblclick()` triggers a navigation e
10401041
10411042
Shortcut for [page.mainFrame().dblclick(selector[, options])](#framedblclickselector-options).
10421043

1044+
1045+
#### page.dispatchEvent(selector, type[, eventInit, options])
1046+
- `selector` <[string]> A selector to search for element to use. If there are multiple elements satisfying the selector, the first will be used.
1047+
- `type` <[string]> DOM event type: `"click"`, `"dragstart"`, etc.
1048+
- `eventInit` <[Object]> event-specific initialization properties.
1049+
- `options` <[Object]>
1050+
- `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.
1051+
- returns: <[Promise]>
1052+
1053+
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).
1054+
1055+
```js
1056+
await page.dispatchEvent('button#submit', 'click');
1057+
```
1058+
1059+
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.
1060+
1061+
Since `eventInit` is event-specific, please refer to the events documentation for the lists of initial properties:
1062+
- [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent)
1063+
- [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent)
1064+
- [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent)
1065+
- [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent)
1066+
- [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent)
1067+
- [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent)
1068+
- [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event)
1069+
1070+
You can also specify `JSHandle` as the property value if you want live objects to be passed into the event:
1071+
1072+
```js
1073+
// Note you can only create DataTransfer in Chromium and Firefox
1074+
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
1075+
await page.dispatchEvent('#source', 'dragstart', { dataTransfer });
1076+
```
1077+
10431078
#### page.emulateMedia(options)
10441079
- `options` <[Object]>
10451080
- `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.
@@ -1894,6 +1929,7 @@ An example of getting text from an iframe element:
18941929
- [frame.click(selector[, options])](#frameclickselector-options)
18951930
- [frame.content()](#framecontent)
18961931
- [frame.dblclick(selector[, options])](#framedblclickselector-options)
1932+
- [frame.dispatchEvent(selector, type[, eventInit, options])](#framedispatcheventselector-type-eventinit-options)
18971933
- [frame.evaluate(pageFunction[, arg])](#frameevaluatepagefunction-arg)
18981934
- [frame.evaluateHandle(pageFunction[, arg])](#frameevaluatehandlepagefunction-arg)
18991935
- [frame.fill(selector, value[, options])](#framefillselector-value-options)
@@ -2052,6 +2088,39 @@ Bear in mind that if the first click of the `dblclick()` triggers a navigation e
20522088

20532089
> **NOTE** `frame.dblclick()` dispatches two `click` events and a single `dblclick` event.
20542090
2091+
#### frame.dispatchEvent(selector, type[, eventInit, options])
2092+
- `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.
2093+
- `type` <[string]> DOM event type: `"click"`, `"dragstart"`, etc.
2094+
- `eventInit` <[Object]> event-specific initialization properties.
2095+
- `options` <[Object]>
2096+
- `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.
2097+
- returns: <[Promise]>
2098+
2099+
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).
2100+
2101+
```js
2102+
await frame.dispatchEvent('button#submit', 'click');
2103+
```
2104+
2105+
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.
2106+
2107+
Since `eventInit` is event-specific, please refer to the events documentation for the lists of initial properties:
2108+
- [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent)
2109+
- [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent)
2110+
- [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent)
2111+
- [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent)
2112+
- [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent)
2113+
- [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent)
2114+
- [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event)
2115+
2116+
You can also specify `JSHandle` as the property value if you want live objects to be passed into the event:
2117+
2118+
```js
2119+
// Note you can only create DataTransfer in Chromium and Firefox
2120+
const dataTransfer = await frame.evaluateHandle(() => new DataTransfer());
2121+
await frame.dispatchEvent('#source', 'dragstart', { dataTransfer });
2122+
```
2123+
20552124
#### frame.evaluate(pageFunction[, arg])
20562125
- `pageFunction` <[function]|[string]> Function to be evaluated in browser context
20572126
- `arg` <[Serializable]|[JSHandle]> Optional argument to pass to `pageFunction`
@@ -2477,6 +2546,7 @@ ElementHandle instances can be used as an argument in [`page.$eval()`](#pageeval
24772546
- [elementHandle.click([options])](#elementhandleclickoptions)
24782547
- [elementHandle.contentFrame()](#elementhandlecontentframe)
24792548
- [elementHandle.dblclick([options])](#elementhandledblclickoptions)
2549+
- [elementHandle.dispatchEvent(type[, eventInit])](#elementhandledispatcheventtype-eventinit)
24802550
- [elementHandle.fill(value[, options])](#elementhandlefillvalue-options)
24812551
- [elementHandle.focus()](#elementhandlefocus)
24822552
- [elementHandle.getAttribute(name)](#elementhandlegetattributename)
@@ -2626,6 +2696,36 @@ Bear in mind that if the first click of the `dblclick()` triggers a navigation e
26262696

26272697
> **NOTE** `elementHandle.dblclick()` dispatches two `click` events and a single `dblclick` event.
26282698
2699+
#### elementHandle.dispatchEvent(type[, eventInit])
2700+
- `type` <[string]> DOM event type: `"click"`, `"dragstart"`, etc.
2701+
- `eventInit` <[Object]> event-specific initialization properties.
2702+
- returns: <[Promise]>
2703+
2704+
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).
2705+
2706+
```js
2707+
await elementHandle.dispatchEvent('click');
2708+
```
2709+
2710+
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.
2711+
2712+
Since `eventInit` is event-specific, please refer to the events documentation for the lists of initial properties:
2713+
- [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent)
2714+
- [FocusEvent](https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent)
2715+
- [KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent)
2716+
- [MouseEvent](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent)
2717+
- [PointerEvent](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent)
2718+
- [TouchEvent](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent)
2719+
- [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event/Event)
2720+
2721+
You can also specify `JSHandle` as the property value if you want live objects to be passed into the event:
2722+
2723+
```js
2724+
// Note you can only create DataTransfer in Chromium and Firefox
2725+
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
2726+
await elementHandle.dispatchEvent('dragstart', { dataTransfer });
2727+
```
2728+
26292729
#### elementHandle.fill(value[, options])
26302730
- `value` <[string]> Value to set for the `<input>`, `<textarea>` or `[contenteditable]` element.
26312731
- `options` <[Object]>

src/dom.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
9494
return this;
9595
}
9696

97+
async _evaluateInMain<R, Arg>(pageFunction: types.FuncOn<{ injected: Injected, node: T }, Arg, R>, arg: Arg): Promise<R> {
98+
const main = await this._context.frame._mainContext();
99+
return main._doEvaluateInternal(true /* returnByValue */, true /* waitForNavigations */, pageFunction, { injected: await main._injected(), node: this }, arg);
100+
}
101+
97102
async _evaluateInUtility<R, Arg>(pageFunction: types.FuncOn<{ injected: Injected, node: T }, Arg, R>, arg: Arg): Promise<R> {
98103
const utility = await this._context.frame._utilityContext();
99104
return utility._doEvaluateInternal(true /* returnByValue */, true /* waitForNavigations */, pageFunction, { injected: await utility._injected(), node: this }, arg);
@@ -152,6 +157,11 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
152157
}, {});
153158
}
154159

160+
async dispatchEvent(type: string, eventInit: Object = {}) {
161+
await this._evaluateInMain(({ injected, node }, { type, eventInit }) =>
162+
injected.dispatchEvent(node, type, eventInit), { type, eventInit });
163+
}
164+
155165
async _scrollRectIntoViewIfNeeded(rect?: types.Rect): Promise<void> {
156166
this._page._log(inputLog, 'scrolling into view if needed...');
157167
await this._page._delegate.scrollRectIntoViewIfNeeded(this, rect);

src/frames.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,13 @@ export class Frame {
454454
return handle;
455455
}
456456

457+
async dispatchEvent(selector: string, type: string, eventInit?: Object, options?: types.TimeoutOptions): Promise<void> {
458+
const deadline = this._page._timeoutSettings.computeDeadline(options);
459+
const task = selectors._dispatchEventTask(selector, type, eventInit || {}, deadline);
460+
const result = await this._scheduleRerunnableTask(task, 'main', deadline, `selector "${selectorToString(selector, 'attached')}"`);
461+
result.dispose();
462+
}
463+
457464
async $eval<R, Arg>(selector: string, pageFunction: types.FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
458465
async $eval<R>(selector: string, pageFunction: types.FuncOn<Element, void, R>, arg?: any): Promise<R>;
459466
async $eval<R, Arg>(selector: string, pageFunction: types.FuncOn<Element, Arg, R>, arg: Arg): Promise<R> {

src/injected/injected.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,21 @@ export class Injected {
346346
return { status: result ? 'success' : 'timeout' };
347347
}
348348

349+
dispatchEvent(node: Node, type: string, eventInit: Object) {
350+
let event;
351+
eventInit = { bubbles: true, cancelable: true, composed: true, ...eventInit };
352+
switch (eventType.get(type)) {
353+
case 'mouse': event = new MouseEvent(type, eventInit); break;
354+
case 'keyboard': event = new KeyboardEvent(type, eventInit); break;
355+
case 'touch': event = new TouchEvent(type, eventInit); break;
356+
case 'pointer': event = new PointerEvent(type, eventInit); break;
357+
case 'focus': event = new FocusEvent(type, eventInit); break;
358+
case 'drag': event = new DragEvent(type, eventInit); break;
359+
default: event = new Event(type, eventInit); break;
360+
}
361+
node.dispatchEvent(event);
362+
}
363+
349364
private _parentElementOrShadowHost(element: Element): Element | undefined {
350365
if (element.parentElement)
351366
return element.parentElement;
@@ -368,3 +383,51 @@ export class Injected {
368383
return element;
369384
}
370385
}
386+
387+
const eventType = new Map<string, 'mouse'|'keyboard'|'touch'|'pointer'|'focus'|'drag'>([
388+
['auxclick', 'mouse'],
389+
['click', 'mouse'],
390+
['dblclick', 'mouse'],
391+
['mousedown','mouse'],
392+
['mouseeenter', 'mouse'],
393+
['mouseleave', 'mouse'],
394+
['mousemove', 'mouse'],
395+
['mouseout', 'mouse'],
396+
['mouseover', 'mouse'],
397+
['mouseup', 'mouse'],
398+
['mouseleave', 'mouse'],
399+
['mousewheel', 'mouse'],
400+
401+
['keydown', 'keyboard'],
402+
['keyup', 'keyboard'],
403+
['keypress', 'keyboard'],
404+
['textInput', 'keyboard'],
405+
406+
['touchstart', 'touch'],
407+
['touchmove', 'touch'],
408+
['touchend', 'touch'],
409+
['touchcancel', 'touch'],
410+
411+
['pointerover', 'pointer'],
412+
['pointerout', 'pointer'],
413+
['pointerenter', 'pointer'],
414+
['pointerleave', 'pointer'],
415+
['pointerdown', 'pointer'],
416+
['pointerup', 'pointer'],
417+
['pointermove', 'pointer'],
418+
['pointercancel', 'pointer'],
419+
['gotpointercapture', 'pointer'],
420+
['lostpointercapture', 'pointer'],
421+
422+
['focus', 'focus'],
423+
['blur', 'focus'],
424+
425+
['drag', 'drag'],
426+
['dragstart', 'drag'],
427+
['dragend', 'drag'],
428+
['dragover', 'drag'],
429+
['dragenter', 'drag'],
430+
['dragleave', 'drag'],
431+
['dragexit', 'drag'],
432+
['drop', 'drag'],
433+
]);

src/page.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ export class Page extends ExtendedEventEmitter implements InnerLogger {
220220
return this.mainFrame().waitForSelector(selector, options);
221221
}
222222

223+
async dispatchEvent(selector: string, type: string, eventInit?: Object, options?: types.TimeoutOptions): Promise<void> {
224+
return this.mainFrame().dispatchEvent(selector, type, eventInit, options);
225+
}
226+
223227
async evaluateHandle<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg): Promise<types.SmartHandle<R>>;
224228
async evaluateHandle<R>(pageFunction: types.Func1<void, R>, arg?: any): Promise<types.SmartHandle<R>>;
225229
async evaluateHandle<R, Arg>(pageFunction: types.Func1<Arg, R>, arg: Arg): Promise<types.SmartHandle<R>> {

src/selectors.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,19 @@ export class Selectors {
163163
return { world: this._needsMainContext(parsed) ? 'main' : 'utility', task };
164164
}
165165

166+
_dispatchEventTask(selector: string, type: string, eventInit: Object, deadline: number): (context: dom.FrameExecutionContext) => Promise<js.JSHandle> {
167+
const parsed = this._parseSelector(selector);
168+
const task = async (context: dom.FrameExecutionContext) => context.evaluateHandleInternal(({ evaluator, parsed, type, eventInit, timeout }) => {
169+
return evaluator.injected.poll('mutation', timeout, () => {
170+
const element = evaluator.querySelector(parsed, document);
171+
if (element)
172+
evaluator.injected.dispatchEvent(element, type, eventInit);
173+
return element || false;
174+
});
175+
}, { evaluator: await this._prepareEvaluator(context), parsed, type, eventInit, timeout: helper.timeUntilDeadline(deadline) });
176+
return task;
177+
}
178+
166179
async _createSelector(name: string, handle: dom.ElementHandle<Element>): Promise<string | undefined> {
167180
const mainContext = await handle._page.mainFrame()._mainContext();
168181
return mainContext.evaluateInternal(({ evaluator, target, name }) => {

test/assets/drag-n-drop.html

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,41 @@
1-
<!DOCTYPE html>
2-
<html lang=en>
3-
<title>Examples of DataTransfer's setData(), getData() and clearData()</title>
4-
<meta content="width=device-width">
51
<style>
6-
div:not(.mouse-helper) {
7-
margin: 0em;
8-
padding: 2em;
9-
}
10-
#source {
11-
color: blue;
12-
border: 1px solid black;
13-
}
14-
#target {
15-
border: 1px solid black;
16-
}
2+
div:not(.mouse-helper) {
3+
margin: 0em;
4+
padding: 2em;
5+
}
6+
#source {
7+
color: blue;
8+
border: 1px solid black;
9+
}
10+
#target {
11+
border: 1px solid black;
12+
}
1713
</style>
14+
1815
<script>
16+
1917
function dragstart_handler(ev) {
20-
console.log("dragStart");
21-
// Change the source element's background color to signify drag has started
22-
ev.currentTarget.style.border = "dashed";
23-
// Set the drag's format and data. Use the event target's id for the data
24-
ev.dataTransfer.setData("text/plain", ev.target.id);
18+
ev.currentTarget.style.border = "dashed";
19+
ev.dataTransfer.setData("text/plain", ev.target.id);
2520
}
2621

2722
function dragover_handler(ev) {
28-
console.log("dragOver");
29-
ev.preventDefault();
23+
ev.preventDefault();
3024
}
3125

3226
function drop_handler(ev) {
33-
console.log("Drop");
34-
ev.preventDefault();
35-
// Get the data, which is the id of the drop target
36-
var data = ev.dataTransfer.getData("text");
37-
ev.target.appendChild(document.getElementById(data));
38-
// Clear the drag data cache (for all formats/types)
39-
ev.dataTransfer.clearData();
27+
console.log("Drop");
28+
ev.preventDefault();
29+
var data = ev.dataTransfer.getData("text");
30+
ev.target.appendChild(document.getElementById(data));
31+
ev.dataTransfer.clearData();
4032
}
4133
</script>
34+
4235
<body>
43-
<script src="input/mouse-helper.js"></script>
44-
<h1>Examples of <code>DataTransfer</code>: <code>setData()</code>, <code>getData()</code>, <code>clearData()</code></h1>
45-
<div>
46-
<p id="source" ondragstart="dragstart_handler(event);" draggable="true">
47-
Select this element, drag it to the Drop Zone and then release the selection to move the element.</p>
48-
</div>
49-
<div id="target" ondrop="drop_handler(event);" ondragover="dragover_handler(event);">Drop Zone</div>
36+
<div>
37+
<p id="source" ondragstart="dragstart_handler(event);" draggable="true">
38+
Select this element, drag it to the Drop Zone and then release the selection to move the element.</p>
39+
</div>
40+
<div id="target" ondrop="drop_handler(event);" ondragover="dragover_handler(event);">Drop Zone</div>
5041
</body>
51-
</html>

test/assets/input/button.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
window.shiftKey = undefined;
1616
window.pageX = undefined;
1717
window.pageY = undefined;
18+
window.bubbles = undefined;
1819
document.querySelector('button').addEventListener('click', e => {
1920
result = 'Clicked';
2021
offsetX = e.offsetX;
2122
offsetY = e.offsetY;
2223
pageX = e.pageX;
2324
pageY = e.pageY;
2425
shiftKey = e.shiftKey;
26+
bubbles = e.bubbles;
27+
cancelable = e.cancelable;
28+
composed = e.composed;
2529
}, false);
2630
</script>
2731
</body>

0 commit comments

Comments
 (0)