chore: reuse Frame between browsers (#100)
This commit is contained in:
parent
49f8963bf1
commit
dfc5592910
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CDPSession } from './Connection';
|
import { CDPSession } from './Connection';
|
||||||
import { Frame } from './Frame';
|
import { Frame } from './FrameManager';
|
||||||
import { helper } from '../helper';
|
import { helper } from '../helper';
|
||||||
import { valueFromRemoteObject, getExceptionMessage } from './protocolHelper';
|
import { valueFromRemoteObject, getExceptionMessage } from './protocolHelper';
|
||||||
import { createJSHandle, ElementHandle, JSHandle } from './JSHandle';
|
import { createJSHandle, ElementHandle, JSHandle } from './JSHandle';
|
||||||
|
|
@ -29,7 +29,7 @@ import * as types from '../types';
|
||||||
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;
|
||||||
|
|
||||||
export class ExecutionContext implements types.EvaluationContext<JSHandle> {
|
export class ExecutionContext {
|
||||||
_client: CDPSession;
|
_client: CDPSession;
|
||||||
private _frame: Frame;
|
private _frame: Frame;
|
||||||
private _injectedPromise: Promise<JSHandle> | null = null;
|
private _injectedPromise: Promise<JSHandle> | null = null;
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,12 @@ import { assert, debugError } from '../helper';
|
||||||
import { TimeoutSettings } from '../TimeoutSettings';
|
import { TimeoutSettings } from '../TimeoutSettings';
|
||||||
import { CDPSession } from './Connection';
|
import { CDPSession } from './Connection';
|
||||||
import { EVALUATION_SCRIPT_URL, ExecutionContext } from './ExecutionContext';
|
import { EVALUATION_SCRIPT_URL, ExecutionContext } from './ExecutionContext';
|
||||||
import { Frame, NavigateOptions, FrameDelegate } from './Frame';
|
import * as frames from '../frames';
|
||||||
import { LifecycleWatcher } from './LifecycleWatcher';
|
import { LifecycleWatcher } from './LifecycleWatcher';
|
||||||
import { NetworkManager, Response } from './NetworkManager';
|
import { NetworkManager, Response } from './NetworkManager';
|
||||||
import { Page } from './Page';
|
import { Page } from './Page';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { ElementHandle } from './JSHandle';
|
import { ElementHandle, JSHandle } from './JSHandle';
|
||||||
|
|
||||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||||
|
|
||||||
|
|
@ -44,7 +44,9 @@ type FrameData = {
|
||||||
lifecycleEvents: Set<string>,
|
lifecycleEvents: Set<string>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FrameManager extends EventEmitter implements FrameDelegate {
|
export type Frame = frames.Frame<JSHandle, ElementHandle, ExecutionContext, Response>;
|
||||||
|
|
||||||
|
export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, ExecutionContext, Response> {
|
||||||
_client: CDPSession;
|
_client: CDPSession;
|
||||||
private _page: Page;
|
private _page: Page;
|
||||||
private _networkManager: NetworkManager;
|
private _networkManager: NetworkManager;
|
||||||
|
|
@ -153,7 +155,7 @@ export class FrameManager extends EventEmitter implements FrameDelegate {
|
||||||
return watcher.navigationResponse();
|
return watcher.navigationResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setFrameContent(frame: Frame, html: string, options: NavigateOptions = {}) {
|
async setFrameContent(frame: Frame, html: string, options: frames.NavigateOptions = {}) {
|
||||||
const {
|
const {
|
||||||
waitUntil = ['load'],
|
waitUntil = ['load'],
|
||||||
timeout = this._timeoutSettings.navigationTimeout(),
|
timeout = this._timeoutSettings.navigationTimeout(),
|
||||||
|
|
@ -242,7 +244,7 @@ export class FrameManager extends EventEmitter implements FrameDelegate {
|
||||||
return;
|
return;
|
||||||
assert(parentFrameId);
|
assert(parentFrameId);
|
||||||
const parentFrame = this._frames.get(parentFrameId);
|
const parentFrame = this._frames.get(parentFrameId);
|
||||||
const frame = new Frame(this, parentFrame);
|
const frame = new frames.Frame(this, parentFrame);
|
||||||
const data: FrameData = {
|
const data: FrameData = {
|
||||||
id: frameId,
|
id: frameId,
|
||||||
loaderId: '',
|
loaderId: '',
|
||||||
|
|
@ -273,7 +275,7 @@ export class FrameManager extends EventEmitter implements FrameDelegate {
|
||||||
data.id = framePayload.id;
|
data.id = framePayload.id;
|
||||||
} else {
|
} else {
|
||||||
// Initial main frame navigation.
|
// Initial main frame navigation.
|
||||||
frame = new Frame(this, null);
|
frame = new frames.Frame(this, null);
|
||||||
const data: FrameData = {
|
const data: FrameData = {
|
||||||
id: framePayload.id,
|
id: framePayload.id,
|
||||||
loaderId: '',
|
loaderId: '',
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import * as input from '../input';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { CDPSession } from './Connection';
|
import { CDPSession } from './Connection';
|
||||||
import { ExecutionContext } from './ExecutionContext';
|
import { ExecutionContext } from './ExecutionContext';
|
||||||
import { Frame } from './Frame';
|
import { Frame } from './FrameManager';
|
||||||
import { FrameManager } from './FrameManager';
|
import { FrameManager } from './FrameManager';
|
||||||
import { Page } from './Page';
|
import { Page } from './Page';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import { CDPSessionEvents } from './Connection';
|
import { CDPSessionEvents } from './Connection';
|
||||||
import { TimeoutError } from '../Errors';
|
import { TimeoutError } from '../Errors';
|
||||||
import { Frame } from './Frame';
|
import { Frame } from './FrameManager';
|
||||||
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
||||||
import { assert, helper, RegisteredListener } from '../helper';
|
import { assert, helper, RegisteredListener } from '../helper';
|
||||||
import { NetworkManagerEvents, Request, Response } from './NetworkManager';
|
import { NetworkManagerEvents, Request, Response } from './NetworkManager';
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { CDPSession } from './Connection';
|
import { CDPSession } from './Connection';
|
||||||
import { Frame } from './Frame';
|
import { Frame } from './FrameManager';
|
||||||
import { FrameManager } from './FrameManager';
|
import { FrameManager } from './FrameManager';
|
||||||
import { assert, debugError, helper } from '../helper';
|
import { assert, debugError, helper } from '../helper';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import { Overrides } from './features/overrides';
|
||||||
import { Interception } from './features/interception';
|
import { Interception } from './features/interception';
|
||||||
import { PDF } from './features/pdf';
|
import { PDF } from './features/pdf';
|
||||||
import { Workers } from './features/workers';
|
import { Workers } from './features/workers';
|
||||||
import { Frame } from './Frame';
|
import { Frame } from './FrameManager';
|
||||||
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
import { FrameManager, FrameManagerEvents } from './FrameManager';
|
||||||
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
||||||
import { createJSHandle, ElementHandle, JSHandle } from './JSHandle';
|
import { createJSHandle, ElementHandle, JSHandle } from './JSHandle';
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export { Interception } from './features/interception';
|
||||||
export { PDF } from './features/pdf';
|
export { PDF } from './features/pdf';
|
||||||
export { Permissions } from './features/permissions';
|
export { Permissions } from './features/permissions';
|
||||||
export { Worker, Workers } from './features/workers';
|
export { Worker, Workers } from './features/workers';
|
||||||
export { Frame } from './Frame';
|
export { Frame } from '../frames';
|
||||||
export { Keyboard, Mouse } from '../input';
|
export { Keyboard, Mouse } from '../input';
|
||||||
export { ElementHandle, JSHandle } from './JSHandle';
|
export { ElementHandle, JSHandle } from './JSHandle';
|
||||||
export { Request, Response } from './NetworkManager';
|
export { Request, Response } from './NetworkManager';
|
||||||
|
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright 2019 Google Inc. All rights reserved.
|
|
||||||
* Modifications copyright (c) Microsoft Corporation.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { JSHandle } from './JSHandle';
|
|
||||||
import { ExecutionContext } from './ExecutionContext';
|
|
||||||
import { WaitTaskParams, WaitTask } from '../waitTask';
|
|
||||||
|
|
||||||
export class DOMWorld {
|
|
||||||
_frame: any;
|
|
||||||
_timeoutSettings: any;
|
|
||||||
_contextPromise: any;
|
|
||||||
_contextResolveCallback: any;
|
|
||||||
private _context: ExecutionContext | null;
|
|
||||||
_waitTasks: Set<WaitTask<JSHandle>>;
|
|
||||||
_detached: boolean;
|
|
||||||
constructor(frame, timeoutSettings) {
|
|
||||||
this._frame = frame;
|
|
||||||
this._timeoutSettings = timeoutSettings;
|
|
||||||
|
|
||||||
this._contextPromise;
|
|
||||||
this._contextResolveCallback = null;
|
|
||||||
this._setContext(null);
|
|
||||||
|
|
||||||
this._waitTasks = new Set();
|
|
||||||
this._detached = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
frame() {
|
|
||||||
return this._frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
_setContext(context: ExecutionContext) {
|
|
||||||
this._context = context;
|
|
||||||
if (context) {
|
|
||||||
this._contextResolveCallback.call(null, context);
|
|
||||||
this._contextResolveCallback = null;
|
|
||||||
for (const waitTask of this._waitTasks)
|
|
||||||
waitTask.rerun(context);
|
|
||||||
} else {
|
|
||||||
this._contextPromise = new Promise(fulfill => {
|
|
||||||
this._contextResolveCallback = fulfill;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_detach() {
|
|
||||||
this._detached = true;
|
|
||||||
for (const waitTask of this._waitTasks)
|
|
||||||
waitTask.terminate(new Error('waitForFunction failed: frame got detached.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async executionContext(): Promise<ExecutionContext> {
|
|
||||||
if (this._detached)
|
|
||||||
throw new Error(`Execution Context is not available in detached frame "${this._frame.url()}" (are you trying to evaluate?)`);
|
|
||||||
return this._contextPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleWaitTask(params: WaitTaskParams): Promise<JSHandle> {
|
|
||||||
const task = new WaitTask(params, () => this._waitTasks.delete(task));
|
|
||||||
this._waitTasks.add(task);
|
|
||||||
if (this._context)
|
|
||||||
task.rerun(this._context);
|
|
||||||
return task.promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -23,7 +23,7 @@ import * as cssSelectorEngineSource from '../generated/cssSelectorEngineSource';
|
||||||
import * as xpathSelectorEngineSource from '../generated/xpathSelectorEngineSource';
|
import * as xpathSelectorEngineSource from '../generated/xpathSelectorEngineSource';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
|
|
||||||
export class ExecutionContext implements types.EvaluationContext<JSHandle> {
|
export class ExecutionContext {
|
||||||
_session: any;
|
_session: any;
|
||||||
_frame: Frame;
|
_frame: Frame;
|
||||||
_executionContextId: string;
|
_executionContextId: string;
|
||||||
|
|
|
||||||
|
|
@ -17,21 +17,14 @@
|
||||||
|
|
||||||
import { JugglerSession } from './Connection';
|
import { JugglerSession } from './Connection';
|
||||||
import { Page } from './Page';
|
import { Page } from './Page';
|
||||||
import * as fs from 'fs';
|
|
||||||
import {RegisteredListener, helper, assert} from '../helper';
|
import {RegisteredListener, helper, assert} from '../helper';
|
||||||
import {TimeoutError} from '../Errors';
|
import {TimeoutError} from '../Errors';
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
import {ExecutionContext} from './ExecutionContext';
|
import {ExecutionContext} from './ExecutionContext';
|
||||||
import {NavigationWatchdog, NextNavigationWatchdog} from './NavigationWatchdog';
|
import {NavigationWatchdog, NextNavigationWatchdog} from './NavigationWatchdog';
|
||||||
import {DOMWorld} from './DOMWorld';
|
|
||||||
import { JSHandle, ElementHandle } from './JSHandle';
|
import { JSHandle, ElementHandle } from './JSHandle';
|
||||||
import { TimeoutSettings } from '../TimeoutSettings';
|
import { TimeoutSettings } from '../TimeoutSettings';
|
||||||
import { NetworkManager } from './NetworkManager';
|
import * as frames from '../frames';
|
||||||
import { MultiClickOptions, ClickOptions, SelectOption } from '../input';
|
|
||||||
import * as types from '../types';
|
|
||||||
import { waitForSelectorOrXPath, WaitTaskParams } from '../waitTask';
|
|
||||||
|
|
||||||
const readFileAsync = helper.promisify(fs.readFile);
|
|
||||||
|
|
||||||
export const FrameManagerEvents = {
|
export const FrameManagerEvents = {
|
||||||
FrameNavigated: Symbol('FrameManagerEvents.FrameNavigated'),
|
FrameNavigated: Symbol('FrameManagerEvents.FrameNavigated'),
|
||||||
|
|
@ -40,7 +33,17 @@ export const FrameManagerEvents = {
|
||||||
Load: Symbol('FrameManagerEvents.Load'),
|
Load: Symbol('FrameManagerEvents.Load'),
|
||||||
DOMContentLoaded: Symbol('FrameManagerEvents.DOMContentLoaded'),
|
DOMContentLoaded: Symbol('FrameManagerEvents.DOMContentLoaded'),
|
||||||
};
|
};
|
||||||
export class FrameManager extends EventEmitter {
|
|
||||||
|
const frameDataSymbol = Symbol('frameData');
|
||||||
|
type FrameData = {
|
||||||
|
frameId: string,
|
||||||
|
lastCommittedNavigationId: string,
|
||||||
|
firedEvents: Set<string>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Frame = frames.Frame<JSHandle, ElementHandle, ExecutionContext, Response>;
|
||||||
|
|
||||||
|
export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, ExecutionContext, Response> {
|
||||||
_session: JugglerSession;
|
_session: JugglerSession;
|
||||||
_page: Page;
|
_page: Page;
|
||||||
_networkManager: any;
|
_networkManager: any;
|
||||||
|
|
@ -49,6 +52,7 @@ export class FrameManager extends EventEmitter {
|
||||||
_frames: Map<string, Frame>;
|
_frames: Map<string, Frame>;
|
||||||
_contextIdToContext: Map<string, ExecutionContext>;
|
_contextIdToContext: Map<string, ExecutionContext>;
|
||||||
_eventListeners: RegisteredListener[];
|
_eventListeners: RegisteredListener[];
|
||||||
|
|
||||||
constructor(session: JugglerSession, page: Page, networkManager, timeoutSettings) {
|
constructor(session: JugglerSession, page: Page, networkManager, timeoutSettings) {
|
||||||
super();
|
super();
|
||||||
this._session = session;
|
this._session = session;
|
||||||
|
|
@ -77,8 +81,10 @@ export class FrameManager extends EventEmitter {
|
||||||
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 ExecutionContext(this._session, frame, executionContextId);
|
const context = new ExecutionContext(this._session, frame, executionContextId);
|
||||||
if (frame)
|
if (frame) {
|
||||||
frame._mainWorld._setContext(context);
|
frame._contextCreated('main', context);
|
||||||
|
frame._contextCreated('utility', context);
|
||||||
|
}
|
||||||
this._contextIdToContext.set(executionContextId, context);
|
this._contextIdToContext.set(executionContextId, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,11 +93,15 @@ export class FrameManager extends EventEmitter {
|
||||||
if (!context)
|
if (!context)
|
||||||
return;
|
return;
|
||||||
this._contextIdToContext.delete(executionContextId);
|
this._contextIdToContext.delete(executionContextId);
|
||||||
if (context._frame)
|
if (context.frame())
|
||||||
context._frame._mainWorld._setContext(null);
|
context.frame()._contextDestroyed(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
frame(frameId) {
|
_frameData(frame: Frame): FrameData {
|
||||||
|
return (frame as any)[frameDataSymbol];
|
||||||
|
}
|
||||||
|
|
||||||
|
frame(frameId: string): Frame {
|
||||||
return this._frames.get(frameId);
|
return this._frames.get(frameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,32 +114,38 @@ export class FrameManager extends EventEmitter {
|
||||||
collect(this._mainFrame);
|
collect(this._mainFrame);
|
||||||
return frames;
|
return frames;
|
||||||
|
|
||||||
function collect(frame) {
|
function collect(frame: Frame) {
|
||||||
frames.push(frame);
|
frames.push(frame);
|
||||||
for (const subframe of frame._children)
|
for (const subframe of frame.childFrames())
|
||||||
collect(subframe);
|
collect(subframe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onNavigationCommitted(params) {
|
_onNavigationCommitted(params) {
|
||||||
const frame = this._frames.get(params.frameId);
|
const frame = this._frames.get(params.frameId);
|
||||||
frame._navigated(params.url, params.name, params.navigationId);
|
frame._navigated(params.url, params.name);
|
||||||
|
const data = this._frameData(frame);
|
||||||
|
data.lastCommittedNavigationId = params.navigationId;
|
||||||
|
data.firedEvents.clear();
|
||||||
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSameDocumentNavigation(params) {
|
_onSameDocumentNavigation(params) {
|
||||||
const frame = this._frames.get(params.frameId);
|
const frame = this._frames.get(params.frameId);
|
||||||
frame._url = params.url;
|
frame._navigated(params.url, frame.name());
|
||||||
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFrameAttached(params) {
|
_onFrameAttached(params) {
|
||||||
const frame = new Frame(this._session, this, this._networkManager, this._page, params.frameId, this._timeoutSettings);
|
|
||||||
const parentFrame = this._frames.get(params.parentFrameId) || null;
|
const parentFrame = this._frames.get(params.parentFrameId) || null;
|
||||||
if (parentFrame) {
|
const frame = new frames.Frame(this, parentFrame);
|
||||||
frame._parentFrame = parentFrame;
|
const data: FrameData = {
|
||||||
parentFrame._children.add(frame);
|
frameId: params.frameId,
|
||||||
} else {
|
lastCommittedNavigationId: '',
|
||||||
|
firedEvents: new Set(),
|
||||||
|
};
|
||||||
|
frame[frameDataSymbol] = data;
|
||||||
|
if (!parentFrame) {
|
||||||
assert(!this._mainFrame, 'INTERNAL ERROR: re-attaching main frame!');
|
assert(!this._mainFrame, 'INTERNAL ERROR: re-attaching main frame!');
|
||||||
this._mainFrame = frame;
|
this._mainFrame = frame;
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +162,7 @@ export class FrameManager extends EventEmitter {
|
||||||
|
|
||||||
_onEventFired({frameId, name}) {
|
_onEventFired({frameId, name}) {
|
||||||
const frame = this._frames.get(frameId);
|
const frame = this._frames.get(frameId);
|
||||||
frame._firedEvents.add(name.toLowerCase());
|
this._frameData(frame).firedEvents.add(name.toLowerCase());
|
||||||
if (frame === this._mainFrame) {
|
if (frame === this._mainFrame) {
|
||||||
if (name === 'load')
|
if (name === 'load')
|
||||||
this.emit(FrameManagerEvents.Load);
|
this.emit(FrameManagerEvents.Load);
|
||||||
|
|
@ -158,45 +174,17 @@ export class FrameManager extends EventEmitter {
|
||||||
dispose() {
|
dispose() {
|
||||||
helper.removeEventListeners(this._eventListeners);
|
helper.removeEventListeners(this._eventListeners);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export class Frame {
|
timeoutSettings(): TimeoutSettings {
|
||||||
_parentFrame: Frame|null = null;
|
return this._timeoutSettings;
|
||||||
private _session: JugglerSession;
|
|
||||||
_page: Page;
|
|
||||||
_frameManager: FrameManager;
|
|
||||||
private _networkManager: NetworkManager;
|
|
||||||
private _timeoutSettings: TimeoutSettings;
|
|
||||||
_frameId: string;
|
|
||||||
_url: string = '';
|
|
||||||
private _name: string = '';
|
|
||||||
_children: Set<Frame>;
|
|
||||||
private _detached: boolean;
|
|
||||||
_firedEvents: Set<string>;
|
|
||||||
_mainWorld: DOMWorld;
|
|
||||||
_lastCommittedNavigationId: string;
|
|
||||||
|
|
||||||
constructor(session: JugglerSession, frameManager : FrameManager, networkManager, page: Page, frameId: string, timeoutSettings) {
|
|
||||||
this._session = session;
|
|
||||||
this._page = page;
|
|
||||||
this._frameManager = frameManager;
|
|
||||||
this._networkManager = networkManager;
|
|
||||||
this._timeoutSettings = timeoutSettings;
|
|
||||||
this._frameId = frameId;
|
|
||||||
this._children = new Set();
|
|
||||||
this._detached = false;
|
|
||||||
|
|
||||||
|
|
||||||
this._firedEvents = new Set();
|
|
||||||
|
|
||||||
this._mainWorld = new DOMWorld(this, timeoutSettings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async executionContext() {
|
async adoptElementHandle(elementHandle: ElementHandle, context: ExecutionContext): Promise<ElementHandle> {
|
||||||
return this._mainWorld.executionContext();
|
assert(false, 'Multiple isolated worlds are not implemented');
|
||||||
|
return elementHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForNavigation(options: { timeout?: number; waitUntil?: string | Array<string>; } = {}) {
|
async waitForFrameNavigation(frame: Frame, options: { timeout?: number; waitUntil?: string | Array<string>; } = {}) {
|
||||||
const {
|
const {
|
||||||
timeout = this._timeoutSettings.navigationTimeout(),
|
timeout = this._timeoutSettings.navigationTimeout(),
|
||||||
waitUntil = ['load'],
|
waitUntil = ['load'],
|
||||||
|
|
@ -208,7 +196,7 @@ export class Frame {
|
||||||
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
||||||
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
||||||
|
|
||||||
const nextNavigationDog = new NextNavigationWatchdog(this._session, this);
|
const nextNavigationDog = new NextNavigationWatchdog(this, frame);
|
||||||
const error1 = await Promise.race([
|
const error1 = await Promise.race([
|
||||||
nextNavigationDog.promise(),
|
nextNavigationDog.promise(),
|
||||||
timeoutPromise,
|
timeoutPromise,
|
||||||
|
|
@ -229,7 +217,7 @@ export class Frame {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const watchDog = new NavigationWatchdog(this._session, this, this._networkManager, navigationId, url, normalizedWaitUntil);
|
const watchDog = new NavigationWatchdog(this, frame, this._networkManager, navigationId, url, normalizedWaitUntil);
|
||||||
const error = await Promise.race([
|
const error = await Promise.race([
|
||||||
timeoutPromise,
|
timeoutPromise,
|
||||||
watchDog.promise(),
|
watchDog.promise(),
|
||||||
|
|
@ -241,7 +229,7 @@ export class Frame {
|
||||||
return watchDog.navigationResponse();
|
return watchDog.navigationResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
async goto(url: string, options: { timeout?: number; waitUntil?: string | Array<string>; referer?: string; } = {}) {
|
async navigateFrame(frame: Frame, url: string, options: { timeout?: number; waitUntil?: string | Array<string>; referer?: string; } = {}) {
|
||||||
const {
|
const {
|
||||||
timeout = this._timeoutSettings.navigationTimeout(),
|
timeout = this._timeoutSettings.navigationTimeout(),
|
||||||
waitUntil = ['load'],
|
waitUntil = ['load'],
|
||||||
|
|
@ -249,7 +237,7 @@ export class Frame {
|
||||||
} = options;
|
} = options;
|
||||||
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
||||||
const {navigationId} = await this._session.send('Page.navigate', {
|
const {navigationId} = await this._session.send('Page.navigate', {
|
||||||
frameId: this._frameId,
|
frameId: this._frameData(frame).frameId,
|
||||||
referer,
|
referer,
|
||||||
url,
|
url,
|
||||||
});
|
});
|
||||||
|
|
@ -261,7 +249,7 @@ export class Frame {
|
||||||
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
||||||
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
||||||
|
|
||||||
const watchDog = new NavigationWatchdog(this._session, this, this._networkManager, navigationId, url, normalizedWaitUntil);
|
const watchDog = new NavigationWatchdog(this, frame, this._networkManager, navigationId, url, normalizedWaitUntil);
|
||||||
const error = await Promise.race([
|
const error = await Promise.race([
|
||||||
timeoutPromise,
|
timeoutPromise,
|
||||||
watchDog.promise(),
|
watchDog.promise(),
|
||||||
|
|
@ -273,355 +261,14 @@ export class Frame {
|
||||||
return watchDog.navigationResponse();
|
return watchDog.navigationResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
async click(selector: string, options?: ClickOptions) {
|
async setFrameContent(frame: Frame, html: string) {
|
||||||
const context = await this._mainWorld.executionContext();
|
const context = await frame._utilityContext();
|
||||||
const document = await context._document();
|
|
||||||
const handle = await document.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.click(options);
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async dblclick(selector: string, options?: MultiClickOptions) {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
const handle = await document.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.dblclick(options);
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async tripleclick(selector: string, options?: MultiClickOptions) {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
const handle = await document.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.tripleclick(options);
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async select(selector: string, ...values: (string | ElementHandle | SelectOption)[]): Promise<string[]> {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
const handle = await document.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
const result = await handle.select(...values);
|
|
||||||
await handle.dispose();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fill(selector: string, value: string) {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
const handle = await document.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.fill(value);
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
const handle = await document.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.type(text, options);
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async focus(selector: string) {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
const handle = await document.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.focus();
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async hover(selector: string) {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
const handle = await document.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.hover();
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_detach() {
|
|
||||||
this._parentFrame._children.delete(this);
|
|
||||||
this._parentFrame = null;
|
|
||||||
this._detached = true;
|
|
||||||
this._mainWorld._detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
_navigated(url, name, navigationId) {
|
|
||||||
this._url = url;
|
|
||||||
this._name = name;
|
|
||||||
this._lastCommittedNavigationId = navigationId;
|
|
||||||
this._firedEvents.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: { polling?: string | number; timeout?: number; visible?: boolean; hidden?: boolean; } | undefined, ...args: Array<any>): Promise<JSHandle> {
|
|
||||||
const xPathPattern = '//';
|
|
||||||
|
|
||||||
if (helper.isString(selectorOrFunctionOrTimeout)) {
|
|
||||||
const string = selectorOrFunctionOrTimeout;
|
|
||||||
if (string.startsWith(xPathPattern))
|
|
||||||
return this.waitForXPath(string, options);
|
|
||||||
return this.waitForSelector(string, options);
|
|
||||||
}
|
|
||||||
if (helper.isNumber(selectorOrFunctionOrTimeout))
|
|
||||||
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout));
|
|
||||||
if (typeof selectorOrFunctionOrTimeout === 'function')
|
|
||||||
return this.waitForFunction(selectorOrFunctionOrTimeout, options, ...args);
|
|
||||||
return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForFunction(
|
|
||||||
pageFunction: Function | string,
|
|
||||||
options: { polling?: string | number; timeout?: number; } = {},
|
|
||||||
...args): Promise<JSHandle> {
|
|
||||||
const {
|
|
||||||
polling = 'raf',
|
|
||||||
timeout = this._frameManager._timeoutSettings.timeout(),
|
|
||||||
} = options;
|
|
||||||
const params: WaitTaskParams = {
|
|
||||||
predicateBody: pageFunction,
|
|
||||||
title: 'function',
|
|
||||||
polling,
|
|
||||||
timeout,
|
|
||||||
args
|
|
||||||
};
|
|
||||||
return this._mainWorld.scheduleWaitTask(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForSelector(selector: string, options: {
|
|
||||||
visible?: boolean;
|
|
||||||
hidden?: boolean;
|
|
||||||
timeout?: number; } | undefined): Promise<ElementHandle | null> {
|
|
||||||
const params = waitForSelectorOrXPath(selector, false /* isXPath */, { timeout: this._frameManager._timeoutSettings.timeout(), ...options });
|
|
||||||
const handle = await this._mainWorld.scheduleWaitTask(params);
|
|
||||||
if (!handle.asElement()) {
|
|
||||||
await handle.dispose();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return handle.asElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForXPath(xpath: string, options: {
|
|
||||||
visible?: boolean;
|
|
||||||
hidden?: boolean;
|
|
||||||
timeout?: number; } | undefined): Promise<ElementHandle | null> {
|
|
||||||
const params = waitForSelectorOrXPath(xpath, true /* isXPath */, { timeout: this._frameManager._timeoutSettings.timeout(), ...options });
|
|
||||||
const handle = await this._mainWorld.scheduleWaitTask(params);
|
|
||||||
if (!handle.asElement()) {
|
|
||||||
await handle.dispose();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return handle.asElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
async content(): Promise<string> {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
return context.evaluate(() => {
|
|
||||||
let retVal = '';
|
|
||||||
if (document.doctype)
|
|
||||||
retVal = new XMLSerializer().serializeToString(document.doctype);
|
|
||||||
if (document.documentElement)
|
|
||||||
retVal += document.documentElement.outerHTML;
|
|
||||||
return retVal;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async setContent(html: string) {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
await context.evaluate(html => {
|
await context.evaluate(html => {
|
||||||
document.open();
|
document.open();
|
||||||
document.write(html);
|
document.write(html);
|
||||||
document.close();
|
document.close();
|
||||||
}, html);
|
}, html);
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluate: types.Evaluate<JSHandle> = async (pageFunction, ...args) => {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
return context.evaluate(pageFunction, ...args as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
async $(selector: string): Promise<ElementHandle | null> {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
return document.$(selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
async $$(selector: string): Promise<Array<ElementHandle>> {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
return document.$$(selector);
|
|
||||||
}
|
|
||||||
|
|
||||||
$eval: types.$Eval<JSHandle> = async (selector, pageFunction, ...args) => {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
return document.$eval(selector, pageFunction, ...args as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
$$eval: types.$$Eval<JSHandle> = async (selector, pageFunction, ...args) => {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
return document.$$eval(selector, pageFunction, ...args as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
async $x(expression: string): Promise<Array<ElementHandle>> {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
const document = await context._document();
|
|
||||||
return document.$x(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluateHandle: types.EvaluateHandle<JSHandle> = async (pageFunction, ...args) => {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
return context.evaluateHandle(pageFunction, ...args as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
async addScriptTag(options: {
|
|
||||||
url?: string; path?: string;
|
|
||||||
content?: string;
|
|
||||||
type?: string;
|
|
||||||
}): Promise<ElementHandle> {
|
|
||||||
const {
|
|
||||||
url = null,
|
|
||||||
path = null,
|
|
||||||
content = null,
|
|
||||||
type = ''
|
|
||||||
} = options;
|
|
||||||
if (url !== null) {
|
|
||||||
try {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
return (await context.evaluateHandle(addScriptUrl, url, type)).asElement();
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Loading script from ${url} failed`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path !== null) {
|
|
||||||
let contents = await readFileAsync(path, 'utf8');
|
|
||||||
contents += '//# sourceURL=' + path.replace(/\n/g, '');
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
return (await context.evaluateHandle(addScriptContent, contents, type)).asElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content !== null) {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
return (await context.evaluateHandle(addScriptContent, content, type)).asElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Provide an object with a `url`, `path` or `content` property');
|
|
||||||
|
|
||||||
async function addScriptUrl(url: string, type: string): Promise<HTMLElement> {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = url;
|
|
||||||
if (type)
|
|
||||||
script.type = type;
|
|
||||||
const promise = new Promise((res, rej) => {
|
|
||||||
script.onload = res;
|
|
||||||
script.onerror = rej;
|
|
||||||
});
|
|
||||||
document.head.appendChild(script);
|
|
||||||
await promise;
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addScriptContent(content: string, type: string = 'text/javascript'): HTMLElement {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.type = type;
|
|
||||||
script.text = content;
|
|
||||||
let error = null;
|
|
||||||
script.onerror = e => error = e;
|
|
||||||
document.head.appendChild(script);
|
|
||||||
if (error)
|
|
||||||
throw error;
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise<ElementHandle> {
|
|
||||||
const {
|
|
||||||
url = null,
|
|
||||||
path = null,
|
|
||||||
content = null
|
|
||||||
} = options;
|
|
||||||
if (url !== null) {
|
|
||||||
try {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
return (await context.evaluateHandle(addStyleUrl, url)).asElement();
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Loading style from ${url} failed`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path !== null) {
|
|
||||||
let contents = await readFileAsync(path, 'utf8');
|
|
||||||
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
return (await context.evaluateHandle(addStyleContent, contents)).asElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content !== null) {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
return (await context.evaluateHandle(addStyleContent, content)).asElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Provide an object with a `url`, `path` or `content` property');
|
|
||||||
|
|
||||||
async function addStyleUrl(url: string): Promise<HTMLElement> {
|
|
||||||
const link = document.createElement('link');
|
|
||||||
link.rel = 'stylesheet';
|
|
||||||
link.href = url;
|
|
||||||
const promise = new Promise((res, rej) => {
|
|
||||||
link.onload = res;
|
|
||||||
link.onerror = rej;
|
|
||||||
});
|
|
||||||
document.head.appendChild(link);
|
|
||||||
await promise;
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addStyleContent(content: string): Promise<HTMLElement> {
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.type = 'text/css';
|
|
||||||
style.appendChild(document.createTextNode(content));
|
|
||||||
const promise = new Promise((res, rej) => {
|
|
||||||
style.onload = res;
|
|
||||||
style.onerror = rej;
|
|
||||||
});
|
|
||||||
document.head.appendChild(style);
|
|
||||||
await promise;
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async title(): Promise<string> {
|
|
||||||
const context = await this._mainWorld.executionContext();
|
|
||||||
return context.evaluate(() => document.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
name() {
|
|
||||||
return this._name;
|
|
||||||
}
|
|
||||||
|
|
||||||
isDetached() {
|
|
||||||
return this._detached;
|
|
||||||
}
|
|
||||||
|
|
||||||
childFrames() {
|
|
||||||
return Array.from(this._children);
|
|
||||||
}
|
|
||||||
|
|
||||||
url() {
|
|
||||||
return this._url;
|
|
||||||
}
|
|
||||||
|
|
||||||
parentFrame() {
|
|
||||||
return this._parentFrame;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeWaitUntil(waitUntil) {
|
export function normalizeWaitUntil(waitUntil) {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ import * as input from '../input';
|
||||||
import * as types from '../types';
|
import * as types from '../types';
|
||||||
import { JugglerSession } from './Connection';
|
import { JugglerSession } from './Connection';
|
||||||
import { ExecutionContext } from './ExecutionContext';
|
import { ExecutionContext } from './ExecutionContext';
|
||||||
import { Frame } from './FrameManager';
|
import { Frame, FrameManager } from './FrameManager';
|
||||||
|
import { Page } from './Page';
|
||||||
|
|
||||||
type SelectorRoot = Element | ShadowRoot | Document;
|
type SelectorRoot = Element | ShadowRoot | Document;
|
||||||
|
|
||||||
|
|
@ -137,11 +138,14 @@ export class JSHandle {
|
||||||
|
|
||||||
export class ElementHandle extends JSHandle {
|
export class ElementHandle extends JSHandle {
|
||||||
_frame: Frame;
|
_frame: Frame;
|
||||||
_frameId: any;
|
_frameId: string;
|
||||||
constructor(frame: Frame, context: ExecutionContext, payload: any) {
|
_page: Page;
|
||||||
|
|
||||||
|
constructor(frame: Frame, frameId: string, page: Page, context: ExecutionContext, payload: any) {
|
||||||
super(context, payload);
|
super(context, payload);
|
||||||
this._frame = frame;
|
this._frame = frame;
|
||||||
this._frameId = frame._frameId;
|
this._frameId = frameId;
|
||||||
|
this._page = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
async contentFrame(): Promise<Frame | null> {
|
async contentFrame(): Promise<Frame | null> {
|
||||||
|
|
@ -151,7 +155,7 @@ export class ElementHandle extends JSHandle {
|
||||||
});
|
});
|
||||||
if (!frameId)
|
if (!frameId)
|
||||||
return null;
|
return null;
|
||||||
const frame = this._frame._frameManager.frame(frameId);
|
const frame = this._page._frameManager.frame(frameId);
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,7 +181,7 @@ export class ElementHandle extends JSHandle {
|
||||||
assert(clip.height, 'Node has 0 height.');
|
assert(clip.height, 'Node has 0 height.');
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
|
|
||||||
return await this._frame._page.screenshot(Object.assign({}, options, {
|
return await this._page.screenshot(Object.assign({}, options, {
|
||||||
clip: {
|
clip: {
|
||||||
x: clip.x,
|
x: clip.x,
|
||||||
y: clip.y,
|
y: clip.y,
|
||||||
|
|
@ -294,19 +298,19 @@ export class ElementHandle extends JSHandle {
|
||||||
async click(options?: input.ClickOptions) {
|
async click(options?: input.ClickOptions) {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
const {x, y} = await this._clickablePoint();
|
const {x, y} = await this._clickablePoint();
|
||||||
await this._frame._page.mouse.click(x, y, options);
|
await this._page.mouse.click(x, y, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async dblclick(options?: input.MultiClickOptions): Promise<void> {
|
async dblclick(options?: input.MultiClickOptions): Promise<void> {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
const {x, y} = await this._clickablePoint();
|
const {x, y} = await this._clickablePoint();
|
||||||
await this._frame._page.mouse.dblclick(x, y, options);
|
await this._page.mouse.dblclick(x, y, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async tripleclick(options?: input.MultiClickOptions): Promise<void> {
|
async tripleclick(options?: input.MultiClickOptions): Promise<void> {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
const {x, y} = await this._clickablePoint();
|
const {x, y} = await this._clickablePoint();
|
||||||
await this._frame._page.mouse.tripleclick(x, y, options);
|
await this._page.mouse.tripleclick(x, y, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setInputFiles(...files: (string|input.FilePayload)[]) {
|
async setInputFiles(...files: (string|input.FilePayload)[]) {
|
||||||
|
|
@ -318,7 +322,7 @@ export class ElementHandle extends JSHandle {
|
||||||
async hover() {
|
async hover() {
|
||||||
await this._scrollIntoViewIfNeeded();
|
await this._scrollIntoViewIfNeeded();
|
||||||
const {x, y} = await this._clickablePoint();
|
const {x, y} = await this._clickablePoint();
|
||||||
await this._frame._page.mouse.move(x, y);
|
await this._page.mouse.move(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
async focus() {
|
async focus() {
|
||||||
|
|
@ -327,12 +331,12 @@ export class ElementHandle extends 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._frame._page.keyboard.type(text, options);
|
await this._page.keyboard.type(text, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async press(key: string, options: { delay?: number; } | undefined) {
|
async press(key: string, options: { delay?: number; } | undefined) {
|
||||||
await this.focus();
|
await this.focus();
|
||||||
await this._frame._page.keyboard.press(key, options);
|
await this._page.keyboard.press(key, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async select(...values: (string | ElementHandle | input.SelectOption)[]): Promise<string[]> {
|
async select(...values: (string | ElementHandle | input.SelectOption)[]): Promise<string[]> {
|
||||||
|
|
@ -356,7 +360,7 @@ export class ElementHandle extends JSHandle {
|
||||||
if (error)
|
if (error)
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
await this.focus();
|
await this.focus();
|
||||||
await this._frame._page.keyboard.sendCharacters(value);
|
await this._page.keyboard.sendCharacters(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _clickablePoint(): Promise<{ x: number; y: number; }> {
|
async _clickablePoint(): Promise<{ x: number; y: number; }> {
|
||||||
|
|
@ -376,14 +380,20 @@ export class ElementHandle extends JSHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createHandle(context: ExecutionContext, result: any, exceptionDetails?: any) {
|
export function createHandle(context: ExecutionContext, result: any, exceptionDetails?: any) {
|
||||||
const frame = context.frame();
|
|
||||||
if (exceptionDetails) {
|
if (exceptionDetails) {
|
||||||
if (exceptionDetails.value)
|
if (exceptionDetails.value)
|
||||||
throw new Error('Evaluation failed: ' + JSON.stringify(exceptionDetails.value));
|
throw new Error('Evaluation failed: ' + JSON.stringify(exceptionDetails.value));
|
||||||
else
|
else
|
||||||
throw new Error('Evaluation failed: ' + exceptionDetails.text + '\n' + exceptionDetails.stack);
|
throw new Error('Evaluation failed: ' + exceptionDetails.text + '\n' + exceptionDetails.stack);
|
||||||
}
|
}
|
||||||
return result.subtype === 'node' ? new ElementHandle(frame, context, result) : new JSHandle(context, result);
|
if (result.subtype === 'node') {
|
||||||
|
const frame = context.frame();
|
||||||
|
const frameManager = frame._delegate as FrameManager;
|
||||||
|
const frameId = frameManager._frameData(frame).frameId;
|
||||||
|
const page = frameManager._page;
|
||||||
|
return new ElementHandle(frame, frameId, page, context, result);
|
||||||
|
}
|
||||||
|
return new JSHandle(context, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeQuadArea(quad) {
|
function computeQuadArea(quad) {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,23 @@
|
||||||
import { helper, RegisteredListener } from '../helper';
|
import { helper, RegisteredListener } from '../helper';
|
||||||
import { JugglerSession, JugglerSessionEvents } from './Connection';
|
import { JugglerSessionEvents } from './Connection';
|
||||||
import { Frame, FrameManagerEvents } from './FrameManager';
|
import { Frame, FrameManagerEvents, FrameManager } from './FrameManager';
|
||||||
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
||||||
|
|
||||||
export class NextNavigationWatchdog {
|
export class NextNavigationWatchdog {
|
||||||
|
private _frameManager: FrameManager;
|
||||||
private _navigatedFrame: Frame;
|
private _navigatedFrame: Frame;
|
||||||
private _promise: Promise<unknown>;
|
private _promise: Promise<unknown>;
|
||||||
private _resolveCallback: (value?: unknown) => void;
|
private _resolveCallback: (value?: unknown) => void;
|
||||||
private _navigation: {navigationId: number|null, url?: string} = null;
|
private _navigation: {navigationId: number|null, url?: string} = null;
|
||||||
private _eventListeners: RegisteredListener[];
|
private _eventListeners: RegisteredListener[];
|
||||||
constructor(session : JugglerSession, navigatedFrame : Frame) {
|
|
||||||
|
constructor(frameManager: FrameManager, navigatedFrame: Frame) {
|
||||||
|
this._frameManager = frameManager;
|
||||||
this._navigatedFrame = navigatedFrame;
|
this._navigatedFrame = navigatedFrame;
|
||||||
this._promise = new Promise(x => this._resolveCallback = x);
|
this._promise = new Promise(x => this._resolveCallback = x);
|
||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(session, 'Page.navigationStarted', this._onNavigationStarted.bind(this)),
|
helper.addEventListener(frameManager._session, 'Page.navigationStarted', this._onNavigationStarted.bind(this)),
|
||||||
helper.addEventListener(session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)),
|
helper.addEventListener(frameManager._session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,7 +30,7 @@ export class NextNavigationWatchdog {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onNavigationStarted(params) {
|
_onNavigationStarted(params) {
|
||||||
if (params.frameId === this._navigatedFrame._frameId) {
|
if (params.frameId === this._frameManager._frameData(this._navigatedFrame).frameId) {
|
||||||
this._navigation = {
|
this._navigation = {
|
||||||
navigationId: params.navigationId,
|
navigationId: params.navigationId,
|
||||||
url: params.url,
|
url: params.url,
|
||||||
|
|
@ -37,7 +40,7 @@ export class NextNavigationWatchdog {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSameDocumentNavigation(params) {
|
_onSameDocumentNavigation(params) {
|
||||||
if (params.frameId === this._navigatedFrame._frameId) {
|
if (params.frameId === this._frameManager._frameData(this._navigatedFrame).frameId) {
|
||||||
this._navigation = {
|
this._navigation = {
|
||||||
navigationId: null,
|
navigationId: null,
|
||||||
};
|
};
|
||||||
|
|
@ -51,6 +54,7 @@ export class NextNavigationWatchdog {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NavigationWatchdog {
|
export class NavigationWatchdog {
|
||||||
|
private _frameManager: FrameManager;
|
||||||
private _navigatedFrame: Frame;
|
private _navigatedFrame: Frame;
|
||||||
private _targetNavigationId: any;
|
private _targetNavigationId: any;
|
||||||
private _firedEvents: any;
|
private _firedEvents: any;
|
||||||
|
|
@ -59,7 +63,9 @@ export class NavigationWatchdog {
|
||||||
private _resolveCallback: (value?: unknown) => void;
|
private _resolveCallback: (value?: unknown) => void;
|
||||||
private _navigationRequest: any;
|
private _navigationRequest: any;
|
||||||
private _eventListeners: RegisteredListener[];
|
private _eventListeners: RegisteredListener[];
|
||||||
constructor(session : JugglerSession, navigatedFrame : Frame, networkManager : NetworkManager, targetNavigationId, targetURL, firedEvents) {
|
|
||||||
|
constructor(frameManager: FrameManager, navigatedFrame: Frame, networkManager: NetworkManager, targetNavigationId, targetURL, firedEvents) {
|
||||||
|
this._frameManager = frameManager;
|
||||||
this._navigatedFrame = navigatedFrame;
|
this._navigatedFrame = navigatedFrame;
|
||||||
this._targetNavigationId = targetNavigationId;
|
this._targetNavigationId = targetNavigationId;
|
||||||
this._firedEvents = firedEvents;
|
this._firedEvents = firedEvents;
|
||||||
|
|
@ -70,15 +76,15 @@ export class NavigationWatchdog {
|
||||||
|
|
||||||
const check = this._checkNavigationComplete.bind(this);
|
const check = this._checkNavigationComplete.bind(this);
|
||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(session, JugglerSessionEvents.Disconnected, () => this._resolveCallback(new Error('Navigation failed because browser has disconnected!'))),
|
helper.addEventListener(frameManager._session, JugglerSessionEvents.Disconnected, () => this._resolveCallback(new Error('Navigation failed because browser has disconnected!'))),
|
||||||
helper.addEventListener(session, 'Page.eventFired', check),
|
helper.addEventListener(frameManager._session, 'Page.eventFired', check),
|
||||||
helper.addEventListener(session, 'Page.frameAttached', check),
|
helper.addEventListener(frameManager._session, 'Page.frameAttached', check),
|
||||||
helper.addEventListener(session, 'Page.frameDetached', check),
|
helper.addEventListener(frameManager._session, 'Page.frameDetached', check),
|
||||||
helper.addEventListener(session, 'Page.navigationStarted', check),
|
helper.addEventListener(frameManager._session, 'Page.navigationStarted', check),
|
||||||
helper.addEventListener(session, 'Page.navigationCommitted', check),
|
helper.addEventListener(frameManager._session, 'Page.navigationCommitted', check),
|
||||||
helper.addEventListener(session, 'Page.navigationAborted', this._onNavigationAborted.bind(this)),
|
helper.addEventListener(frameManager._session, 'Page.navigationAborted', this._onNavigationAborted.bind(this)),
|
||||||
helper.addEventListener(networkManager, NetworkManagerEvents.Request, this._onRequest.bind(this)),
|
helper.addEventListener(networkManager, NetworkManagerEvents.Request, this._onRequest.bind(this)),
|
||||||
helper.addEventListener(navigatedFrame._frameManager, FrameManagerEvents.FrameDetached, check),
|
helper.addEventListener(frameManager, FrameManagerEvents.FrameDetached, check),
|
||||||
];
|
];
|
||||||
check();
|
check();
|
||||||
}
|
}
|
||||||
|
|
@ -94,24 +100,23 @@ export class NavigationWatchdog {
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkNavigationComplete() {
|
_checkNavigationComplete() {
|
||||||
if (this._navigatedFrame.isDetached())
|
const checkFiredEvents = (frame: Frame, firedEvents) => {
|
||||||
this._resolveCallback(new Error('Navigating frame was detached'));
|
for (const subframe of frame.childFrames()) {
|
||||||
else if (this._navigatedFrame._lastCommittedNavigationId === this._targetNavigationId
|
|
||||||
&& checkFiredEvents(this._navigatedFrame, this._firedEvents))
|
|
||||||
this._resolveCallback(null);
|
|
||||||
|
|
||||||
|
|
||||||
function checkFiredEvents(frame, firedEvents) {
|
|
||||||
for (const subframe of frame._children) {
|
|
||||||
if (!checkFiredEvents(subframe, firedEvents))
|
if (!checkFiredEvents(subframe, firedEvents))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return firedEvents.every(event => frame._firedEvents.has(event));
|
return firedEvents.every(event => this._frameManager._frameData(frame).firedEvents.has(event));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
if (this._navigatedFrame.isDetached())
|
||||||
|
this._resolveCallback(new Error('Navigating frame was detached'));
|
||||||
|
else if (this._frameManager._frameData(this._navigatedFrame).lastCommittedNavigationId === this._targetNavigationId
|
||||||
|
&& checkFiredEvents(this._navigatedFrame, this._firedEvents))
|
||||||
|
this._resolveCallback(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onNavigationAborted(params) {
|
_onNavigationAborted(params) {
|
||||||
if (params.frameId === this._navigatedFrame._frameId && params.navigationId === this._targetNavigationId)
|
if (params.frameId === this._frameManager._frameData(this._navigatedFrame).frameId && params.navigationId === this._targetNavigationId)
|
||||||
this._resolveCallback(new Error('Navigation to ' + this._targetURL + ' failed: ' + params.errorText));
|
this._resolveCallback(new Error('Navigation to ' + this._targetURL + ' failed: ' + params.errorText));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export class Page extends EventEmitter {
|
||||||
private _closed: boolean;
|
private _closed: boolean;
|
||||||
private _pageBindings: Map<string, Function>;
|
private _pageBindings: Map<string, Function>;
|
||||||
private _networkManager: NetworkManager;
|
private _networkManager: NetworkManager;
|
||||||
private _frameManager: FrameManager;
|
_frameManager: FrameManager;
|
||||||
private _eventListeners: RegisteredListener[];
|
private _eventListeners: RegisteredListener[];
|
||||||
private _viewport: Viewport;
|
private _viewport: Viewport;
|
||||||
private _disconnectPromise: Promise<Error>;
|
private _disconnectPromise: Promise<Error>;
|
||||||
|
|
@ -312,7 +312,7 @@ export class Page extends EventEmitter {
|
||||||
const frame = this._frameManager.mainFrame();
|
const frame = this._frameManager.mainFrame();
|
||||||
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
||||||
const {navigationId, navigationURL} = await this._session.send('Page.goBack', {
|
const {navigationId, navigationURL} = await this._session.send('Page.goBack', {
|
||||||
frameId: frame._frameId,
|
frameId: this._frameManager._frameData(frame).frameId,
|
||||||
});
|
});
|
||||||
if (!navigationId)
|
if (!navigationId)
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -322,7 +322,7 @@ export class Page extends EventEmitter {
|
||||||
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
||||||
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
||||||
|
|
||||||
const watchDog = new NavigationWatchdog(this._session, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
|
const watchDog = new NavigationWatchdog(this._frameManager, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
|
||||||
const error = await Promise.race([
|
const error = await Promise.race([
|
||||||
timeoutPromise,
|
timeoutPromise,
|
||||||
watchDog.promise(),
|
watchDog.promise(),
|
||||||
|
|
@ -342,7 +342,7 @@ export class Page extends EventEmitter {
|
||||||
const frame = this._frameManager.mainFrame();
|
const frame = this._frameManager.mainFrame();
|
||||||
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
||||||
const {navigationId, navigationURL} = await this._session.send('Page.goForward', {
|
const {navigationId, navigationURL} = await this._session.send('Page.goForward', {
|
||||||
frameId: frame._frameId,
|
frameId: this._frameManager._frameData(frame).frameId,
|
||||||
});
|
});
|
||||||
if (!navigationId)
|
if (!navigationId)
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -352,7 +352,7 @@ export class Page extends EventEmitter {
|
||||||
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
||||||
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
||||||
|
|
||||||
const watchDog = new NavigationWatchdog(this._session, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
|
const watchDog = new NavigationWatchdog(this._frameManager, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
|
||||||
const error = await Promise.race([
|
const error = await Promise.race([
|
||||||
timeoutPromise,
|
timeoutPromise,
|
||||||
watchDog.promise(),
|
watchDog.promise(),
|
||||||
|
|
@ -372,7 +372,7 @@ export class Page extends EventEmitter {
|
||||||
const frame = this._frameManager.mainFrame();
|
const frame = this._frameManager.mainFrame();
|
||||||
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
||||||
const {navigationId, navigationURL} = await this._session.send('Page.reload', {
|
const {navigationId, navigationURL} = await this._session.send('Page.reload', {
|
||||||
frameId: frame._frameId,
|
frameId: this._frameManager._frameData(frame).frameId,
|
||||||
});
|
});
|
||||||
if (!navigationId)
|
if (!navigationId)
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -382,7 +382,7 @@ export class Page extends EventEmitter {
|
||||||
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
||||||
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
||||||
|
|
||||||
const watchDog = new NavigationWatchdog(this._session, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
|
const watchDog = new NavigationWatchdog(this._frameManager, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
|
||||||
const error = await Promise.race([
|
const error = await Promise.race([
|
||||||
timeoutPromise,
|
timeoutPromise,
|
||||||
watchDog.promise(),
|
watchDog.promise(),
|
||||||
|
|
|
||||||
|
|
@ -15,24 +15,21 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as types from '../types';
|
import * as types from './types';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { helper, assert } from '../helper';
|
import { helper, assert } from './helper';
|
||||||
import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption } from '../input';
|
import { ClickOptions, MultiClickOptions, PointerActionOptions, SelectOption } from './input';
|
||||||
import { ExecutionContext } from './ExecutionContext';
|
import { waitForSelectorOrXPath, WaitTaskParams, WaitTask } from './waitTask';
|
||||||
import { ElementHandle, JSHandle } from './JSHandle';
|
import { TimeoutSettings } from './TimeoutSettings';
|
||||||
import { Response } from './NetworkManager';
|
|
||||||
import { waitForSelectorOrXPath, WaitTaskParams, WaitTask } from '../waitTask';
|
|
||||||
import { TimeoutSettings } from '../TimeoutSettings';
|
|
||||||
|
|
||||||
const readFileAsync = helper.promisify(fs.readFile);
|
const readFileAsync = helper.promisify(fs.readFile);
|
||||||
|
|
||||||
type WorldType = 'main' | 'utility';
|
type WorldType = 'main' | 'utility';
|
||||||
type World = {
|
type World<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle>, ExecutionContext> = {
|
||||||
contextPromise: Promise<ExecutionContext>;
|
contextPromise: Promise<ExecutionContext>;
|
||||||
contextResolveCallback: (c: ExecutionContext) => void;
|
contextResolveCallback: (c: ExecutionContext) => void;
|
||||||
context: ExecutionContext | null;
|
context: ExecutionContext | null;
|
||||||
waitTasks: Set<WaitTask<JSHandle>>;
|
waitTasks: Set<WaitTask<JSHandle, ElementHandle>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NavigateOptions = {
|
export type NavigateOptions = {
|
||||||
|
|
@ -44,24 +41,24 @@ export type GotoOptions = NavigateOptions & {
|
||||||
referer?: string,
|
referer?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FrameDelegate {
|
export interface FrameDelegate<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle>, ExecutionContext extends types.ExecutionContext<JSHandle, ElementHandle>, Response> {
|
||||||
timeoutSettings(): TimeoutSettings;
|
timeoutSettings(): TimeoutSettings;
|
||||||
navigateFrame(frame: Frame, url: string, options?: GotoOptions): Promise<Response | null>;
|
navigateFrame(frame: Frame<JSHandle, ElementHandle, ExecutionContext, Response>, url: string, options?: GotoOptions): Promise<Response | null>;
|
||||||
waitForFrameNavigation(frame: Frame, options?: NavigateOptions): Promise<Response | null>;
|
waitForFrameNavigation(frame: Frame<JSHandle, ElementHandle, ExecutionContext, Response>, options?: NavigateOptions): Promise<Response | null>;
|
||||||
setFrameContent(frame: Frame, html: string, options?: NavigateOptions): Promise<void>;
|
setFrameContent(frame: Frame<JSHandle, ElementHandle, ExecutionContext, Response>, html: string, options?: NavigateOptions): Promise<void>;
|
||||||
adoptElementHandle(elementHandle: ElementHandle, context: ExecutionContext): Promise<ElementHandle>;
|
adoptElementHandle(elementHandle: ElementHandle, context: ExecutionContext): Promise<ElementHandle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Frame {
|
export class Frame<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle>, ExecutionContext extends types.ExecutionContext<JSHandle, ElementHandle>, Response> {
|
||||||
_delegate: FrameDelegate;
|
_delegate: FrameDelegate<JSHandle, ElementHandle, ExecutionContext, Response>;
|
||||||
private _parentFrame: Frame;
|
private _parentFrame: Frame<JSHandle, ElementHandle, ExecutionContext, Response>;
|
||||||
private _url = '';
|
private _url = '';
|
||||||
private _detached = false;
|
private _detached = false;
|
||||||
private _worlds = new Map<WorldType, World>();
|
private _worlds = new Map<WorldType, World<JSHandle, ElementHandle, ExecutionContext>>();
|
||||||
private _childFrames = new Set<Frame>();
|
private _childFrames = new Set<Frame<JSHandle, ElementHandle, ExecutionContext, Response>>();
|
||||||
private _name: string;
|
private _name: string;
|
||||||
|
|
||||||
constructor(delegate: FrameDelegate, parentFrame: Frame | null) {
|
constructor(delegate: FrameDelegate<JSHandle, ElementHandle, ExecutionContext, Response>, parentFrame: Frame<JSHandle, ElementHandle, ExecutionContext, Response> | null) {
|
||||||
this._delegate = delegate;
|
this._delegate = delegate;
|
||||||
this._parentFrame = parentFrame;
|
this._parentFrame = parentFrame;
|
||||||
|
|
||||||
|
|
@ -162,11 +159,11 @@ export class Frame {
|
||||||
return this._url;
|
return this._url;
|
||||||
}
|
}
|
||||||
|
|
||||||
parentFrame(): Frame | null {
|
parentFrame(): Frame<JSHandle, ElementHandle, ExecutionContext, Response> | null {
|
||||||
return this._parentFrame;
|
return this._parentFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
childFrames(): Frame[] {
|
childFrames(): Frame<JSHandle, ElementHandle, ExecutionContext, Response>[] {
|
||||||
return Array.from(this._childFrames);
|
return Array.from(this._childFrames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,7 +348,11 @@ export class Frame {
|
||||||
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 utilityContext = await this._utilityContext();
|
||||||
const adoptedValues = await Promise.all(values.map(async value => value instanceof ElementHandle ? this._adoptElementHandle(value, utilityContext, false /* dispose */) : value));
|
const adoptedValues = await Promise.all(values.map(async value => {
|
||||||
|
if (typeof value === 'object' && (value as any).asElement && (value as any).asElement() === value)
|
||||||
|
return this._adoptElementHandle(value as ElementHandle, utilityContext, false /* dispose */);
|
||||||
|
return value;
|
||||||
|
}));
|
||||||
const result = await handle.select(...adoptedValues);
|
const result = await handle.select(...adoptedValues);
|
||||||
await handle.dispose();
|
await handle.dispose();
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -372,8 +373,8 @@ export class Frame {
|
||||||
if (helper.isString(selectorOrFunctionOrTimeout)) {
|
if (helper.isString(selectorOrFunctionOrTimeout)) {
|
||||||
const string = selectorOrFunctionOrTimeout as string;
|
const string = selectorOrFunctionOrTimeout as string;
|
||||||
if (string.startsWith(xPathPattern))
|
if (string.startsWith(xPathPattern))
|
||||||
return this.waitForXPath(string, options);
|
return this.waitForXPath(string, options) as any;
|
||||||
return this.waitForSelector(string, options);
|
return this.waitForSelector(string, options) as any;
|
||||||
}
|
}
|
||||||
if (helper.isNumber(selectorOrFunctionOrTimeout))
|
if (helper.isNumber(selectorOrFunctionOrTimeout))
|
||||||
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout as number));
|
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout as number));
|
||||||
|
|
@ -449,7 +450,7 @@ export class Frame {
|
||||||
this._parentFrame = null;
|
this._parentFrame = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _scheduleWaitTask(params: WaitTaskParams, world: World): Promise<JSHandle> {
|
private _scheduleWaitTask(params: WaitTaskParams, world: World<JSHandle, ElementHandle, ExecutionContext>): Promise<JSHandle> {
|
||||||
const task = new WaitTask(params, () => world.waitTasks.delete(task));
|
const task = new WaitTask(params, () => world.waitTasks.delete(task));
|
||||||
world.waitTasks.add(task);
|
world.waitTasks.add(task);
|
||||||
if (world.context)
|
if (world.context)
|
||||||
25
src/types.ts
25
src/types.ts
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
import * as input from './input';
|
||||||
|
|
||||||
type Boxed<Args extends any[], Handle> = { [Index in keyof Args]: Args[Index] | Handle };
|
type Boxed<Args extends any[], Handle> = { [Index in keyof Args]: Args[Index] | Handle };
|
||||||
type PageFunction<Args extends any[], R = any> = string | ((...args: Args) => R | Promise<R>);
|
type PageFunction<Args extends any[], R = any> = string | ((...args: Args) => R | Promise<R>);
|
||||||
type PageFunctionOn<On, Args extends any[], R = any> = string | ((on: On, ...args: Args) => R | Promise<R>);
|
type PageFunctionOn<On, Args extends any[], R = any> = string | ((on: On, ...args: Args) => R | Promise<R>);
|
||||||
|
|
@ -12,11 +14,30 @@ export type $$Eval<Handle> = <Args extends any[], R>(selector: string, pageFunct
|
||||||
export type EvaluateOn<Handle> = <Args extends any[], R>(pageFunction: PageFunctionOn<any, Args, R>, ...args: Boxed<Args, Handle>) => Promise<R>;
|
export type EvaluateOn<Handle> = <Args extends any[], R>(pageFunction: PageFunctionOn<any, Args, R>, ...args: Boxed<Args, Handle>) => Promise<R>;
|
||||||
export type EvaluateHandleOn<Handle> = <Args extends any[]>(pageFunction: PageFunctionOn<any, Args>, ...args: Boxed<Args, Handle>) => Promise<Handle>;
|
export type EvaluateHandleOn<Handle> = <Args extends any[]>(pageFunction: PageFunctionOn<any, Args>, ...args: Boxed<Args, Handle>) => Promise<Handle>;
|
||||||
|
|
||||||
export interface EvaluationContext<Handle> {
|
export interface ExecutionContext<Handle extends JSHandle<Handle, EHandle>, EHandle extends ElementHandle<Handle, EHandle>> {
|
||||||
evaluate: Evaluate<Handle>;
|
evaluate: Evaluate<Handle>;
|
||||||
evaluateHandle: EvaluateHandle<Handle>;
|
evaluateHandle: EvaluateHandle<Handle>;
|
||||||
|
_document(): Promise<EHandle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Handle {
|
export interface JSHandle<Handle extends JSHandle<Handle, EHandle>, EHandle extends ElementHandle<Handle, EHandle>> {
|
||||||
|
executionContext(): ExecutionContext<Handle, EHandle>;
|
||||||
dispose(): Promise<void>;
|
dispose(): Promise<void>;
|
||||||
|
asElement(): EHandle | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ElementHandle<Handle extends JSHandle<Handle, EHandle>, EHandle extends ElementHandle<Handle, EHandle>> extends JSHandle<Handle, EHandle> {
|
||||||
|
$(selector: string): Promise<EHandle | null>;
|
||||||
|
$x(expression: string): Promise<EHandle[]>;
|
||||||
|
$$(selector: string): Promise<EHandle[]>;
|
||||||
|
$eval: $Eval<Handle>;
|
||||||
|
$$eval: $$Eval<Handle>;
|
||||||
|
click(options?: input.ClickOptions): Promise<void>;
|
||||||
|
dblclick(options?: input.MultiClickOptions): Promise<void>;
|
||||||
|
tripleclick(options?: input.MultiClickOptions): Promise<void>;
|
||||||
|
fill(value: string): Promise<void>;
|
||||||
|
focus(): Promise<void>;
|
||||||
|
hover(options?: input.PointerActionOptions): Promise<void>;
|
||||||
|
select(...values: (string | EHandle | input.SelectOption)[]): Promise<string[]>;
|
||||||
|
type(text: string, options: { delay: (number | undefined); } | undefined): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,12 @@ export type WaitTaskParams = {
|
||||||
args: any[];
|
args: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export class WaitTask<Handle extends types.Handle> {
|
export class WaitTask<JSHandle extends types.JSHandle<JSHandle, ElementHandle>, ElementHandle extends types.ElementHandle<JSHandle, ElementHandle>> {
|
||||||
readonly promise: Promise<Handle>;
|
readonly promise: Promise<JSHandle>;
|
||||||
private _cleanup: () => void;
|
private _cleanup: () => void;
|
||||||
private _params: WaitTaskParams & { predicateBody: string };
|
private _params: WaitTaskParams & { predicateBody: string };
|
||||||
private _runCount: number;
|
private _runCount: number;
|
||||||
private _resolve: (result: Handle) => void;
|
private _resolve: (result: JSHandle) => void;
|
||||||
private _reject: (reason: Error) => void;
|
private _reject: (reason: Error) => void;
|
||||||
private _timeoutTimer: NodeJS.Timer;
|
private _timeoutTimer: NodeJS.Timer;
|
||||||
private _terminated: boolean;
|
private _terminated: boolean;
|
||||||
|
|
@ -38,7 +38,7 @@ export class WaitTask<Handle extends types.Handle> {
|
||||||
};
|
};
|
||||||
this._cleanup = cleanup;
|
this._cleanup = cleanup;
|
||||||
this._runCount = 0;
|
this._runCount = 0;
|
||||||
this.promise = new Promise<Handle>((resolve, reject) => {
|
this.promise = new Promise<JSHandle>((resolve, reject) => {
|
||||||
this._resolve = resolve;
|
this._resolve = resolve;
|
||||||
this._reject = reject;
|
this._reject = reject;
|
||||||
});
|
});
|
||||||
|
|
@ -56,9 +56,9 @@ export class WaitTask<Handle extends types.Handle> {
|
||||||
this._doCleanup();
|
this._doCleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
async rerun(context: types.EvaluationContext<Handle>) {
|
async rerun(context: types.ExecutionContext<JSHandle, ElementHandle>) {
|
||||||
const runCount = ++this._runCount;
|
const runCount = ++this._runCount;
|
||||||
let success: Handle | null = null;
|
let success: JSHandle | null = null;
|
||||||
let error = null;
|
let error = null;
|
||||||
try {
|
try {
|
||||||
success = await context.evaluateHandle(waitForPredicatePageFunction, this._params.predicateBody, this._params.polling, this._params.timeout, ...this._params.args);
|
success = await context.evaluateHandle(waitForPredicatePageFunction, this._params.predicateBody, this._params.polling, this._params.timeout, ...this._params.args);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import { TargetSession } from './Connection';
|
||||||
import { Frame } from './FrameManager';
|
import { Frame } from './FrameManager';
|
||||||
import { helper } from '../helper';
|
import { helper } from '../helper';
|
||||||
import { valueFromRemoteObject } from './protocolHelper';
|
import { valueFromRemoteObject } from './protocolHelper';
|
||||||
import { createJSHandle, JSHandle } from './JSHandle';
|
import { createJSHandle, JSHandle, ElementHandle } from './JSHandle';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import * as injectedSource from '../generated/injectedSource';
|
import * as injectedSource from '../generated/injectedSource';
|
||||||
import * as cssSelectorEngineSource from '../generated/cssSelectorEngineSource';
|
import * as cssSelectorEngineSource from '../generated/cssSelectorEngineSource';
|
||||||
|
|
@ -29,7 +29,7 @@ import * as types from '../types';
|
||||||
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;
|
||||||
|
|
||||||
export class ExecutionContext implements types.EvaluationContext<JSHandle> {
|
export class ExecutionContext {
|
||||||
_globalObjectId?: string;
|
_globalObjectId?: string;
|
||||||
_session: TargetSession;
|
_session: TargetSession;
|
||||||
_frame: Frame;
|
_frame: Frame;
|
||||||
|
|
@ -37,6 +37,7 @@ export class ExecutionContext implements types.EvaluationContext<JSHandle> {
|
||||||
private _contextDestroyedCallback: any;
|
private _contextDestroyedCallback: any;
|
||||||
private _executionContextDestroyedPromise: Promise<unknown>;
|
private _executionContextDestroyedPromise: Promise<unknown>;
|
||||||
private _injectedPromise: Promise<JSHandle> | null = null;
|
private _injectedPromise: Promise<JSHandle> | null = null;
|
||||||
|
private _documentPromise: Promise<ElementHandle> | null = null;
|
||||||
|
|
||||||
constructor(client: TargetSession, contextPayload: Protocol.Runtime.ExecutionContextDescription, frame: Frame | null) {
|
constructor(client: TargetSession, contextPayload: Protocol.Runtime.ExecutionContextDescription, frame: Frame | null) {
|
||||||
this._session = client;
|
this._session = client;
|
||||||
|
|
@ -317,4 +318,10 @@ export class ExecutionContext implements types.EvaluationContext<JSHandle> {
|
||||||
}
|
}
|
||||||
return this._injectedPromise;
|
return this._injectedPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_document(): Promise<ElementHandle> {
|
||||||
|
if (!this._documentPromise)
|
||||||
|
this._documentPromise = this.evaluateHandle('document').then(handle => handle.asElement()!);
|
||||||
|
return this._documentPromise;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as EventEmitter from 'events';
|
import * as EventEmitter from 'events';
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as types from '../types';
|
|
||||||
import { TimeoutError } from '../Errors';
|
import { TimeoutError } from '../Errors';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
import { assert, debugError, helper, RegisteredListener } from '../helper';
|
import { assert, debugError, helper, RegisteredListener } from '../helper';
|
||||||
|
|
@ -28,10 +26,7 @@ import { ElementHandle, JSHandle } from './JSHandle';
|
||||||
import { NetworkManager, NetworkManagerEvents, Request, Response } from './NetworkManager';
|
import { NetworkManager, NetworkManagerEvents, Request, Response } from './NetworkManager';
|
||||||
import { Page } from './Page';
|
import { Page } from './Page';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
import { MultiClickOptions, ClickOptions, SelectOption } from '../input';
|
import * as frames from '../frames';
|
||||||
import { WaitTask, WaitTaskParams, waitForSelectorOrXPath } from '../waitTask';
|
|
||||||
|
|
||||||
const readFileAsync = helper.promisify(fs.readFile);
|
|
||||||
|
|
||||||
export const FrameManagerEvents = {
|
export const FrameManagerEvents = {
|
||||||
FrameNavigatedWithinDocument: Symbol('FrameNavigatedWithinDocument'),
|
FrameNavigatedWithinDocument: Symbol('FrameNavigatedWithinDocument'),
|
||||||
|
|
@ -41,7 +36,14 @@ export const FrameManagerEvents = {
|
||||||
FrameNavigated: Symbol('FrameNavigated'),
|
FrameNavigated: Symbol('FrameNavigated'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FrameManager extends EventEmitter {
|
const frameDataSymbol = Symbol('frameData');
|
||||||
|
type FrameData = {
|
||||||
|
id: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Frame = frames.Frame<JSHandle, ElementHandle, ExecutionContext, Response>;
|
||||||
|
|
||||||
|
export class FrameManager extends EventEmitter implements frames.FrameDelegate<JSHandle, ElementHandle, ExecutionContext, Response> {
|
||||||
_session: TargetSession;
|
_session: TargetSession;
|
||||||
_page: Page;
|
_page: Page;
|
||||||
_networkManager: NetworkManager;
|
_networkManager: NetworkManager;
|
||||||
|
|
@ -99,8 +101,10 @@ export class FrameManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectFromTarget() {
|
disconnectFromTarget() {
|
||||||
for (const frame of this.frames())
|
for (const context of this._contextIdToContext.values()) {
|
||||||
frame._setContext(null);
|
context._dispose();
|
||||||
|
context.frame()._contextDestroyed(context);
|
||||||
|
}
|
||||||
// this._mainFrame = null;
|
// this._mainFrame = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,7 +116,6 @@ export class FrameManager extends EventEmitter {
|
||||||
const frame = this._frames.get(frameId);
|
const frame = this._frames.get(frameId);
|
||||||
if (!frame)
|
if (!frame)
|
||||||
return;
|
return;
|
||||||
frame._onLoadingStopped();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleFrameTree(frameTree: Protocol.Page.FrameResourceTree) {
|
_handleFrameTree(frameTree: Protocol.Page.FrameResourceTree) {
|
||||||
|
|
@ -142,13 +145,21 @@ export class FrameManager extends EventEmitter {
|
||||||
return this._frames.get(frameId) || null;
|
return this._frames.get(frameId) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_frameData(frame: Frame): FrameData {
|
||||||
|
return (frame as any)[frameDataSymbol];
|
||||||
|
}
|
||||||
|
|
||||||
_onFrameAttached(frameId: string, parentFrameId: string | null) {
|
_onFrameAttached(frameId: string, parentFrameId: string | null) {
|
||||||
if (this._frames.has(frameId))
|
if (this._frames.has(frameId))
|
||||||
return;
|
return;
|
||||||
assert(parentFrameId);
|
assert(parentFrameId);
|
||||||
const parentFrame = this._frames.get(parentFrameId);
|
const parentFrame = this._frames.get(parentFrameId);
|
||||||
const frame = new Frame(this, this._session, parentFrame, frameId);
|
const frame = new frames.Frame(this, parentFrame);
|
||||||
this._frames.set(frame._id, frame);
|
const data: FrameData = {
|
||||||
|
id: frameId,
|
||||||
|
};
|
||||||
|
frame[frameDataSymbol] = data;
|
||||||
|
this._frames.set(frameId, frame);
|
||||||
this.emit(FrameManagerEvents.FrameAttached, frame);
|
this.emit(FrameManagerEvents.FrameAttached, frame);
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
@ -163,13 +174,17 @@ export class FrameManager extends EventEmitter {
|
||||||
this._removeFramesRecursively(child);
|
this._removeFramesRecursively(child);
|
||||||
if (isMainFrame) {
|
if (isMainFrame) {
|
||||||
// Update frame id to retain frame identity on cross-process navigation.
|
// Update frame id to retain frame identity on cross-process navigation.
|
||||||
this._frames.delete(frame._id);
|
const data = this._frameData(frame);
|
||||||
frame._id = framePayload.id;
|
this._frames.delete(data.id);
|
||||||
this._frames.set(framePayload.id, frame);
|
data.id = framePayload.id;
|
||||||
}
|
}
|
||||||
} else if (isMainFrame) {
|
} else if (isMainFrame) {
|
||||||
// Initial frame navigation.
|
// Initial frame navigation.
|
||||||
frame = new Frame(this, this._session, null, framePayload.id);
|
frame = new frames.Frame(this, null);
|
||||||
|
const data: FrameData = {
|
||||||
|
id: framePayload.id,
|
||||||
|
};
|
||||||
|
frame[frameDataSymbol] = data;
|
||||||
this._frames.set(framePayload.id, frame);
|
this._frames.set(framePayload.id, frame);
|
||||||
} else {
|
} else {
|
||||||
// FIXME(WebKit): there is no Page.frameAttached event in WK.
|
// FIXME(WebKit): there is no Page.frameAttached event in WK.
|
||||||
|
|
@ -180,7 +195,13 @@ export class FrameManager extends EventEmitter {
|
||||||
this._mainFrame = frame;
|
this._mainFrame = frame;
|
||||||
|
|
||||||
// Update frame payload.
|
// Update frame payload.
|
||||||
frame._navigated(framePayload);
|
frame._navigated(framePayload.url, framePayload.name);
|
||||||
|
for (const context of this._contextIdToContext.values()) {
|
||||||
|
if (context.frame() === frame) {
|
||||||
|
context._dispose();
|
||||||
|
frame._contextDestroyed(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +210,7 @@ export class FrameManager extends EventEmitter {
|
||||||
const frame = this._frames.get(frameId);
|
const frame = this._frames.get(frameId);
|
||||||
if (!frame)
|
if (!frame)
|
||||||
return;
|
return;
|
||||||
frame._navigatedWithinDocument(url);
|
frame._navigated(url, frame.name());
|
||||||
this.emit(FrameManagerEvents.FrameNavigatedWithinDocument, frame);
|
this.emit(FrameManagerEvents.FrameNavigatedWithinDocument, frame);
|
||||||
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
||||||
}
|
}
|
||||||
|
|
@ -209,12 +230,11 @@ export class FrameManager extends EventEmitter {
|
||||||
const frame = this._frames.get(frameId) || null;
|
const frame = this._frames.get(frameId) || null;
|
||||||
if (!frame)
|
if (!frame)
|
||||||
return;
|
return;
|
||||||
// FIXME(WebKit): we ignore duplicate Runtime.executionContextCreated events here.
|
|
||||||
if (frame._executionContext && frame._executionContext._contextId === contextPayload.id)
|
|
||||||
return;
|
|
||||||
/** @type {!ExecutionContext} */
|
|
||||||
const context: ExecutionContext = new ExecutionContext(this._session, contextPayload, frame);
|
const context: ExecutionContext = new ExecutionContext(this._session, contextPayload, frame);
|
||||||
frame._setContext(context);
|
if (frame) {
|
||||||
|
frame._contextCreated('main', context);
|
||||||
|
frame._contextCreated('utility', context);
|
||||||
|
}
|
||||||
this._contextIdToContext.set(contextPayload.id, context);
|
this._contextIdToContext.set(contextPayload.id, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,458 +248,52 @@ export class FrameManager extends EventEmitter {
|
||||||
for (const child of frame.childFrames())
|
for (const child of frame.childFrames())
|
||||||
this._removeFramesRecursively(child);
|
this._removeFramesRecursively(child);
|
||||||
frame._detach();
|
frame._detach();
|
||||||
this._frames.delete(frame._id);
|
this._frames.delete(this._frameData(frame).id);
|
||||||
this.emit(FrameManagerEvents.FrameDetached, frame);
|
this.emit(FrameManagerEvents.FrameDetached, frame);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export class Frame {
|
timeoutSettings(): TimeoutSettings {
|
||||||
_id: string;
|
return this._timeoutSettings;
|
||||||
_frameManager: FrameManager;
|
|
||||||
_session: any;
|
|
||||||
_parentFrame: Frame;
|
|
||||||
_url: string;
|
|
||||||
_detached: boolean;
|
|
||||||
_loaderId: string;
|
|
||||||
_lifecycleEvents: Set<string>;
|
|
||||||
_waitTasks: Set<WaitTask<JSHandle>>;
|
|
||||||
_executionContext: ExecutionContext | null;
|
|
||||||
_contextPromise: Promise<ExecutionContext>;
|
|
||||||
_contextResolveCallback: (arg: ExecutionContext) => void;
|
|
||||||
_childFrames: Set<Frame>;
|
|
||||||
_documentPromise: Promise<ElementHandle>;
|
|
||||||
_name: string;
|
|
||||||
_navigationURL: any;
|
|
||||||
constructor(frameManager: FrameManager, client: TargetSession, parentFrame: Frame | null, frameId: string) {
|
|
||||||
this._frameManager = frameManager;
|
|
||||||
this._session = client;
|
|
||||||
this._parentFrame = parentFrame;
|
|
||||||
this._url = '';
|
|
||||||
this._id = frameId;
|
|
||||||
this._detached = false;
|
|
||||||
|
|
||||||
this._loaderId = '';
|
|
||||||
/** @type {!Set<string>} */
|
|
||||||
this._lifecycleEvents = new Set();
|
|
||||||
|
|
||||||
/** @type {!Set<!WaitTask>} */
|
|
||||||
this._waitTasks = new Set();
|
|
||||||
|
|
||||||
this._executionContext = null;
|
|
||||||
this._contextPromise = null;
|
|
||||||
this._contextResolveCallback = null;
|
|
||||||
this._setContext(null);
|
|
||||||
|
|
||||||
/** @type {!Set<!Frame>} */
|
|
||||||
this._childFrames = new Set();
|
|
||||||
if (this._parentFrame)
|
|
||||||
this._parentFrame._childFrames.add(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async goto(url: string, options: { referer?: string; timeout?: number; waitUntil?: string | Array<string>; } | undefined = {}): Promise<Response | null> {
|
async navigateFrame(frame: Frame, url: string, options: { referer?: string; timeout?: number; waitUntil?: string | Array<string>; } | undefined = {}): Promise<Response | null> {
|
||||||
const {
|
const {
|
||||||
timeout = this._frameManager._timeoutSettings.navigationTimeout(),
|
timeout = this._timeoutSettings.navigationTimeout(),
|
||||||
} = options;
|
} = options;
|
||||||
const watchDog = new NextNavigationWatchdog(this, timeout);
|
const watchDog = new NextNavigationWatchdog(this, frame, timeout);
|
||||||
await this._session.send('Page.navigate', {url});
|
await this._session.send('Page.navigate', {url});
|
||||||
return watchDog.waitForNavigation();
|
return watchDog.waitForNavigation();
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForNavigation(): Promise<Response | null> {
|
async waitForFrameNavigation(frame: Frame, options?: frames.NavigateOptions): Promise<Response | null> {
|
||||||
// FIXME: this method only works for main frames.
|
// FIXME: this method only works for main frames.
|
||||||
const watchDog = new NextNavigationWatchdog(this, 10000);
|
const watchDog = new NextNavigationWatchdog(this, frame, 10000);
|
||||||
return watchDog.waitForNavigation();
|
return watchDog.waitForNavigation();
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSelector(selector: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined): Promise<ElementHandle | null> {
|
async adoptElementHandle(elementHandle: ElementHandle, context: ExecutionContext): Promise<ElementHandle> {
|
||||||
const params = waitForSelectorOrXPath(selector, false /* isXPath */, { timeout: this._frameManager._timeoutSettings.timeout(), ...options });
|
assert(false, 'Multiple isolated worlds are not implemented');
|
||||||
const handle = await this._scheduleWaitTask(params);
|
return elementHandle;
|
||||||
if (!handle.asElement()) {
|
|
||||||
await handle.dispose();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return handle.asElement();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForXPath(xpath: string, options: { visible?: boolean, hidden?: boolean, timeout?: number } = {}): Promise<ElementHandle | null> {
|
async setFrameContent(frame: Frame, html: string, options: { timeout?: number; waitUntil?: string | Array<string>; } | undefined = {}) {
|
||||||
const params = waitForSelectorOrXPath(xpath, true /* isXPath */, { timeout: this._frameManager._timeoutSettings.timeout(), ...options });
|
|
||||||
const handle = await this._scheduleWaitTask(params);
|
|
||||||
if (!handle.asElement()) {
|
|
||||||
await handle.dispose();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return handle.asElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForFunction(pageFunction: Function | string, options: { polling?: string | number; timeout?: number; } | undefined = {}, ...args): Promise<JSHandle> {
|
|
||||||
const {
|
|
||||||
polling = 'raf',
|
|
||||||
timeout = this._frameManager._timeoutSettings.timeout(),
|
|
||||||
} = options;
|
|
||||||
const params: WaitTaskParams = {
|
|
||||||
predicateBody: pageFunction,
|
|
||||||
title: 'function',
|
|
||||||
polling,
|
|
||||||
timeout,
|
|
||||||
args
|
|
||||||
};
|
|
||||||
return this._scheduleWaitTask(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
async executionContext(): Promise<ExecutionContext> {
|
|
||||||
if (this._detached)
|
|
||||||
throw new Error(`Execution Context is not available in detached frame`);
|
|
||||||
return this._contextPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluateHandle: types.EvaluateHandle<JSHandle> = async (pageFunction, ...args) => {
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return context.evaluateHandle(pageFunction, ...args as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluate: types.Evaluate<JSHandle> = async (pageFunction, ...args) => {
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return context.evaluate(pageFunction, ...args as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
async $(selector: string): Promise<ElementHandle | null> {
|
|
||||||
const document = await this._document();
|
|
||||||
const value = await document.$(selector);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
_document(): Promise<ElementHandle> {
|
|
||||||
if (!this._documentPromise) {
|
|
||||||
this._documentPromise = this.executionContext().then(async context => {
|
|
||||||
const document = await context.evaluateHandle('document');
|
|
||||||
return document.asElement();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this._documentPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
async $x(expression: string): Promise<Array<ElementHandle>> {
|
|
||||||
const document = await this._document();
|
|
||||||
const value = await document.$x(expression);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
$eval: types.$Eval<JSHandle> = async (selector, pageFunction, ...args) => {
|
|
||||||
const document = await this._document();
|
|
||||||
return document.$eval(selector, pageFunction, ...args as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
$$eval: types.$$Eval<JSHandle> = async (selector, pageFunction, ...args) => {
|
|
||||||
const document = await this._document();
|
|
||||||
const value = await document.$$eval(selector, pageFunction, ...args as any);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async $$(selector: string): Promise<Array<ElementHandle>> {
|
|
||||||
const document = await this._document();
|
|
||||||
const value = await document.$$(selector);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async content(): Promise<string> {
|
|
||||||
return await this.evaluate(() => {
|
|
||||||
let retVal = '';
|
|
||||||
if (document.doctype)
|
|
||||||
retVal = new XMLSerializer().serializeToString(document.doctype);
|
|
||||||
if (document.documentElement)
|
|
||||||
retVal += document.documentElement.outerHTML;
|
|
||||||
return retVal;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async setContent(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, 1000);
|
const watchDog = new NextNavigationWatchdog(this, frame, 1000);
|
||||||
await this.evaluate(html => {
|
await frame.evaluate(html => {
|
||||||
document.open();
|
document.open();
|
||||||
document.write(html);
|
document.write(html);
|
||||||
document.close();
|
document.close();
|
||||||
}, html);
|
}, html);
|
||||||
await watchDog.waitForNavigation();
|
await watchDog.waitForNavigation();
|
||||||
}
|
}
|
||||||
|
|
||||||
async addScriptTag(options: { url?: string; path?: string; content?: string; type?: string; }): Promise<ElementHandle> {
|
|
||||||
const {
|
|
||||||
url = null,
|
|
||||||
path = null,
|
|
||||||
content = null,
|
|
||||||
type = ''
|
|
||||||
} = options;
|
|
||||||
if (url !== null) {
|
|
||||||
try {
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return (await context.evaluateHandle(addScriptUrl, url, type)).asElement();
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Loading script from ${url} failed`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path !== null) {
|
|
||||||
let contents = await readFileAsync(path, 'utf8');
|
|
||||||
contents += '//# sourceURL=' + path.replace(/\n/g, '');
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return (await context.evaluateHandle(addScriptContent, contents, type)).asElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content !== null) {
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return (await context.evaluateHandle(addScriptContent, content, type)).asElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Provide an object with a `url`, `path` or `content` property');
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
async function addScriptUrl(url: string, type: string): Promise<HTMLElement> {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = url;
|
|
||||||
if (type)
|
|
||||||
script.type = type;
|
|
||||||
const promise = new Promise((res, rej) => {
|
|
||||||
script.onload = res;
|
|
||||||
script.onerror = rej;
|
|
||||||
});
|
|
||||||
document.head.appendChild(script);
|
|
||||||
await promise;
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
function addScriptContent(content: string, type: string = 'text/javascript'): HTMLElement {
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.type = type;
|
|
||||||
script.text = content;
|
|
||||||
let error = null;
|
|
||||||
script.onerror = e => error = e;
|
|
||||||
document.head.appendChild(script);
|
|
||||||
if (error)
|
|
||||||
throw error;
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise<ElementHandle> {
|
|
||||||
const {
|
|
||||||
url = null,
|
|
||||||
path = null,
|
|
||||||
content = null
|
|
||||||
} = options;
|
|
||||||
if (url !== null) {
|
|
||||||
try {
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return (await context.evaluateHandle(addStyleUrl, url)).asElement();
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`Loading style from ${url} failed`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path !== null) {
|
|
||||||
let contents = await readFileAsync(path, 'utf8');
|
|
||||||
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return (await context.evaluateHandle(addStyleContent, contents)).asElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content !== null) {
|
|
||||||
const context = await this.executionContext();
|
|
||||||
return (await context.evaluateHandle(addStyleContent, content)).asElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('Provide an object with a `url`, `path` or `content` property');
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
async function addStyleUrl(url: string): Promise<HTMLElement> {
|
|
||||||
const link = document.createElement('link');
|
|
||||||
link.rel = 'stylesheet';
|
|
||||||
link.href = url;
|
|
||||||
const promise = new Promise((res, rej) => {
|
|
||||||
link.onload = res;
|
|
||||||
link.onerror = rej;
|
|
||||||
});
|
|
||||||
document.head.appendChild(link);
|
|
||||||
await promise;
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
async function addStyleContent(content: string): Promise<HTMLElement> {
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.type = 'text/css';
|
|
||||||
style.appendChild(document.createTextNode(content));
|
|
||||||
const promise = new Promise((res, rej) => {
|
|
||||||
style.onload = res;
|
|
||||||
style.onerror = rej;
|
|
||||||
});
|
|
||||||
document.head.appendChild(style);
|
|
||||||
await promise;
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
name(): string {
|
|
||||||
return this._name || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
url(): string {
|
|
||||||
return this._url;
|
|
||||||
}
|
|
||||||
|
|
||||||
parentFrame(): Frame | null {
|
|
||||||
return this._parentFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
childFrames(): Array<Frame> {
|
|
||||||
return Array.from(this._childFrames);
|
|
||||||
}
|
|
||||||
|
|
||||||
isDetached(): boolean {
|
|
||||||
return this._detached;
|
|
||||||
}
|
|
||||||
|
|
||||||
async click(selector: string, options?: ClickOptions) {
|
|
||||||
const handle = await this.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.click(options);
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async dblclick(selector: string, options?: MultiClickOptions) {
|
|
||||||
const handle = await this.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.dblclick(options);
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async tripleclick(selector: string, options?: MultiClickOptions) {
|
|
||||||
const handle = await this.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.tripleclick(options);
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fill(selector: string, value: string) {
|
|
||||||
const handle = await this.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.fill(value);
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async focus(selector: string) {
|
|
||||||
const handle = await this.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.focus();
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async hover(selector: string) {
|
|
||||||
const handle = await this.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.hover();
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
async select(selector: string, ...values: (string | ElementHandle | SelectOption)[]): Promise<string[]> {
|
|
||||||
const handle = await this.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
const result = await handle.select(...values);
|
|
||||||
await handle.dispose();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async type(selector: string, text: string, options: { delay: (number | undefined); } | undefined) {
|
|
||||||
const handle = await this.$(selector);
|
|
||||||
assert(handle, 'No node found for selector: ' + selector);
|
|
||||||
await handle.type(text, options);
|
|
||||||
await handle.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
waitFor(selectorOrFunctionOrTimeout: (string | number | Function), options: object | undefined = {}, ...args: Array<any>): Promise<JSHandle | null> {
|
|
||||||
const xPathPattern = '//';
|
|
||||||
|
|
||||||
if (helper.isString(selectorOrFunctionOrTimeout)) {
|
|
||||||
const string: string = /** @type {string} */ (selectorOrFunctionOrTimeout);
|
|
||||||
if (string.startsWith(xPathPattern))
|
|
||||||
return this.waitForXPath(string, options);
|
|
||||||
return this.waitForSelector(string, options);
|
|
||||||
}
|
|
||||||
if (helper.isNumber(selectorOrFunctionOrTimeout))
|
|
||||||
return new Promise(fulfill => setTimeout(fulfill, /** @type {number} */ (selectorOrFunctionOrTimeout)));
|
|
||||||
if (typeof selectorOrFunctionOrTimeout === 'function')
|
|
||||||
return this.waitForFunction(selectorOrFunctionOrTimeout, options, ...args);
|
|
||||||
return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
|
|
||||||
}
|
|
||||||
|
|
||||||
async title(): Promise<string> {
|
|
||||||
return this.evaluate(() => document.title);
|
|
||||||
}
|
|
||||||
|
|
||||||
_navigated(framePayload: Protocol.Page.Frame) {
|
|
||||||
this._name = framePayload.name;
|
|
||||||
// TODO(lushnikov): remove this once requestInterception has loaderId exposed.
|
|
||||||
this._navigationURL = framePayload.url;
|
|
||||||
this._url = framePayload.url;
|
|
||||||
// It may have been disposed by targetDestroyed.
|
|
||||||
if (this._executionContext)
|
|
||||||
this._setContext(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
_navigatedWithinDocument(url: string) {
|
|
||||||
this._url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onLoadingStopped() {
|
|
||||||
this._lifecycleEvents.add('DOMContentLoaded');
|
|
||||||
this._lifecycleEvents.add('load');
|
|
||||||
}
|
|
||||||
|
|
||||||
_detach() {
|
|
||||||
this._detached = true;
|
|
||||||
for (const waitTask of this._waitTasks)
|
|
||||||
waitTask.terminate(new Error('waitForFunction failed: frame got detached.'));
|
|
||||||
if (this._parentFrame)
|
|
||||||
this._parentFrame._childFrames.delete(this);
|
|
||||||
this._parentFrame = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_setContext(context: ExecutionContext | null) {
|
|
||||||
if (this._executionContext)
|
|
||||||
this._executionContext._dispose();
|
|
||||||
this._executionContext = context;
|
|
||||||
if (context) {
|
|
||||||
this._contextResolveCallback.call(null, context);
|
|
||||||
this._contextResolveCallback = null;
|
|
||||||
for (const waitTask of this._waitTasks)
|
|
||||||
waitTask.rerun(context);
|
|
||||||
} else {
|
|
||||||
this._documentPromise = null;
|
|
||||||
this._contextPromise = new Promise(fulfill => {
|
|
||||||
this._contextResolveCallback = fulfill;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _scheduleWaitTask(params: WaitTaskParams): Promise<JSHandle> {
|
|
||||||
const task = new WaitTask(params, () => this._waitTasks.delete(task));
|
|
||||||
this._waitTasks.add(task);
|
|
||||||
if (this._executionContext)
|
|
||||||
task.rerun(this._executionContext);
|
|
||||||
return task.promise;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
class NextNavigationWatchdog {
|
class NextNavigationWatchdog {
|
||||||
_frame: any;
|
_frameManager: FrameManager;
|
||||||
|
_frame: Frame;
|
||||||
_newDocumentNavigationPromise: Promise<unknown>;
|
_newDocumentNavigationPromise: Promise<unknown>;
|
||||||
_newDocumentNavigationCallback: (value?: unknown) => void;
|
_newDocumentNavigationCallback: (value?: unknown) => void;
|
||||||
_sameDocumentNavigationPromise: Promise<unknown>;
|
_sameDocumentNavigationPromise: Promise<unknown>;
|
||||||
|
|
@ -689,7 +303,8 @@ class NextNavigationWatchdog {
|
||||||
_timeoutPromise: Promise<unknown>;
|
_timeoutPromise: Promise<unknown>;
|
||||||
_timeoutId: NodeJS.Timer;
|
_timeoutId: NodeJS.Timer;
|
||||||
|
|
||||||
constructor(frame, timeout) {
|
constructor(frameManager: FrameManager, frame: Frame, timeout) {
|
||||||
|
this._frameManager = frameManager;
|
||||||
this._frame = frame;
|
this._frame = frame;
|
||||||
this._newDocumentNavigationPromise = new Promise(fulfill => {
|
this._newDocumentNavigationPromise = new Promise(fulfill => {
|
||||||
this._newDocumentNavigationCallback = fulfill;
|
this._newDocumentNavigationCallback = fulfill;
|
||||||
|
|
@ -700,10 +315,10 @@ class NextNavigationWatchdog {
|
||||||
/** @type {?Request} */
|
/** @type {?Request} */
|
||||||
this._navigationRequest = null;
|
this._navigationRequest = null;
|
||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(frame._frameManager._page, Events.Page.Load, event => this._newDocumentNavigationCallback()),
|
helper.addEventListener(frameManager._page, Events.Page.Load, event => this._newDocumentNavigationCallback()),
|
||||||
helper.addEventListener(frame._frameManager, FrameManagerEvents.FrameNavigatedWithinDocument, frame => this._onSameDocumentNavigation(frame)),
|
helper.addEventListener(frameManager, FrameManagerEvents.FrameNavigatedWithinDocument, frame => this._onSameDocumentNavigation(frame)),
|
||||||
helper.addEventListener(frame._frameManager, FrameManagerEvents.TargetSwappedOnNavigation, event => this._onTargetReconnected()),
|
helper.addEventListener(frameManager, FrameManagerEvents.TargetSwappedOnNavigation, event => this._onTargetReconnected()),
|
||||||
helper.addEventListener(frame._frameManager.networkManager(), NetworkManagerEvents.Request, this._onRequest.bind(this)),
|
helper.addEventListener(frameManager.networkManager(), NetworkManagerEvents.Request, this._onRequest.bind(this)),
|
||||||
];
|
];
|
||||||
const timeoutError = new TimeoutError('Navigation Timeout Exceeded: ' + timeout + 'ms');
|
const timeoutError = new TimeoutError('Navigation Timeout Exceeded: ' + timeout + 'ms');
|
||||||
let timeoutCallback;
|
let timeoutCallback;
|
||||||
|
|
@ -731,7 +346,7 @@ class NextNavigationWatchdog {
|
||||||
const context = await this._frame.executionContext();
|
const context = await this._frame.executionContext();
|
||||||
const readyState = await context.evaluate(() => document.readyState);
|
const readyState = await context.evaluate(() => document.readyState);
|
||||||
switch (readyState) {
|
switch (readyState) {
|
||||||
case 'loaded':
|
case 'loading':
|
||||||
case 'interactive':
|
case 'interactive':
|
||||||
case 'complete':
|
case 'complete':
|
||||||
this._newDocumentNavigationCallback();
|
this._newDocumentNavigationCallback();
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ const writeFileAsync = helper.promisify(fs.writeFile);
|
||||||
export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject) {
|
export function createJSHandle(context: ExecutionContext, remoteObject: Protocol.Runtime.RemoteObject) {
|
||||||
const frame = context.frame();
|
const frame = context.frame();
|
||||||
if (remoteObject.subtype === 'node' && frame) {
|
if (remoteObject.subtype === 'node' && frame) {
|
||||||
const frameManager = frame._frameManager;
|
const frameManager = frame._delegate as FrameManager;
|
||||||
return new ElementHandle(context, context._session, remoteObject, frameManager.page(), frameManager);
|
return new ElementHandle(context, context._session, remoteObject, frameManager.page(), frameManager);
|
||||||
}
|
}
|
||||||
return new JSHandle(context, context._session, remoteObject);
|
return new JSHandle(context, context._session, remoteObject);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue