mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: encapsulate dom-related logic to DOMWorld, part 1 (#112)
This commit is contained in:
parent
336338c97e
commit
8efd258e40
@ -18,7 +18,6 @@
|
|||||||
import { CDPSession } from './Connection';
|
import { CDPSession } from './Connection';
|
||||||
import { helper } from '../helper';
|
import { helper } from '../helper';
|
||||||
import { valueFromRemoteObject, getExceptionMessage, releaseObject } from './protocolHelper';
|
import { valueFromRemoteObject, getExceptionMessage, releaseObject } from './protocolHelper';
|
||||||
import { createJSHandle } from './JSHandle';
|
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import * as js from '../javascript';
|
import * as js from '../javascript';
|
||||||
import * as dom from '../dom';
|
import * as dom from '../dom';
|
||||||
@ -51,7 +50,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
|||||||
}).catch(rewriteError);
|
}).catch(rewriteError);
|
||||||
if (exceptionDetails)
|
if (exceptionDetails)
|
||||||
throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails));
|
throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails));
|
||||||
return returnByValue ? valueFromRemoteObject(remoteObject) : createJSHandle(context, remoteObject);
|
return returnByValue ? valueFromRemoteObject(remoteObject) : toHandle(context, remoteObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof pageFunction !== 'function')
|
if (typeof pageFunction !== 'function')
|
||||||
@ -92,7 +91,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
|||||||
const { exceptionDetails, result: remoteObject } = await callFunctionOnPromise.catch(rewriteError);
|
const { exceptionDetails, result: remoteObject } = await callFunctionOnPromise.catch(rewriteError);
|
||||||
if (exceptionDetails)
|
if (exceptionDetails)
|
||||||
throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails));
|
throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails));
|
||||||
return returnByValue ? valueFromRemoteObject(remoteObject) : createJSHandle(context, remoteObject);
|
return returnByValue ? valueFromRemoteObject(remoteObject) : toHandle(context, remoteObject);
|
||||||
|
|
||||||
function convertArgument(arg: any): any {
|
function convertArgument(arg: any): any {
|
||||||
if (typeof arg === 'bigint') // eslint-disable-line valid-typeof
|
if (typeof arg === 'bigint') // eslint-disable-line valid-typeof
|
||||||
@ -133,14 +132,6 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async adoptBackendNodeId(context: js.ExecutionContext, backendNodeId: Protocol.DOM.BackendNodeId) {
|
|
||||||
const {object} = await this._client.send('DOM.resolveNode', {
|
|
||||||
backendNodeId,
|
|
||||||
executionContextId: this._contextId,
|
|
||||||
});
|
|
||||||
return createJSHandle(context, object) as dom.ElementHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getProperties(handle: js.JSHandle): Promise<Map<string, js.JSHandle>> {
|
async getProperties(handle: js.JSHandle): Promise<Map<string, js.JSHandle>> {
|
||||||
const response = await this._client.send('Runtime.getProperties', {
|
const response = await this._client.send('Runtime.getProperties', {
|
||||||
objectId: toRemoteObject(handle).objectId,
|
objectId: toRemoteObject(handle).objectId,
|
||||||
@ -150,7 +141,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
|||||||
for (const property of response.result) {
|
for (const property of response.result) {
|
||||||
if (!property.enumerable)
|
if (!property.enumerable)
|
||||||
continue;
|
continue;
|
||||||
result.set(property.name, createJSHandle(handle.executionContext(), property.value));
|
result.set(property.name, toHandle(handle.executionContext(), property.value));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -189,6 +180,13 @@ export function toRemoteObject(handle: js.JSHandle): Protocol.Runtime.RemoteObje
|
|||||||
return (handle as any)[remoteObjectSymbol];
|
return (handle as any)[remoteObjectSymbol];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markJSHandle(handle: js.JSHandle, remoteObject: Protocol.Runtime.RemoteObject) {
|
export function toHandle(context: js.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
|
||||||
|
if (remoteObject.subtype === 'node' && context.frame()) {
|
||||||
|
const handle = new dom.ElementHandle(context);
|
||||||
|
(handle as any)[remoteObjectSymbol] = remoteObject;
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
const handle = new js.JSHandle(context);
|
||||||
(handle as any)[remoteObjectSymbol] = remoteObject;
|
(handle as any)[remoteObjectSymbol] = remoteObject;
|
||||||
|
return handle;
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
import * as dom from '../dom';
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import { assert, debugError } from '../helper';
|
import { assert, debugError } from '../helper';
|
||||||
import * as js from '../javascript';
|
import * as js from '../javascript';
|
||||||
import * as dom from '../dom';
|
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { TimeoutSettings } from '../TimeoutSettings';
|
import { TimeoutSettings } from '../TimeoutSettings';
|
||||||
import { CDPSession } from './Connection';
|
import { CDPSession } from './Connection';
|
||||||
import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate, toRemoteObject } from './ExecutionContext';
|
import { EVALUATION_SCRIPT_URL, ExecutionContextDelegate } from './ExecutionContext';
|
||||||
|
import { DOMWorldDelegate } from './JSHandle';
|
||||||
import { LifecycleWatcher } from './LifecycleWatcher';
|
import { LifecycleWatcher } from './LifecycleWatcher';
|
||||||
import { NetworkManager } from './NetworkManager';
|
import { NetworkManager } from './NetworkManager';
|
||||||
import { Page } from './Page';
|
import { Page } from './Page';
|
||||||
@ -178,13 +179,6 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
async adoptElementHandle(elementHandle: dom.ElementHandle, context: js.ExecutionContext): Promise<dom.ElementHandle> {
|
|
||||||
const nodeInfo = await this._client.send('DOM.describeNode', {
|
|
||||||
objectId: toRemoteObject(elementHandle).objectId,
|
|
||||||
});
|
|
||||||
return (context._delegate as ExecutionContextDelegate).adoptBackendNodeId(context, nodeInfo.node.backendNodeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onLifecycleEvent(event: Protocol.Page.lifecycleEventPayload) {
|
_onLifecycleEvent(event: Protocol.Page.lifecycleEventPayload) {
|
||||||
const frame = this._frames.get(event.frameId);
|
const frame = this._frames.get(event.frameId);
|
||||||
if (!frame)
|
if (!frame)
|
||||||
@ -324,7 +318,9 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
const frame = this._frames.get(frameId) || null;
|
const frame = this._frames.get(frameId) || null;
|
||||||
if (contextPayload.auxData && contextPayload.auxData['type'] === 'isolated')
|
if (contextPayload.auxData && contextPayload.auxData['type'] === 'isolated')
|
||||||
this._isolatedWorlds.add(contextPayload.name);
|
this._isolatedWorlds.add(contextPayload.name);
|
||||||
const context: js.ExecutionContext = new js.ExecutionContext(new ExecutionContextDelegate(this._client, contextPayload), frame);
|
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._client, contextPayload));
|
||||||
|
if (frame)
|
||||||
|
context._domWorld = new dom.DOMWorld(context, new DOMWorldDelegate(this, frame));
|
||||||
if (frame) {
|
if (frame) {
|
||||||
if (contextPayload.auxData && !!contextPayload.auxData['isDefault'])
|
if (contextPayload.auxData && !!contextPayload.auxData['isDefault'])
|
||||||
frame._contextCreated('main', context);
|
frame._contextCreated('main', context);
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert, debugError } from '../helper';
|
import { assert, debugError } from '../helper';
|
||||||
import * as js from '../javascript';
|
|
||||||
import * as dom from '../dom';
|
import * as dom from '../dom';
|
||||||
import * as input from '../input';
|
import * as input from '../input';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
@ -24,29 +23,20 @@ import * as frames from '../frames';
|
|||||||
import { CDPSession } from './Connection';
|
import { CDPSession } from './Connection';
|
||||||
import { FrameManager } from './FrameManager';
|
import { FrameManager } from './FrameManager';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { ExecutionContextDelegate, markJSHandle, toRemoteObject } from './ExecutionContext';
|
import { toRemoteObject, toHandle, ExecutionContextDelegate } from './ExecutionContext';
|
||||||
|
|
||||||
export function createJSHandle(context: js.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject): js.JSHandle {
|
export class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
||||||
const frame = context.frame();
|
readonly keyboard: input.Keyboard;
|
||||||
if (remoteObject.subtype === 'node' && frame) {
|
readonly mouse: input.Mouse;
|
||||||
const frameManager = frame._delegate as FrameManager;
|
readonly frame: frames.Frame;
|
||||||
const page = frameManager.page();
|
|
||||||
const delegate = new DOMWorldDelegate((context._delegate as ExecutionContextDelegate)._client, frameManager);
|
|
||||||
const handle = new dom.ElementHandle(context, page.keyboard, page.mouse, delegate);
|
|
||||||
markJSHandle(handle, remoteObject);
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
const handle = new js.JSHandle(context);
|
|
||||||
markJSHandle(handle, remoteObject);
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
|
||||||
private _client: CDPSession;
|
private _client: CDPSession;
|
||||||
private _frameManager: FrameManager;
|
private _frameManager: FrameManager;
|
||||||
|
|
||||||
constructor(client: CDPSession, frameManager: FrameManager) {
|
constructor(frameManager: FrameManager, frame: frames.Frame) {
|
||||||
this._client = client;
|
this.keyboard = frameManager.page().keyboard;
|
||||||
|
this.mouse = frameManager.page().mouse;
|
||||||
|
this.frame = frame;
|
||||||
|
this._client = frameManager._client;
|
||||||
this._frameManager = frameManager;
|
this._frameManager = frameManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,8 +173,8 @@ class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
|||||||
// Filter out quads that have too small area to click into.
|
// Filter out quads that have too small area to click into.
|
||||||
const { clientWidth, clientHeight } = layoutMetrics.layoutViewport;
|
const { clientWidth, clientHeight } = layoutMetrics.layoutViewport;
|
||||||
const quads = result.quads.map(fromProtocolQuad)
|
const quads = result.quads.map(fromProtocolQuad)
|
||||||
.map(quad => intersectQuadWithViewport(quad, clientWidth, clientHeight))
|
.map(quad => intersectQuadWithViewport(quad, clientWidth, clientHeight))
|
||||||
.filter(quad => computeQuadArea(quad) > 1);
|
.filter(quad => computeQuadArea(quad) > 1);
|
||||||
if (!quads.length)
|
if (!quads.length)
|
||||||
throw new Error('Node is either not visible or not an HTMLElement');
|
throw new Error('Node is either not visible or not an HTMLElement');
|
||||||
// Return the middle point of the first quad.
|
// Return the middle point of the first quad.
|
||||||
@ -234,4 +224,19 @@ class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
|||||||
async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void> {
|
async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void> {
|
||||||
await handle.evaluate(input.setFileInputFunction, files);
|
await handle.evaluate(input.setFileInputFunction, files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise<dom.ElementHandle> {
|
||||||
|
const nodeInfo = await this._client.send('DOM.describeNode', {
|
||||||
|
objectId: toRemoteObject(handle).objectId,
|
||||||
|
});
|
||||||
|
return this.adoptBackendNodeId(nodeInfo.node.backendNodeId, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
async adoptBackendNodeId(backendNodeId: Protocol.DOM.BackendNodeId, to: dom.DOMWorld): Promise<dom.ElementHandle> {
|
||||||
|
const {object} = await this._client.send('DOM.resolveNode', {
|
||||||
|
backendNodeId,
|
||||||
|
executionContextId: (to.context._delegate as ExecutionContextDelegate)._contextId,
|
||||||
|
});
|
||||||
|
return toHandle(to.context, object).asElement()!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,7 @@ import { PDF } from './features/pdf';
|
|||||||
import { Workers } from './features/workers';
|
import { Workers } from './features/workers';
|
||||||
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
||||||
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
||||||
import { createJSHandle } from './JSHandle';
|
import { toHandle, toRemoteObject } from './ExecutionContext';
|
||||||
import { toRemoteObject } from './ExecutionContext';
|
|
||||||
import { NetworkManagerEvents } from './NetworkManager';
|
import { NetworkManagerEvents } from './NetworkManager';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { getExceptionMessage, releaseObject, valueFromRemoteObject } from './protocolHelper';
|
import { getExceptionMessage, releaseObject, valueFromRemoteObject } from './protocolHelper';
|
||||||
@ -48,7 +47,7 @@ import * as dom from '../dom';
|
|||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import * as js from '../javascript';
|
import * as js from '../javascript';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import { ExecutionContextDelegate } from './ExecutionContext';
|
import { DOMWorldDelegate } from './JSHandle';
|
||||||
|
|
||||||
const writeFileAsync = helper.promisify(fs.writeFile);
|
const writeFileAsync = helper.promisify(fs.writeFile);
|
||||||
|
|
||||||
@ -159,8 +158,8 @@ export class Page extends EventEmitter {
|
|||||||
if (!this._fileChooserInterceptors.size)
|
if (!this._fileChooserInterceptors.size)
|
||||||
return;
|
return;
|
||||||
const frame = this._frameManager.frame(event.frameId);
|
const frame = this._frameManager.frame(event.frameId);
|
||||||
const context = await frame._utilityContext();
|
const utilityWorld = await frame._utilityDOMWorld();
|
||||||
const handle = await (context._delegate as ExecutionContextDelegate).adoptBackendNodeId(context, event.backendNodeId);
|
const handle = await (utilityWorld.delegate as DOMWorldDelegate).adoptBackendNodeId(event.backendNodeId, utilityWorld);
|
||||||
const interceptors = Array.from(this._fileChooserInterceptors);
|
const interceptors = Array.from(this._fileChooserInterceptors);
|
||||||
this._fileChooserInterceptors.clear();
|
this._fileChooserInterceptors.clear();
|
||||||
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);
|
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);
|
||||||
@ -321,7 +320,7 @@ export class Page extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const context = this._frameManager.executionContextById(event.executionContextId);
|
const context = this._frameManager.executionContextById(event.executionContextId);
|
||||||
const values = event.args.map(arg => createJSHandle(context, arg));
|
const values = event.args.map(arg => toHandle(context, arg));
|
||||||
this._addConsoleMessage(event.type, values, event.stackTrace);
|
this._addConsoleMessage(event.type, values, event.stackTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,8 +21,7 @@ import { Protocol } from '../protocol';
|
|||||||
import { Events } from '../events';
|
import { Events } from '../events';
|
||||||
import * as types from '../../types';
|
import * as types from '../../types';
|
||||||
import * as js from '../../javascript';
|
import * as js from '../../javascript';
|
||||||
import { ExecutionContextDelegate } from '../ExecutionContext';
|
import { toHandle, ExecutionContextDelegate } from '../ExecutionContext';
|
||||||
import { createJSHandle } from '../JSHandle';
|
|
||||||
|
|
||||||
type AddToConsoleCallback = (type: string, args: js.JSHandle[], stackTrace: Protocol.Runtime.StackTrace | undefined) => void;
|
type AddToConsoleCallback = (type: string, args: js.JSHandle[], stackTrace: Protocol.Runtime.StackTrace | undefined) => void;
|
||||||
type HandleExceptionCallback = (exceptionDetails: Protocol.Runtime.ExceptionDetails) => void;
|
type HandleExceptionCallback = (exceptionDetails: Protocol.Runtime.ExceptionDetails) => void;
|
||||||
@ -68,8 +67,8 @@ export class Worker extends EventEmitter {
|
|||||||
this._executionContextPromise = new Promise(x => this._executionContextCallback = x);
|
this._executionContextPromise = new Promise(x => this._executionContextCallback = x);
|
||||||
let jsHandleFactory: (o: Protocol.Runtime.RemoteObject) => js.JSHandle;
|
let jsHandleFactory: (o: Protocol.Runtime.RemoteObject) => js.JSHandle;
|
||||||
this._client.once('Runtime.executionContextCreated', async event => {
|
this._client.once('Runtime.executionContextCreated', async event => {
|
||||||
jsHandleFactory = remoteObject => createJSHandle(executionContext, remoteObject);
|
jsHandleFactory = remoteObject => toHandle(executionContext, remoteObject);
|
||||||
const executionContext = new js.ExecutionContext(new ExecutionContextDelegate(client, event.context), null);
|
const executionContext = new js.ExecutionContext(new ExecutionContextDelegate(client, event.context));
|
||||||
this._executionContextCallback(executionContext);
|
this._executionContextCallback(executionContext);
|
||||||
});
|
});
|
||||||
// This might fail if the target is closed before we recieve all execution contexts.
|
// This might fail if the target is closed before we recieve all execution contexts.
|
||||||
|
97
src/dom.ts
97
src/dom.ts
@ -7,28 +7,73 @@ import Injected from './injected/injected';
|
|||||||
import * as input from './input';
|
import * as input from './input';
|
||||||
import * as js from './javascript';
|
import * as js from './javascript';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
|
import * as injectedSource from './generated/injectedSource';
|
||||||
|
import * as cssSelectorEngineSource from './generated/cssSelectorEngineSource';
|
||||||
|
import * as xpathSelectorEngineSource from './generated/xpathSelectorEngineSource';
|
||||||
|
|
||||||
type SelectorRoot = Element | ShadowRoot | Document;
|
type SelectorRoot = Element | ShadowRoot | Document;
|
||||||
|
|
||||||
export interface DOMWorldDelegate {
|
export interface DOMWorldDelegate {
|
||||||
|
keyboard: input.Keyboard;
|
||||||
|
mouse: input.Mouse;
|
||||||
|
frame: frames.Frame;
|
||||||
isJavascriptEnabled(): boolean;
|
isJavascriptEnabled(): boolean;
|
||||||
contentFrame(handle: ElementHandle): Promise<frames.Frame | null>;
|
contentFrame(handle: ElementHandle): Promise<frames.Frame | null>;
|
||||||
boundingBox(handle: ElementHandle): Promise<types.Rect | null>;
|
boundingBox(handle: ElementHandle): Promise<types.Rect | null>;
|
||||||
screenshot(handle: ElementHandle, options?: any): Promise<string | Buffer>;
|
screenshot(handle: ElementHandle, options?: any): Promise<string | Buffer>;
|
||||||
ensurePointerActionPoint(handle: ElementHandle, relativePoint?: types.Point): Promise<types.Point>;
|
ensurePointerActionPoint(handle: ElementHandle, relativePoint?: types.Point): Promise<types.Point>;
|
||||||
setInputFiles(handle: ElementHandle, files: input.FilePayload[]): Promise<void>;
|
setInputFiles(handle: ElementHandle, files: input.FilePayload[]): Promise<void>;
|
||||||
|
adoptElementHandle(handle: ElementHandle, to: DOMWorld): Promise<ElementHandle>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DOMWorld {
|
||||||
|
readonly context: js.ExecutionContext;
|
||||||
|
readonly delegate: DOMWorldDelegate;
|
||||||
|
|
||||||
|
private _injectedPromise?: Promise<js.JSHandle>;
|
||||||
|
private _documentPromise?: Promise<ElementHandle>;
|
||||||
|
|
||||||
|
constructor(context: js.ExecutionContext, delegate: DOMWorldDelegate) {
|
||||||
|
this.context = context;
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
injected(): Promise<js.JSHandle> {
|
||||||
|
if (!this._injectedPromise) {
|
||||||
|
const engineSources = [cssSelectorEngineSource.source, xpathSelectorEngineSource.source];
|
||||||
|
const source = `
|
||||||
|
new (${injectedSource.source})([
|
||||||
|
${engineSources.join(',\n')}
|
||||||
|
])
|
||||||
|
`;
|
||||||
|
this._injectedPromise = this.context.evaluateHandle(source);
|
||||||
|
}
|
||||||
|
return this._injectedPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
_document(): Promise<ElementHandle> {
|
||||||
|
if (!this._documentPromise)
|
||||||
|
this._documentPromise = this.context.evaluateHandle('document').then(handle => handle.asElement()!);
|
||||||
|
return this._documentPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async adoptElementHandle(handle: ElementHandle, dispose: boolean): Promise<ElementHandle> {
|
||||||
|
if (handle.executionContext() === this.context)
|
||||||
|
return handle;
|
||||||
|
const adopted = this.delegate.adoptElementHandle(handle, this);
|
||||||
|
if (dispose)
|
||||||
|
await handle.dispose();
|
||||||
|
return adopted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ElementHandle extends js.JSHandle {
|
export class ElementHandle extends js.JSHandle {
|
||||||
private _delegate: DOMWorldDelegate;
|
private readonly _world: DOMWorld;
|
||||||
private _keyboard: input.Keyboard;
|
|
||||||
private _mouse: input.Mouse;
|
|
||||||
|
|
||||||
constructor(context: js.ExecutionContext, keyboard: input.Keyboard, mouse: input.Mouse, delegate: DOMWorldDelegate) {
|
constructor(context: js.ExecutionContext) {
|
||||||
super(context);
|
super(context);
|
||||||
this._delegate = delegate;
|
assert(context._domWorld, 'Element handle should have a dom world');
|
||||||
this._keyboard = keyboard;
|
this._world = context._domWorld;
|
||||||
this._mouse = mouse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
asElement(): ElementHandle | null {
|
asElement(): ElementHandle | null {
|
||||||
@ -36,7 +81,7 @@ export class ElementHandle extends js.JSHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async contentFrame(): Promise<frames.Frame | null> {
|
async contentFrame(): Promise<frames.Frame | null> {
|
||||||
return this._delegate.contentFrame(this);
|
return this._world.delegate.contentFrame(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _scrollIntoViewIfNeeded() {
|
async _scrollIntoViewIfNeeded() {
|
||||||
@ -63,35 +108,35 @@ export class ElementHandle extends js.JSHandle {
|
|||||||
if (visibleRatio !== 1.0)
|
if (visibleRatio !== 1.0)
|
||||||
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
|
element.scrollIntoView({block: 'center', inline: 'center', behavior: 'instant'});
|
||||||
return false;
|
return false;
|
||||||
}, this._delegate.isJavascriptEnabled());
|
}, this._world.delegate.isJavascriptEnabled());
|
||||||
if (error)
|
if (error)
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _performPointerAction(action: (point: types.Point) => Promise<void>, options?: input.PointerActionOptions): Promise<void> {
|
async _performPointerAction(action: (point: types.Point) => Promise<void>, options?: input.PointerActionOptions): Promise<void> {
|
||||||
const point = await this._delegate.ensurePointerActionPoint(this, options ? options.relativePoint : undefined);
|
const point = await this._world.delegate.ensurePointerActionPoint(this, options ? options.relativePoint : undefined);
|
||||||
let restoreModifiers: input.Modifier[] | undefined;
|
let restoreModifiers: input.Modifier[] | undefined;
|
||||||
if (options && options.modifiers)
|
if (options && options.modifiers)
|
||||||
restoreModifiers = await this._keyboard._ensureModifiers(options.modifiers);
|
restoreModifiers = await this._world.delegate.keyboard._ensureModifiers(options.modifiers);
|
||||||
await action(point);
|
await action(point);
|
||||||
if (restoreModifiers)
|
if (restoreModifiers)
|
||||||
await this._keyboard._ensureModifiers(restoreModifiers);
|
await this._world.delegate.keyboard._ensureModifiers(restoreModifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
hover(options?: input.PointerActionOptions): Promise<void> {
|
hover(options?: input.PointerActionOptions): Promise<void> {
|
||||||
return this._performPointerAction(point => this._mouse.move(point.x, point.y), options);
|
return this._performPointerAction(point => this._world.delegate.mouse.move(point.x, point.y), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
click(options?: input.ClickOptions): Promise<void> {
|
click(options?: input.ClickOptions): Promise<void> {
|
||||||
return this._performPointerAction(point => this._mouse.click(point.x, point.y, options), options);
|
return this._performPointerAction(point => this._world.delegate.mouse.click(point.x, point.y, options), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
dblclick(options?: input.MultiClickOptions): Promise<void> {
|
dblclick(options?: input.MultiClickOptions): Promise<void> {
|
||||||
return this._performPointerAction(point => this._mouse.dblclick(point.x, point.y, options), options);
|
return this._performPointerAction(point => this._world.delegate.mouse.dblclick(point.x, point.y, options), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
tripleclick(options?: input.MultiClickOptions): Promise<void> {
|
tripleclick(options?: input.MultiClickOptions): Promise<void> {
|
||||||
return this._performPointerAction(point => this._mouse.tripleclick(point.x, point.y, options), options);
|
return this._performPointerAction(point => this._world.delegate.mouse.tripleclick(point.x, point.y, options), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async select(...values: (string | ElementHandle | input.SelectOption)[]): Promise<string[]> {
|
async select(...values: (string | ElementHandle | input.SelectOption)[]): Promise<string[]> {
|
||||||
@ -115,13 +160,13 @@ export class ElementHandle extends js.JSHandle {
|
|||||||
if (error)
|
if (error)
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
await this.focus();
|
await this.focus();
|
||||||
await this._keyboard.sendCharacters(value);
|
await this._world.delegate.keyboard.sendCharacters(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setInputFiles(...files: (string|input.FilePayload)[]) {
|
async setInputFiles(...files: (string|input.FilePayload)[]) {
|
||||||
const multiple = await this.evaluate((element: HTMLInputElement) => !!element.multiple);
|
const multiple = await this.evaluate((element: HTMLInputElement) => !!element.multiple);
|
||||||
assert(multiple || files.length <= 1, 'Non-multiple file input can only accept single file!');
|
assert(multiple || files.length <= 1, 'Non-multiple file input can only accept single file!');
|
||||||
await this._delegate.setInputFiles(this, await input.loadFiles(files));
|
await this._world.delegate.setInputFiles(this, await input.loadFiles(files));
|
||||||
}
|
}
|
||||||
|
|
||||||
async focus() {
|
async focus() {
|
||||||
@ -130,26 +175,26 @@ export class ElementHandle extends js.JSHandle {
|
|||||||
|
|
||||||
async type(text: string, options: { delay: (number | undefined); } | undefined) {
|
async type(text: string, options: { delay: (number | undefined); } | undefined) {
|
||||||
await this.focus();
|
await this.focus();
|
||||||
await this._keyboard.type(text, options);
|
await this._world.delegate.keyboard.type(text, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async press(key: string, options: { delay?: number; text?: string; } | undefined) {
|
async press(key: string, options: { delay?: number; text?: string; } | undefined) {
|
||||||
await this.focus();
|
await this.focus();
|
||||||
await this._keyboard.press(key, options);
|
await this._world.delegate.keyboard.press(key, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async boundingBox(): Promise<types.Rect | null> {
|
async boundingBox(): Promise<types.Rect | null> {
|
||||||
return this._delegate.boundingBox(this);
|
return this._world.delegate.boundingBox(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async screenshot(options: any = {}): Promise<string | Buffer> {
|
async screenshot(options: any = {}): Promise<string | Buffer> {
|
||||||
return this._delegate.screenshot(this, options);
|
return this._world.delegate.screenshot(this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $(selector: string): Promise<ElementHandle | null> {
|
async $(selector: string): Promise<ElementHandle | null> {
|
||||||
const handle = await this.evaluateHandle(
|
const handle = await this.evaluateHandle(
|
||||||
(root: SelectorRoot, selector: string, injected: Injected) => injected.querySelector('css=' + selector, root),
|
(root: SelectorRoot, selector: string, injected: Injected) => injected.querySelector('css=' + selector, root),
|
||||||
selector, await this._context._injected()
|
selector, await this._world.injected()
|
||||||
);
|
);
|
||||||
const element = handle.asElement();
|
const element = handle.asElement();
|
||||||
if (element)
|
if (element)
|
||||||
@ -161,7 +206,7 @@ export class ElementHandle extends js.JSHandle {
|
|||||||
async $$(selector: string): Promise<ElementHandle[]> {
|
async $$(selector: string): Promise<ElementHandle[]> {
|
||||||
const arrayHandle = await this.evaluateHandle(
|
const arrayHandle = await this.evaluateHandle(
|
||||||
(root: SelectorRoot, selector: string, injected: Injected) => injected.querySelectorAll('css=' + selector, root),
|
(root: SelectorRoot, selector: string, injected: Injected) => injected.querySelectorAll('css=' + selector, root),
|
||||||
selector, await this._context._injected()
|
selector, await this._world.injected()
|
||||||
);
|
);
|
||||||
const properties = await arrayHandle.getProperties();
|
const properties = await arrayHandle.getProperties();
|
||||||
await arrayHandle.dispose();
|
await arrayHandle.dispose();
|
||||||
@ -186,7 +231,7 @@ export class ElementHandle extends js.JSHandle {
|
|||||||
$$eval: types.$$Eval = async (selector, pageFunction, ...args) => {
|
$$eval: types.$$Eval = async (selector, pageFunction, ...args) => {
|
||||||
const arrayHandle = await this.evaluateHandle(
|
const arrayHandle = await this.evaluateHandle(
|
||||||
(root: SelectorRoot, selector: string, injected: Injected) => injected.querySelectorAll('css=' + selector, root),
|
(root: SelectorRoot, selector: string, injected: Injected) => injected.querySelectorAll('css=' + selector, root),
|
||||||
selector, await this._context._injected()
|
selector, await this._world.injected()
|
||||||
);
|
);
|
||||||
|
|
||||||
const result = await arrayHandle.evaluate(pageFunction, ...args as any);
|
const result = await arrayHandle.evaluate(pageFunction, ...args as any);
|
||||||
@ -197,7 +242,7 @@ export class ElementHandle extends js.JSHandle {
|
|||||||
async $x(expression: string): Promise<ElementHandle[]> {
|
async $x(expression: string): Promise<ElementHandle[]> {
|
||||||
const arrayHandle = await this.evaluateHandle(
|
const arrayHandle = await this.evaluateHandle(
|
||||||
(root: SelectorRoot, expression: string, injected: Injected) => injected.querySelectorAll('xpath=' + expression, root),
|
(root: SelectorRoot, expression: string, injected: Injected) => injected.querySelectorAll('xpath=' + expression, root),
|
||||||
expression, await this._context._injected()
|
expression, await this._world.injected()
|
||||||
);
|
);
|
||||||
const properties = await arrayHandle.getProperties();
|
const properties = await arrayHandle.getProperties();
|
||||||
await arrayHandle.dispose();
|
await arrayHandle.dispose();
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {helper, debugError} from '../helper';
|
import {helper, debugError} from '../helper';
|
||||||
import { createHandle } from './JSHandle';
|
|
||||||
import * as js from '../javascript';
|
import * as js from '../javascript';
|
||||||
|
import * as dom from '../dom';
|
||||||
import { JugglerSession } from './Connection';
|
import { JugglerSession } from './Connection';
|
||||||
|
|
||||||
export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
||||||
@ -48,7 +48,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
|||||||
expression: pageFunction.trim(),
|
expression: pageFunction.trim(),
|
||||||
executionContextId: this._executionContextId,
|
executionContextId: this._executionContextId,
|
||||||
}).catch(rewriteError);
|
}).catch(rewriteError);
|
||||||
return createHandle(context, payload.result, payload.exceptionDetails);
|
return toHandle(context, payload.result, payload.exceptionDetails);
|
||||||
}
|
}
|
||||||
if (typeof pageFunction !== 'function')
|
if (typeof pageFunction !== 'function')
|
||||||
throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`);
|
throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`);
|
||||||
@ -101,7 +101,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
const payload = await callFunctionPromise.catch(rewriteError);
|
const payload = await callFunctionPromise.catch(rewriteError);
|
||||||
return createHandle(context, payload.result, payload.exceptionDetails);
|
return toHandle(context, payload.result, payload.exceptionDetails);
|
||||||
|
|
||||||
function rewriteError(error) {
|
function rewriteError(error) {
|
||||||
if (error.message.includes('Failed to find execution context with id'))
|
if (error.message.includes('Failed to find execution context with id'))
|
||||||
@ -117,7 +117,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
|||||||
});
|
});
|
||||||
const result = new Map();
|
const result = new Map();
|
||||||
for (const property of response.properties)
|
for (const property of response.properties)
|
||||||
result.set(property.name, createHandle(handle.executionContext(), property.value, null));
|
result.set(property.name, toHandle(handle.executionContext(), property.value, null));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,8 +163,21 @@ export function toPayload(handle: js.JSHandle): any {
|
|||||||
return (handle as any)[payloadSymbol];
|
return (handle as any)[payloadSymbol];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markJSHandle(handle: js.JSHandle, payload: any) {
|
export function toHandle(context: js.ExecutionContext, result: any, exceptionDetails?: any) {
|
||||||
(handle as any)[payloadSymbol] = payload;
|
if (exceptionDetails) {
|
||||||
|
if (exceptionDetails.value)
|
||||||
|
throw new Error('Evaluation failed: ' + JSON.stringify(exceptionDetails.value));
|
||||||
|
else
|
||||||
|
throw new Error('Evaluation failed: ' + exceptionDetails.text + '\n' + exceptionDetails.stack);
|
||||||
|
}
|
||||||
|
if (result.subtype === 'node') {
|
||||||
|
const handle = new dom.ElementHandle(context);
|
||||||
|
(handle as any)[payloadSymbol] = result;
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
const handle = new js.JSHandle(context);
|
||||||
|
(handle as any)[payloadSymbol] = result;
|
||||||
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deserializeValue({unserializableValue, value}) {
|
export function deserializeValue({unserializableValue, value}) {
|
||||||
|
@ -27,6 +27,7 @@ import { ExecutionContextDelegate } from './ExecutionContext';
|
|||||||
import { NavigationWatchdog, NextNavigationWatchdog } from './NavigationWatchdog';
|
import { NavigationWatchdog, NextNavigationWatchdog } from './NavigationWatchdog';
|
||||||
import { Page } from './Page';
|
import { Page } from './Page';
|
||||||
import { NetworkManager } from './NetworkManager';
|
import { NetworkManager } from './NetworkManager';
|
||||||
|
import { DOMWorldDelegate } from './JSHandle';
|
||||||
|
|
||||||
export const FrameManagerEvents = {
|
export const FrameManagerEvents = {
|
||||||
FrameNavigated: Symbol('FrameManagerEvents.FrameNavigated'),
|
FrameNavigated: Symbol('FrameManagerEvents.FrameNavigated'),
|
||||||
@ -80,8 +81,9 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
_onExecutionContextCreated({executionContextId, auxData}) {
|
_onExecutionContextCreated({executionContextId, auxData}) {
|
||||||
const frameId = auxData ? auxData.frameId : null;
|
const frameId = auxData ? auxData.frameId : null;
|
||||||
const frame = this._frames.get(frameId) || null;
|
const frame = this._frames.get(frameId) || null;
|
||||||
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, executionContextId), frame);
|
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, executionContextId));
|
||||||
if (frame) {
|
if (frame) {
|
||||||
|
context._domWorld = new dom.DOMWorld(context, new DOMWorldDelegate(this, frame));
|
||||||
frame._contextCreated('main', context);
|
frame._contextCreated('main', context);
|
||||||
frame._contextCreated('utility', context);
|
frame._contextCreated('utility', context);
|
||||||
}
|
}
|
||||||
@ -175,11 +177,6 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
helper.removeEventListeners(this._eventListeners);
|
helper.removeEventListeners(this._eventListeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
async adoptElementHandle(elementHandle: dom.ElementHandle, context: js.ExecutionContext): Promise<dom.ElementHandle> {
|
|
||||||
assert(false, 'Multiple isolated worlds are not implemented');
|
|
||||||
return elementHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForFrameNavigation(frame: frames.Frame, options: { timeout?: number; waitUntil?: string | Array<string>; } = {}) {
|
async waitForFrameNavigation(frame: frames.Frame, options: { timeout?: number; waitUntil?: string | Array<string>; } = {}) {
|
||||||
const {
|
const {
|
||||||
timeout = this._timeoutSettings.navigationTimeout(),
|
timeout = this._timeoutSettings.navigationTimeout(),
|
||||||
|
@ -16,24 +16,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert, debugError } from '../helper';
|
import { assert, debugError } from '../helper';
|
||||||
import * as js from '../javascript';
|
|
||||||
import * as dom from '../dom';
|
import * as dom from '../dom';
|
||||||
import * as input from '../input';
|
import * as input from '../input';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import { JugglerSession } from './Connection';
|
import { JugglerSession } from './Connection';
|
||||||
import { FrameManager } from './FrameManager';
|
import { FrameManager } from './FrameManager';
|
||||||
import { markJSHandle, ExecutionContextDelegate, toPayload } from './ExecutionContext';
|
import { toPayload } from './ExecutionContext';
|
||||||
|
|
||||||
class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
export class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
||||||
|
readonly keyboard: input.Keyboard;
|
||||||
|
readonly mouse: input.Mouse;
|
||||||
|
readonly frame: frames.Frame;
|
||||||
private _session: JugglerSession;
|
private _session: JugglerSession;
|
||||||
private _frameManager: FrameManager;
|
private _frameManager: FrameManager;
|
||||||
private _frameId: string;
|
private _frameId: string;
|
||||||
|
|
||||||
constructor(session: JugglerSession, frameManager: FrameManager, frameId: string) {
|
constructor(frameManager: FrameManager, frame: frames.Frame) {
|
||||||
this._session = session;
|
this.keyboard = frameManager._page.keyboard;
|
||||||
|
this.mouse = frameManager._page.mouse;
|
||||||
|
this.frame = frame;
|
||||||
|
this._session = frameManager._session;
|
||||||
this._frameManager = frameManager;
|
this._frameManager = frameManager;
|
||||||
this._frameId = frameId;
|
this._frameId = frameManager._frameData(frame).frameId;
|
||||||
}
|
}
|
||||||
|
|
||||||
async contentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
|
async contentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
|
||||||
@ -129,26 +134,9 @@ class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
|||||||
async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void> {
|
async setInputFiles(handle: dom.ElementHandle, files: input.FilePayload[]): Promise<void> {
|
||||||
await handle.evaluate(input.setFileInputFunction, files);
|
await handle.evaluate(input.setFileInputFunction, files);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export function createHandle(context: js.ExecutionContext, result: any, exceptionDetails?: any) {
|
async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise<dom.ElementHandle> {
|
||||||
if (exceptionDetails) {
|
assert(false, 'Multiple isolated worlds are not implemented');
|
||||||
if (exceptionDetails.value)
|
|
||||||
throw new Error('Evaluation failed: ' + JSON.stringify(exceptionDetails.value));
|
|
||||||
else
|
|
||||||
throw new Error('Evaluation failed: ' + exceptionDetails.text + '\n' + exceptionDetails.stack);
|
|
||||||
}
|
|
||||||
if (result.subtype === 'node') {
|
|
||||||
const frame = context.frame();
|
|
||||||
const frameManager = frame._delegate as FrameManager;
|
|
||||||
const frameId = frameManager._frameData(frame).frameId;
|
|
||||||
const session = (context._delegate as ExecutionContextDelegate)._session;
|
|
||||||
const delegate = new DOMWorldDelegate(session, frameManager, frameId);
|
|
||||||
const handle = new dom.ElementHandle(context, frameManager._page.keyboard, frameManager._page.mouse, delegate);
|
|
||||||
markJSHandle(handle, result);
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
const handle = new js.JSHandle(context);
|
|
||||||
markJSHandle(handle, result);
|
|
||||||
return handle;
|
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,6 @@ import { Accessibility } from './features/accessibility';
|
|||||||
import { Interception } from './features/interception';
|
import { Interception } from './features/interception';
|
||||||
import { FrameManager, FrameManagerEvents, normalizeWaitUntil } from './FrameManager';
|
import { FrameManager, FrameManagerEvents, normalizeWaitUntil } from './FrameManager';
|
||||||
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
||||||
import { createHandle } from './JSHandle';
|
|
||||||
import { NavigationWatchdog } from './NavigationWatchdog';
|
import { NavigationWatchdog } from './NavigationWatchdog';
|
||||||
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
||||||
import * as input from '../input';
|
import * as input from '../input';
|
||||||
@ -38,7 +37,7 @@ import * as dom from '../dom';
|
|||||||
import * as js from '../javascript';
|
import * as js from '../javascript';
|
||||||
import * as network from '../network';
|
import * as network from '../network';
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import { toPayload, deserializeValue } from './ExecutionContext';
|
import { toHandle, toPayload, deserializeValue } from './ExecutionContext';
|
||||||
|
|
||||||
const writeFileAsync = helper.promisify(fs.writeFile);
|
const writeFileAsync = helper.promisify(fs.writeFile);
|
||||||
|
|
||||||
@ -547,7 +546,7 @@ export class Page extends EventEmitter {
|
|||||||
|
|
||||||
_onConsole({type, args, executionContextId, location}) {
|
_onConsole({type, args, executionContextId, location}) {
|
||||||
const context = this._frameManager.executionContextById(executionContextId);
|
const context = this._frameManager.executionContextById(executionContextId);
|
||||||
this.emit(Events.Page.Console, new ConsoleMessage(type, args.map(arg => createHandle(context, arg)), location));
|
this.emit(Events.Page.Console, new ConsoleMessage(type, args.map(arg => toHandle(context, arg)), location));
|
||||||
}
|
}
|
||||||
|
|
||||||
isClosed(): boolean {
|
isClosed(): boolean {
|
||||||
@ -571,7 +570,7 @@ export class Page extends EventEmitter {
|
|||||||
if (!this._fileChooserInterceptors.size)
|
if (!this._fileChooserInterceptors.size)
|
||||||
return;
|
return;
|
||||||
const context = this._frameManager.executionContextById(executionContextId);
|
const context = this._frameManager.executionContextById(executionContextId);
|
||||||
const handle = createHandle(context, element) as dom.ElementHandle;
|
const handle = toHandle(context, element) as dom.ElementHandle;
|
||||||
const interceptors = Array.from(this._fileChooserInterceptors);
|
const interceptors = Array.from(this._fileChooserInterceptors);
|
||||||
this._fileChooserInterceptors.clear();
|
this._fileChooserInterceptors.clear();
|
||||||
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);
|
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);
|
||||||
|
@ -48,7 +48,6 @@ export interface FrameDelegate {
|
|||||||
navigateFrame(frame: Frame, url: string, options?: GotoOptions): Promise<network.Response | null>;
|
navigateFrame(frame: Frame, url: string, options?: GotoOptions): Promise<network.Response | null>;
|
||||||
waitForFrameNavigation(frame: Frame, options?: NavigateOptions): Promise<network.Response | null>;
|
waitForFrameNavigation(frame: Frame, options?: NavigateOptions): Promise<network.Response | null>;
|
||||||
setFrameContent(frame: Frame, html: string, options?: NavigateOptions): Promise<void>;
|
setFrameContent(frame: Frame, html: string, options?: NavigateOptions): Promise<void>;
|
||||||
adoptElementHandle(elementHandle: dom.ElementHandle, context: js.ExecutionContext): Promise<dom.ElementHandle>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Frame {
|
export class Frame {
|
||||||
@ -89,12 +88,26 @@ export class Frame {
|
|||||||
return this._worlds.get('main').contextPromise;
|
return this._worlds.get('main').contextPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _mainDOMWorld(): Promise<dom.DOMWorld> {
|
||||||
|
const context = await this._mainContext();
|
||||||
|
if (!context._domWorld)
|
||||||
|
throw new Error(`Execution Context does not belong to frame`);
|
||||||
|
return context._domWorld;
|
||||||
|
}
|
||||||
|
|
||||||
_utilityContext(): Promise<js.ExecutionContext> {
|
_utilityContext(): Promise<js.ExecutionContext> {
|
||||||
if (this._detached)
|
if (this._detached)
|
||||||
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
|
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
|
||||||
return this._worlds.get('utility').contextPromise;
|
return this._worlds.get('utility').contextPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _utilityDOMWorld(): Promise<dom.DOMWorld> {
|
||||||
|
const context = await this._utilityContext();
|
||||||
|
if (!context._domWorld)
|
||||||
|
throw new Error(`Execution Context does not belong to frame`);
|
||||||
|
return context._domWorld;
|
||||||
|
}
|
||||||
|
|
||||||
executionContext(): Promise<js.ExecutionContext> {
|
executionContext(): Promise<js.ExecutionContext> {
|
||||||
return this._mainContext();
|
return this._mainContext();
|
||||||
}
|
}
|
||||||
@ -110,32 +123,32 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $(selector: string): Promise<dom.ElementHandle | null> {
|
async $(selector: string): Promise<dom.ElementHandle | null> {
|
||||||
const context = await this._mainContext();
|
const domWorld = await this._mainDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
return document.$(selector);
|
return document.$(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $x(expression: string): Promise<dom.ElementHandle[]> {
|
async $x(expression: string): Promise<dom.ElementHandle[]> {
|
||||||
const context = await this._mainContext();
|
const domWorld = await this._mainDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
return document.$x(expression);
|
return document.$x(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
$eval: types.$Eval = async (selector, pageFunction, ...args) => {
|
$eval: types.$Eval = async (selector, pageFunction, ...args) => {
|
||||||
const context = await this._mainContext();
|
const domWorld = await this._mainDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
return document.$eval(selector, pageFunction, ...args as any);
|
return document.$eval(selector, pageFunction, ...args as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
$$eval: types.$$Eval = async (selector, pageFunction, ...args) => {
|
$$eval: types.$$Eval = async (selector, pageFunction, ...args) => {
|
||||||
const context = await this._mainContext();
|
const domWorld = await this._mainDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
return document.$$eval(selector, pageFunction, ...args as any);
|
return document.$$eval(selector, pageFunction, ...args as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
async $$(selector: string): Promise<dom.ElementHandle[]> {
|
async $$(selector: string): Promise<dom.ElementHandle[]> {
|
||||||
const context = await this._mainContext();
|
const domWorld = await this._mainDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
return document.$$(selector);
|
return document.$$(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,8 +306,8 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async click(selector: string, options?: ClickOptions) {
|
async click(selector: string, options?: ClickOptions) {
|
||||||
const context = await this._utilityContext();
|
const domWorld = await this._utilityDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
const handle = await document.$(selector);
|
const handle = await document.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, 'No node found for selector: ' + selector);
|
||||||
await handle.click(options);
|
await handle.click(options);
|
||||||
@ -302,8 +315,8 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async dblclick(selector: string, options?: MultiClickOptions) {
|
async dblclick(selector: string, options?: MultiClickOptions) {
|
||||||
const context = await this._utilityContext();
|
const domWorld = await this._utilityDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
const handle = await document.$(selector);
|
const handle = await document.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, 'No node found for selector: ' + selector);
|
||||||
await handle.dblclick(options);
|
await handle.dblclick(options);
|
||||||
@ -311,8 +324,8 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async tripleclick(selector: string, options?: MultiClickOptions) {
|
async tripleclick(selector: string, options?: MultiClickOptions) {
|
||||||
const context = await this._utilityContext();
|
const domWorld = await this._utilityDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
const handle = await document.$(selector);
|
const handle = await document.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, 'No node found for selector: ' + selector);
|
||||||
await handle.tripleclick(options);
|
await handle.tripleclick(options);
|
||||||
@ -320,8 +333,8 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fill(selector: string, value: string) {
|
async fill(selector: string, value: string) {
|
||||||
const context = await this._utilityContext();
|
const domWorld = await this._utilityDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
const handle = await document.$(selector);
|
const handle = await document.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, 'No node found for selector: ' + selector);
|
||||||
await handle.fill(value);
|
await handle.fill(value);
|
||||||
@ -329,8 +342,8 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async focus(selector: string) {
|
async focus(selector: string) {
|
||||||
const context = await this._utilityContext();
|
const domWorld = await this._utilityDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
const handle = await document.$(selector);
|
const handle = await document.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, 'No node found for selector: ' + selector);
|
||||||
await handle.focus();
|
await handle.focus();
|
||||||
@ -338,8 +351,8 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async hover(selector: string, options?: PointerActionOptions) {
|
async hover(selector: string, options?: PointerActionOptions) {
|
||||||
const context = await this._utilityContext();
|
const domWorld = await this._utilityDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
const handle = await document.$(selector);
|
const handle = await document.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, 'No node found for selector: ' + selector);
|
||||||
await handle.hover(options);
|
await handle.hover(options);
|
||||||
@ -347,14 +360,13 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async select(selector: string, ...values: (string | dom.ElementHandle | SelectOption)[]): Promise<string[]> {
|
async select(selector: string, ...values: (string | dom.ElementHandle | SelectOption)[]): Promise<string[]> {
|
||||||
const context = await this._utilityContext();
|
const domWorld = await this._utilityDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
const handle = await document.$(selector);
|
const handle = await document.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, 'No node found for selector: ' + selector);
|
||||||
const utilityContext = await this._utilityContext();
|
|
||||||
const adoptedValues = await Promise.all(values.map(async value => {
|
const adoptedValues = await Promise.all(values.map(async value => {
|
||||||
if (value instanceof dom.ElementHandle)
|
if (value instanceof dom.ElementHandle)
|
||||||
return this._adoptElementHandle(value, utilityContext, false /* dispose */);
|
return domWorld.adoptElementHandle(value, false /* dispose */);
|
||||||
return value;
|
return value;
|
||||||
}));
|
}));
|
||||||
const result = await handle.select(...adoptedValues);
|
const result = await handle.select(...adoptedValues);
|
||||||
@ -363,8 +375,8 @@ export class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
||||||
const context = await this._utilityContext();
|
const domWorld = await this._utilityDOMWorld();
|
||||||
const document = await context._document();
|
const document = await domWorld._document();
|
||||||
const handle = await document.$(selector);
|
const handle = await document.$(selector);
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
assert(handle, 'No node found for selector: ' + selector);
|
||||||
await handle.type(text, options);
|
await handle.type(text, options);
|
||||||
@ -397,8 +409,8 @@ export class Frame {
|
|||||||
await handle.dispose();
|
await handle.dispose();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const mainContext = await this._mainContext();
|
const mainDOMWorld = await this._mainDOMWorld();
|
||||||
return this._adoptElementHandle(handle.asElement(), mainContext, true /* dispose */);
|
return mainDOMWorld.adoptElementHandle(handle.asElement(), true /* dispose */);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForXPath(xpath: string, options: {
|
async waitForXPath(xpath: string, options: {
|
||||||
@ -411,8 +423,8 @@ export class Frame {
|
|||||||
await handle.dispose();
|
await handle.dispose();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const mainContext = await this._mainContext();
|
const mainDOMWorld = await this._mainDOMWorld();
|
||||||
return this._adoptElementHandle(handle.asElement(), mainContext, true /* dispose */);
|
return mainDOMWorld.adoptElementHandle(handle.asElement(), true /* dispose */);
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForFunction(
|
waitForFunction(
|
||||||
@ -491,13 +503,4 @@ export class Frame {
|
|||||||
this._setContext(worldType, null);
|
this._setContext(worldType, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _adoptElementHandle(elementHandle: dom.ElementHandle, context: js.ExecutionContext, dispose: boolean): Promise<dom.ElementHandle> {
|
|
||||||
if (elementHandle.executionContext() === context)
|
|
||||||
return elementHandle;
|
|
||||||
const handle = this._delegate.adoptElementHandle(elementHandle, context);
|
|
||||||
if (dispose)
|
|
||||||
await elementHandle.dispose();
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,6 @@
|
|||||||
import * as frames from './frames';
|
import * as frames from './frames';
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import * as dom from './dom';
|
import * as dom from './dom';
|
||||||
import * as injectedSource from './generated/injectedSource';
|
|
||||||
import * as cssSelectorEngineSource from './generated/cssSelectorEngineSource';
|
|
||||||
import * as xpathSelectorEngineSource from './generated/xpathSelectorEngineSource';
|
|
||||||
|
|
||||||
export interface ExecutionContextDelegate {
|
export interface ExecutionContextDelegate {
|
||||||
evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: string | Function, ...args: any[]): Promise<any>;
|
evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: string | Function, ...args: any[]): Promise<any>;
|
||||||
@ -17,18 +14,15 @@ export interface ExecutionContextDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ExecutionContext {
|
export class ExecutionContext {
|
||||||
_delegate: ExecutionContextDelegate;
|
readonly _delegate: ExecutionContextDelegate;
|
||||||
private _frame: frames.Frame;
|
_domWorld?: dom.DOMWorld;
|
||||||
private _injectedPromise: Promise<JSHandle> | null = null;
|
|
||||||
private _documentPromise: Promise<dom.ElementHandle> | null = null;
|
|
||||||
|
|
||||||
constructor(delegate: ExecutionContextDelegate, frame: frames.Frame | null) {
|
constructor(delegate: ExecutionContextDelegate) {
|
||||||
this._delegate = delegate;
|
this._delegate = delegate;
|
||||||
this._frame = frame;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
frame(): frames.Frame | null {
|
frame(): frames.Frame | null {
|
||||||
return this._frame;
|
return this._domWorld ? this._domWorld.delegate.frame : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate: types.Evaluate = (pageFunction, ...args) => {
|
evaluate: types.Evaluate = (pageFunction, ...args) => {
|
||||||
@ -38,29 +32,10 @@ export class ExecutionContext {
|
|||||||
evaluateHandle: types.EvaluateHandle = (pageFunction, ...args) => {
|
evaluateHandle: types.EvaluateHandle = (pageFunction, ...args) => {
|
||||||
return this._delegate.evaluate(this, false /* returnByValue */, pageFunction, ...args);
|
return this._delegate.evaluate(this, false /* returnByValue */, pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
_injected(): Promise<JSHandle> {
|
|
||||||
if (!this._injectedPromise) {
|
|
||||||
const engineSources = [cssSelectorEngineSource.source, xpathSelectorEngineSource.source];
|
|
||||||
const source = `
|
|
||||||
new (${injectedSource.source})([
|
|
||||||
${engineSources.join(',\n')}
|
|
||||||
])
|
|
||||||
`;
|
|
||||||
this._injectedPromise = this.evaluateHandle(source);
|
|
||||||
}
|
|
||||||
return this._injectedPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
_document(): Promise<dom.ElementHandle> {
|
|
||||||
if (!this._documentPromise)
|
|
||||||
this._documentPromise = this.evaluateHandle('document').then(handle => handle.asElement()!);
|
|
||||||
return this._documentPromise;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class JSHandle {
|
export class JSHandle {
|
||||||
_context: ExecutionContext;
|
readonly _context: ExecutionContext;
|
||||||
_disposed = false;
|
_disposed = false;
|
||||||
|
|
||||||
constructor(context: ExecutionContext) {
|
constructor(context: ExecutionContext) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
import * as types from './types';
|
|
||||||
import * as frames from './frames';
|
import * as frames from './frames';
|
||||||
|
|
||||||
export type NetworkCookie = {
|
export type NetworkCookie = {
|
||||||
|
@ -18,9 +18,9 @@
|
|||||||
import { TargetSession } from './Connection';
|
import { TargetSession } from './Connection';
|
||||||
import { helper } from '../helper';
|
import { helper } from '../helper';
|
||||||
import { valueFromRemoteObject, releaseObject } from './protocolHelper';
|
import { valueFromRemoteObject, releaseObject } from './protocolHelper';
|
||||||
import { createJSHandle } from './JSHandle';
|
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import * as js from '../javascript';
|
import * as js from '../javascript';
|
||||||
|
import * as dom from '../dom';
|
||||||
|
|
||||||
export const EVALUATION_SCRIPT_URL = '__playwright_evaluation_script__';
|
export const EVALUATION_SCRIPT_URL = '__playwright_evaluation_script__';
|
||||||
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
|
const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
|
||||||
@ -78,7 +78,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
|||||||
if (response.wasThrown)
|
if (response.wasThrown)
|
||||||
throw new Error('Evaluation failed: ' + response.result.description);
|
throw new Error('Evaluation failed: ' + response.result.description);
|
||||||
if (!returnByValue)
|
if (!returnByValue)
|
||||||
return createJSHandle(context, response.result);
|
return toHandle(context, response.result);
|
||||||
if (response.result.objectId) {
|
if (response.result.objectId) {
|
||||||
const serializeFunction = function() {
|
const serializeFunction = function() {
|
||||||
try {
|
try {
|
||||||
@ -184,7 +184,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
|||||||
if (response.wasThrown)
|
if (response.wasThrown)
|
||||||
throw new Error('Evaluation failed: ' + response.result.description);
|
throw new Error('Evaluation failed: ' + response.result.description);
|
||||||
if (!returnByValue)
|
if (!returnByValue)
|
||||||
return createJSHandle(context, response.result);
|
return toHandle(context, response.result);
|
||||||
if (response.result.objectId) {
|
if (response.result.objectId) {
|
||||||
const serializeFunction = function() {
|
const serializeFunction = function() {
|
||||||
try {
|
try {
|
||||||
@ -281,7 +281,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate {
|
|||||||
for (const property of response.properties) {
|
for (const property of response.properties) {
|
||||||
if (!property.enumerable)
|
if (!property.enumerable)
|
||||||
continue;
|
continue;
|
||||||
result.set(property.name, createJSHandle(handle.executionContext(), property.value));
|
result.set(property.name, toHandle(handle.executionContext(), property.value));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -338,6 +338,13 @@ export function toRemoteObject(handle: js.JSHandle): Protocol.Runtime.RemoteObje
|
|||||||
return (handle as any)[remoteObjectSymbol];
|
return (handle as any)[remoteObjectSymbol];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markJSHandle(handle: js.JSHandle, remoteObject: Protocol.Runtime.RemoteObject) {
|
export function toHandle(context: js.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject) {
|
||||||
|
if (remoteObject.subtype === 'node' && context.frame()) {
|
||||||
|
const handle = new dom.ElementHandle(context);
|
||||||
|
(handle as any)[remoteObjectSymbol] = remoteObject;
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
const handle = new js.JSHandle(context);
|
||||||
(handle as any)[remoteObjectSymbol] = remoteObject;
|
(handle as any)[remoteObjectSymbol] = remoteObject;
|
||||||
|
return handle;
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import { ExecutionContextDelegate } from './ExecutionContext';
|
|||||||
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
||||||
import { Page } from './Page';
|
import { Page } from './Page';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
import { DOMWorldDelegate } from './JSHandle';
|
||||||
|
|
||||||
export const FrameManagerEvents = {
|
export const FrameManagerEvents = {
|
||||||
FrameNavigatedWithinDocument: Symbol('FrameNavigatedWithinDocument'),
|
FrameNavigatedWithinDocument: Symbol('FrameNavigatedWithinDocument'),
|
||||||
@ -235,8 +236,9 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
const frame = this._frames.get(frameId) || null;
|
const frame = this._frames.get(frameId) || null;
|
||||||
if (!frame)
|
if (!frame)
|
||||||
return;
|
return;
|
||||||
const context: js.ExecutionContext = new js.ExecutionContext(new ExecutionContextDelegate(this._session, contextPayload), frame);
|
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, contextPayload));
|
||||||
if (frame) {
|
if (frame) {
|
||||||
|
context._domWorld = new dom.DOMWorld(context, new DOMWorldDelegate(this, frame));
|
||||||
frame._contextCreated('main', context);
|
frame._contextCreated('main', context);
|
||||||
frame._contextCreated('utility', context);
|
frame._contextCreated('utility', context);
|
||||||
}
|
}
|
||||||
@ -272,11 +274,6 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate {
|
|||||||
return watchDog.waitForNavigation();
|
return watchDog.waitForNavigation();
|
||||||
}
|
}
|
||||||
|
|
||||||
async adoptElementHandle(elementHandle: dom.ElementHandle, context: js.ExecutionContext): Promise<dom.ElementHandle> {
|
|
||||||
assert(false, 'Multiple isolated worlds are not implemented');
|
|
||||||
return elementHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setFrameContent(frame: frames.Frame, html: string, options: { timeout?: number; waitUntil?: string | Array<string>; } | undefined = {}) {
|
async setFrameContent(frame: frames.Frame, html: string, options: { timeout?: number; waitUntil?: string | Array<string>; } | undefined = {}) {
|
||||||
// We rely upon the fact that document.open() will trigger Page.loadEventFired.
|
// We rely upon the fact that document.open() will trigger Page.loadEventFired.
|
||||||
const watchDog = new NextNavigationWatchdog(this, frame, 1000);
|
const watchDog = new NextNavigationWatchdog(this, frame, 1000);
|
||||||
|
@ -16,39 +16,29 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { debugError, helper } from '../helper';
|
import { debugError, helper, assert } from '../helper';
|
||||||
import * as input from '../input';
|
import * as input from '../input';
|
||||||
import * as dom from '../dom';
|
import * as dom from '../dom';
|
||||||
import * as frames from '../frames';
|
import * as frames from '../frames';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { TargetSession } from './Connection';
|
import { TargetSession } from './Connection';
|
||||||
import { ExecutionContextDelegate, markJSHandle, toRemoteObject } from './ExecutionContext';
|
import { toRemoteObject } from './ExecutionContext';
|
||||||
import { FrameManager } from './FrameManager';
|
import { FrameManager } from './FrameManager';
|
||||||
import { Protocol } from './protocol';
|
|
||||||
import * as js from '../javascript';
|
|
||||||
|
|
||||||
const writeFileAsync = helper.promisify(fs.writeFile);
|
const writeFileAsync = helper.promisify(fs.writeFile);
|
||||||
|
|
||||||
export function createJSHandle(context: js.ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject) {
|
export class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
||||||
const frame = context.frame();
|
readonly keyboard: input.Keyboard;
|
||||||
if (remoteObject.subtype === 'node' && frame) {
|
readonly mouse: input.Mouse;
|
||||||
const frameManager = frame._delegate as FrameManager;
|
readonly frame: frames.Frame;
|
||||||
const delegate = new DOMWorldDelegate((context._delegate as ExecutionContextDelegate)._session, frameManager);
|
|
||||||
const handle = new dom.ElementHandle(context, frameManager.page().keyboard, frameManager.page().mouse, delegate);
|
|
||||||
markJSHandle(handle, remoteObject);
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
const handle = new js.JSHandle(context);
|
|
||||||
markJSHandle(handle, remoteObject);
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
|
||||||
private _client: TargetSession;
|
private _client: TargetSession;
|
||||||
private _frameManager: FrameManager;
|
private _frameManager: FrameManager;
|
||||||
|
|
||||||
constructor(client: TargetSession, frameManager: FrameManager) {
|
constructor(frameManager: FrameManager, frame: frames.Frame) {
|
||||||
this._client = client;
|
this.keyboard = frameManager.page().keyboard;
|
||||||
|
this.mouse = frameManager.page().mouse;
|
||||||
|
this.frame = frame;
|
||||||
|
this._client = frameManager._session;
|
||||||
this._frameManager = frameManager;
|
this._frameManager = frameManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,8 +114,8 @@ class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
|||||||
// Filter out quads that have too small area to click into.
|
// Filter out quads that have too small area to click into.
|
||||||
const {clientWidth, clientHeight} = viewport;
|
const {clientWidth, clientHeight} = viewport;
|
||||||
const quads = result.quads.map(fromProtocolQuad)
|
const quads = result.quads.map(fromProtocolQuad)
|
||||||
.map(quad => intersectQuadWithViewport(quad, clientWidth, clientHeight))
|
.map(quad => intersectQuadWithViewport(quad, clientWidth, clientHeight))
|
||||||
.filter(quad => computeQuadArea(quad) > 1);
|
.filter(quad => computeQuadArea(quad) > 1);
|
||||||
if (!quads.length)
|
if (!quads.length)
|
||||||
throw new Error('Node is either not visible or not an HTMLElement');
|
throw new Error('Node is either not visible or not an HTMLElement');
|
||||||
// Return the middle point of the first quad.
|
// Return the middle point of the first quad.
|
||||||
@ -146,4 +136,9 @@ class DOMWorldDelegate implements dom.DOMWorldDelegate {
|
|||||||
const objectId = toRemoteObject(handle).objectId;
|
const objectId = toRemoteObject(handle).objectId;
|
||||||
await this._client.send('DOM.setInputFiles', { objectId, files });
|
await this._client.send('DOM.setInputFiles', { objectId, files });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async adoptElementHandle(handle: dom.ElementHandle, to: dom.DOMWorld): Promise<dom.ElementHandle> {
|
||||||
|
assert(false, 'Multiple isolated worlds are not implemented');
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,7 @@ import { TargetSession, TargetSessionEvents } from './Connection';
|
|||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
||||||
import { RawKeyboardImpl, RawMouseImpl } from './Input';
|
import { RawKeyboardImpl, RawMouseImpl } from './Input';
|
||||||
import { createJSHandle } from './JSHandle';
|
import { toHandle, toRemoteObject } from './ExecutionContext';
|
||||||
import { toRemoteObject } from './ExecutionContext';
|
|
||||||
import { NetworkManagerEvents } from './NetworkManager';
|
import { NetworkManagerEvents } from './NetworkManager';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { valueFromRemoteObject } from './protocolHelper';
|
import { valueFromRemoteObject } from './protocolHelper';
|
||||||
@ -183,7 +182,7 @@ export class Page extends EventEmitter {
|
|||||||
} else {
|
} else {
|
||||||
context = mainFrameContext;
|
context = mainFrameContext;
|
||||||
}
|
}
|
||||||
return createJSHandle(context, p);
|
return toHandle(context, p);
|
||||||
});
|
});
|
||||||
const textTokens = [];
|
const textTokens = [];
|
||||||
for (const handle of handles) {
|
for (const handle of handles) {
|
||||||
@ -462,7 +461,7 @@ export class Page extends EventEmitter {
|
|||||||
if (!this._fileChooserInterceptors.size)
|
if (!this._fileChooserInterceptors.size)
|
||||||
return;
|
return;
|
||||||
const context = await this._frameManager.frame(event.frameId)._utilityContext();
|
const context = await this._frameManager.frame(event.frameId)._utilityContext();
|
||||||
const handle = createJSHandle(context, event.element) as dom.ElementHandle;
|
const handle = toHandle(context, event.element) as dom.ElementHandle;
|
||||||
const interceptors = Array.from(this._fileChooserInterceptors);
|
const interceptors = Array.from(this._fileChooserInterceptors);
|
||||||
this._fileChooserInterceptors.clear();
|
this._fileChooserInterceptors.clear();
|
||||||
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);
|
const multiple = await handle.evaluate((element: HTMLInputElement) => !!element.multiple);
|
||||||
|
@ -104,12 +104,14 @@ function checkSources(sources) {
|
|||||||
function parentClass(classNode) {
|
function parentClass(classNode) {
|
||||||
for (const herigateClause of classNode.heritageClauses || []) {
|
for (const herigateClause of classNode.heritageClauses || []) {
|
||||||
for (const heritageType of herigateClause.types) {
|
for (const heritageType of herigateClause.types) {
|
||||||
const parentClassName = heritageType.expression.escapedText;
|
let expression = heritageType.expression;
|
||||||
return parentClassName;
|
if (expression.kind === ts.SyntaxKind.PropertyAccessExpression)
|
||||||
|
expression = expression.name;
|
||||||
|
if (classNode.name.escapedText !== expression.escapedText)
|
||||||
|
return expression.escapedText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function serializeSymbol(symbol, circular = []) {
|
function serializeSymbol(symbol, circular = []) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user