2019-11-18 18:18:28 -08:00
|
|
|
/**
|
|
|
|
|
* Copyright 2017 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.
|
|
|
|
|
*/
|
2019-11-26 07:53:48 -08:00
|
|
|
|
2019-11-18 18:18:28 -08:00
|
|
|
import * as EventEmitter from 'events';
|
|
|
|
|
import { TimeoutError } from '../Errors';
|
2019-11-27 13:31:13 -08:00
|
|
|
import * as frames from '../frames';
|
2019-11-18 18:18:28 -08:00
|
|
|
import { assert, debugError, helper, RegisteredListener } from '../helper';
|
2019-11-27 13:31:13 -08:00
|
|
|
import * as js from '../javascript';
|
2019-11-27 16:02:31 -08:00
|
|
|
import * as dom from '../dom';
|
2019-11-27 16:03:51 -08:00
|
|
|
import * as network from '../network';
|
2019-11-18 18:18:28 -08:00
|
|
|
import { TargetSession } from './Connection';
|
2019-11-27 13:31:13 -08:00
|
|
|
import { Events } from './events';
|
2019-12-09 15:45:32 -08:00
|
|
|
import { Events as CommonEvents } from '../events';
|
2019-11-27 16:03:51 -08:00
|
|
|
import { ExecutionContextDelegate } from './ExecutionContext';
|
|
|
|
|
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
2019-12-09 15:45:32 -08:00
|
|
|
import { Page, PageDelegate } from '../page';
|
2019-11-18 18:18:28 -08:00
|
|
|
import { Protocol } from './protocol';
|
2019-11-28 12:50:52 -08:00
|
|
|
import { DOMWorldDelegate } from './JSHandle';
|
2019-12-09 09:48:54 -08:00
|
|
|
import * as dialog from '../dialog';
|
2019-12-09 15:45:32 -08:00
|
|
|
import { Browser, BrowserContext } from './Browser';
|
|
|
|
|
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
|
|
|
|
import { WKScreenshotDelegate } from './Screenshotter';
|
|
|
|
|
import * as input from '../input';
|
|
|
|
|
import * as types from '../types';
|
2019-11-18 18:18:28 -08:00
|
|
|
|
|
|
|
|
export const FrameManagerEvents = {
|
|
|
|
|
FrameNavigatedWithinDocument: Symbol('FrameNavigatedWithinDocument'),
|
|
|
|
|
TargetSwappedOnNavigation: Symbol('TargetSwappedOnNavigation'),
|
|
|
|
|
FrameAttached: Symbol('FrameAttached'),
|
|
|
|
|
FrameDetached: Symbol('FrameDetached'),
|
|
|
|
|
FrameNavigated: Symbol('FrameNavigated'),
|
|
|
|
|
};
|
|
|
|
|
|
2019-11-27 12:38:26 -08:00
|
|
|
const frameDataSymbol = Symbol('frameData');
|
|
|
|
|
type FrameData = {
|
|
|
|
|
id: string,
|
|
|
|
|
};
|
|
|
|
|
|
2019-12-09 15:45:32 -08:00
|
|
|
export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate {
|
|
|
|
|
readonly rawMouse: RawMouseImpl;
|
|
|
|
|
readonly rawKeyboard: RawKeyboardImpl;
|
|
|
|
|
readonly screenshotterDelegate: WKScreenshotDelegate;
|
2019-11-18 18:18:28 -08:00
|
|
|
_session: TargetSession;
|
2019-12-09 15:45:32 -08:00
|
|
|
_page: Page<Browser, BrowserContext>;
|
2019-11-18 18:18:28 -08:00
|
|
|
_networkManager: NetworkManager;
|
2019-11-27 16:03:51 -08:00
|
|
|
_frames: Map<string, frames.Frame>;
|
|
|
|
|
_contextIdToContext: Map<number, js.ExecutionContext>;
|
2019-11-18 18:18:28 -08:00
|
|
|
_isolatedWorlds: Set<string>;
|
2019-12-09 10:16:30 -08:00
|
|
|
_sessionListeners: RegisteredListener[] = [];
|
2019-11-27 16:03:51 -08:00
|
|
|
_mainFrame: frames.Frame;
|
2019-12-09 15:45:32 -08:00
|
|
|
private _bootstrapScripts: string[] = [];
|
2019-11-22 14:46:34 -08:00
|
|
|
|
2019-12-09 15:45:32 -08:00
|
|
|
constructor(browserContext: BrowserContext) {
|
2019-11-18 18:18:28 -08:00
|
|
|
super();
|
2019-12-09 15:45:32 -08:00
|
|
|
this.rawKeyboard = new RawKeyboardImpl();
|
|
|
|
|
this.rawMouse = new RawMouseImpl();
|
|
|
|
|
this.screenshotterDelegate = new WKScreenshotDelegate();
|
2019-12-09 10:16:30 -08:00
|
|
|
this._networkManager = new NetworkManager(this);
|
2019-11-18 18:18:28 -08:00
|
|
|
this._frames = new Map();
|
|
|
|
|
this._contextIdToContext = new Map();
|
|
|
|
|
this._isolatedWorlds = new Set();
|
2019-12-09 15:45:32 -08:00
|
|
|
this._page = new Page(this, browserContext);
|
|
|
|
|
this._networkManager.on(NetworkManagerEvents.Request, event => this._page.emit(CommonEvents.Page.Request, event));
|
|
|
|
|
this._networkManager.on(NetworkManagerEvents.Response, event => this._page.emit(CommonEvents.Page.Response, event));
|
|
|
|
|
this._networkManager.on(NetworkManagerEvents.RequestFailed, event => this._page.emit(CommonEvents.Page.RequestFailed, event));
|
|
|
|
|
this._networkManager.on(NetworkManagerEvents.RequestFinished, event => this._page.emit(CommonEvents.Page.RequestFinished, event));
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
|
2019-12-09 10:16:30 -08:00
|
|
|
async initialize(session: TargetSession) {
|
|
|
|
|
helper.removeEventListeners(this._sessionListeners);
|
|
|
|
|
this.disconnectFromTarget();
|
|
|
|
|
this._session = session;
|
2019-12-09 15:45:32 -08:00
|
|
|
this.rawKeyboard.setSession(session);
|
|
|
|
|
this.rawMouse.setSession(session);
|
|
|
|
|
this.screenshotterDelegate.initialize(session);
|
2019-12-09 10:16:30 -08:00
|
|
|
this._addSessionListeners();
|
|
|
|
|
this.emit(FrameManagerEvents.TargetSwappedOnNavigation);
|
2019-11-18 18:18:28 -08:00
|
|
|
const [,{frameTree}] = await Promise.all([
|
|
|
|
|
// Page agent must be enabled before Runtime.
|
|
|
|
|
this._session.send('Page.enable'),
|
|
|
|
|
this._session.send('Page.getResourceTree'),
|
|
|
|
|
]);
|
|
|
|
|
this._handleFrameTree(frameTree);
|
|
|
|
|
await Promise.all([
|
|
|
|
|
this._session.send('Runtime.enable'),
|
2019-12-09 09:48:54 -08:00
|
|
|
this._session.send('Console.enable'),
|
|
|
|
|
this._session.send('Dialog.enable'),
|
|
|
|
|
this._session.send('Page.setInterceptFileChooserDialog', { enabled: true }),
|
2019-12-09 10:16:30 -08:00
|
|
|
this._networkManager.initialize(session),
|
2019-11-18 18:18:28 -08:00
|
|
|
]);
|
2019-12-09 15:45:32 -08:00
|
|
|
if (this._page._state.userAgent !== null)
|
|
|
|
|
await this._session.send('Page.overrideUserAgent', { value: this._page._state.userAgent });
|
|
|
|
|
if (this._page._state.mediaType !== null)
|
|
|
|
|
await this._session.send('Page.setEmulatedMedia', { media: this._page._state.mediaType || '' });
|
|
|
|
|
if (this._page._state.javascriptEnabled !== null)
|
|
|
|
|
await this._session.send('Emulation.setJavaScriptEnabled', { enabled: this._page._state.javascriptEnabled });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
didClose() {
|
|
|
|
|
helper.removeEventListeners(this._sessionListeners);
|
|
|
|
|
this.disconnectFromTarget();
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_addSessionListeners() {
|
|
|
|
|
this._sessionListeners = [
|
|
|
|
|
helper.addEventListener(this._session, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.frameDetached', event => this._onFrameDetached(event.frameId)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)),
|
|
|
|
|
helper.addEventListener(this._session, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
|
2019-12-09 09:48:54 -08:00
|
|
|
helper.addEventListener(this._session, 'Page.loadEventFired', event => this._page.emit(Events.Page.Load)),
|
|
|
|
|
helper.addEventListener(this._session, 'Console.messageAdded', event => this._onConsoleMessage(event)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.domContentEventFired', event => this._page.emit(Events.Page.DOMContentLoaded)),
|
|
|
|
|
helper.addEventListener(this._session, 'Dialog.javascriptDialogOpening', event => this._onDialog(event)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event))
|
2019-11-18 18:18:28 -08:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
disconnectFromTarget() {
|
2019-11-27 12:38:26 -08:00
|
|
|
for (const context of this._contextIdToContext.values()) {
|
2019-11-27 12:39:53 -08:00
|
|
|
(context._delegate as ExecutionContextDelegate)._dispose();
|
2019-11-27 12:38:26 -08:00
|
|
|
context.frame()._contextDestroyed(context);
|
|
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
// this._mainFrame = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
networkManager(): NetworkManager {
|
|
|
|
|
return this._networkManager;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onFrameStoppedLoading(frameId: string) {
|
|
|
|
|
const frame = this._frames.get(frameId);
|
|
|
|
|
if (!frame)
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_handleFrameTree(frameTree: Protocol.Page.FrameResourceTree) {
|
|
|
|
|
if (frameTree.frame.parentId)
|
|
|
|
|
this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId);
|
|
|
|
|
this._onFrameNavigated(frameTree.frame);
|
|
|
|
|
if (!frameTree.childFrames)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (const child of frameTree.childFrames)
|
|
|
|
|
this._handleFrameTree(child);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-09 15:45:32 -08:00
|
|
|
page(): Page<Browser, BrowserContext> {
|
2019-11-18 18:18:28 -08:00
|
|
|
return this._page;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
mainFrame(): frames.Frame {
|
2019-11-18 18:18:28 -08:00
|
|
|
return this._mainFrame;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
frames(): Array<frames.Frame> {
|
2019-11-18 18:18:28 -08:00
|
|
|
return Array.from(this._frames.values());
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
frame(frameId: string): frames.Frame | null {
|
2019-11-18 18:18:28 -08:00
|
|
|
return this._frames.get(frameId) || null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
_frameData(frame: frames.Frame): FrameData {
|
2019-11-27 12:38:26 -08:00
|
|
|
return (frame as any)[frameDataSymbol];
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-18 18:18:28 -08:00
|
|
|
_onFrameAttached(frameId: string, parentFrameId: string | null) {
|
|
|
|
|
if (this._frames.has(frameId))
|
|
|
|
|
return;
|
|
|
|
|
assert(parentFrameId);
|
|
|
|
|
const parentFrame = this._frames.get(parentFrameId);
|
2019-12-09 15:45:32 -08:00
|
|
|
const frame = new frames.Frame(this, this._page._timeoutSettings, parentFrame);
|
2019-11-27 12:38:26 -08:00
|
|
|
const data: FrameData = {
|
|
|
|
|
id: frameId,
|
|
|
|
|
};
|
|
|
|
|
frame[frameDataSymbol] = data;
|
|
|
|
|
this._frames.set(frameId, frame);
|
2019-11-18 18:18:28 -08:00
|
|
|
this.emit(FrameManagerEvents.FrameAttached, frame);
|
2019-12-09 15:45:32 -08:00
|
|
|
this._page.emit(CommonEvents.Page.FrameAttached, frame);
|
2019-11-18 18:18:28 -08:00
|
|
|
return frame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onFrameNavigated(framePayload: Protocol.Page.Frame) {
|
|
|
|
|
const isMainFrame = !framePayload.parentId;
|
|
|
|
|
let frame = isMainFrame ? this._mainFrame : this._frames.get(framePayload.id);
|
|
|
|
|
|
|
|
|
|
// Detach all child frames first.
|
|
|
|
|
if (frame) {
|
|
|
|
|
for (const child of frame.childFrames())
|
|
|
|
|
this._removeFramesRecursively(child);
|
|
|
|
|
if (isMainFrame) {
|
|
|
|
|
// Update frame id to retain frame identity on cross-process navigation.
|
2019-11-27 12:38:26 -08:00
|
|
|
const data = this._frameData(frame);
|
|
|
|
|
this._frames.delete(data.id);
|
|
|
|
|
data.id = framePayload.id;
|
2019-11-27 13:31:13 -08:00
|
|
|
this._frames.set(data.id, frame);
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
} else if (isMainFrame) {
|
|
|
|
|
// Initial frame navigation.
|
2019-12-09 15:45:32 -08:00
|
|
|
frame = new frames.Frame(this, this._page._timeoutSettings, null);
|
2019-11-27 12:38:26 -08:00
|
|
|
const data: FrameData = {
|
|
|
|
|
id: framePayload.id,
|
|
|
|
|
};
|
|
|
|
|
frame[frameDataSymbol] = data;
|
2019-11-18 18:18:28 -08:00
|
|
|
this._frames.set(framePayload.id, frame);
|
|
|
|
|
} else {
|
|
|
|
|
// FIXME(WebKit): there is no Page.frameAttached event in WK.
|
|
|
|
|
frame = this._onFrameAttached(framePayload.id, framePayload.parentId);
|
|
|
|
|
}
|
|
|
|
|
// Update or create main frame.
|
|
|
|
|
if (isMainFrame)
|
|
|
|
|
this._mainFrame = frame;
|
|
|
|
|
|
|
|
|
|
// Update frame payload.
|
2019-11-27 12:38:26 -08:00
|
|
|
frame._navigated(framePayload.url, framePayload.name);
|
|
|
|
|
for (const context of this._contextIdToContext.values()) {
|
|
|
|
|
if (context.frame() === frame) {
|
2019-11-27 13:31:13 -08:00
|
|
|
const delegate = context._delegate as ExecutionContextDelegate;
|
|
|
|
|
delegate._dispose();
|
|
|
|
|
this._contextIdToContext.delete(delegate._contextId);
|
2019-11-27 12:38:26 -08:00
|
|
|
frame._contextDestroyed(context);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
|
|
|
|
|
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
2019-12-09 15:45:32 -08:00
|
|
|
this._page.emit(CommonEvents.Page.FrameNavigated, frame);
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onFrameNavigatedWithinDocument(frameId: string, url: string) {
|
|
|
|
|
const frame = this._frames.get(frameId);
|
|
|
|
|
if (!frame)
|
|
|
|
|
return;
|
2019-11-27 12:38:26 -08:00
|
|
|
frame._navigated(url, frame.name());
|
2019-11-18 18:18:28 -08:00
|
|
|
this.emit(FrameManagerEvents.FrameNavigatedWithinDocument, frame);
|
|
|
|
|
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
2019-12-09 15:45:32 -08:00
|
|
|
this._page.emit(CommonEvents.Page.FrameNavigated, frame);
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onFrameDetached(frameId: string) {
|
|
|
|
|
const frame = this._frames.get(frameId);
|
|
|
|
|
if (frame)
|
|
|
|
|
this._removeFramesRecursively(frame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onExecutionContextCreated(contextPayload : Protocol.Runtime.ExecutionContextDescription) {
|
2019-11-27 13:31:13 -08:00
|
|
|
if (this._contextIdToContext.has(contextPayload.id))
|
|
|
|
|
return;
|
2019-11-18 18:18:28 -08:00
|
|
|
if (!contextPayload.isPageContext)
|
|
|
|
|
return;
|
|
|
|
|
const frameId = contextPayload.frameId;
|
|
|
|
|
// If the frame was attached manually there is no navigation event.
|
|
|
|
|
// FIXME: support frameAttached event in WebKit protocol.
|
|
|
|
|
const frame = this._frames.get(frameId) || null;
|
|
|
|
|
if (!frame)
|
|
|
|
|
return;
|
2019-11-28 12:50:52 -08:00
|
|
|
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, contextPayload));
|
2019-11-27 12:38:26 -08:00
|
|
|
if (frame) {
|
2019-11-28 12:50:52 -08:00
|
|
|
context._domWorld = new dom.DOMWorld(context, new DOMWorldDelegate(this, frame));
|
2019-11-27 12:38:26 -08:00
|
|
|
frame._contextCreated('main', context);
|
|
|
|
|
frame._contextCreated('utility', context);
|
|
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
this._contextIdToContext.set(contextPayload.id, context);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
executionContextById(contextId: number): js.ExecutionContext {
|
2019-11-18 18:18:28 -08:00
|
|
|
const context = this._contextIdToContext.get(contextId);
|
|
|
|
|
assert(context, 'INTERNAL ERROR: missing context with id = ' + contextId);
|
|
|
|
|
return context;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
_removeFramesRecursively(frame: frames.Frame) {
|
2019-11-18 18:18:28 -08:00
|
|
|
for (const child of frame.childFrames())
|
|
|
|
|
this._removeFramesRecursively(child);
|
|
|
|
|
frame._detach();
|
2019-11-27 12:38:26 -08:00
|
|
|
this._frames.delete(this._frameData(frame).id);
|
2019-11-18 18:18:28 -08:00
|
|
|
this.emit(FrameManagerEvents.FrameDetached, frame);
|
2019-12-09 15:45:32 -08:00
|
|
|
this._page.emit(CommonEvents.Page.FrameDetached, frame);
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
async navigateFrame(frame: frames.Frame, url: string, options: { referer?: string; timeout?: number; waitUntil?: string | Array<string>; } | undefined = {}): Promise<network.Response | null> {
|
2019-11-18 18:18:28 -08:00
|
|
|
const {
|
2019-12-09 15:45:32 -08:00
|
|
|
timeout = this._page._timeoutSettings.navigationTimeout(),
|
2019-11-18 18:18:28 -08:00
|
|
|
} = options;
|
2019-11-27 12:38:26 -08:00
|
|
|
const watchDog = new NextNavigationWatchdog(this, frame, timeout);
|
2019-11-18 18:18:28 -08:00
|
|
|
await this._session.send('Page.navigate', {url});
|
|
|
|
|
return watchDog.waitForNavigation();
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
async waitForFrameNavigation(frame: frames.Frame, options?: frames.NavigateOptions): Promise<network.Response | null> {
|
2019-11-18 18:18:28 -08:00
|
|
|
// FIXME: this method only works for main frames.
|
2019-11-27 12:38:26 -08:00
|
|
|
const watchDog = new NextNavigationWatchdog(this, frame, 10000);
|
2019-11-18 18:18:28 -08:00
|
|
|
return watchDog.waitForNavigation();
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
async setFrameContent(frame: frames.Frame, html: string, options: { timeout?: number; waitUntil?: string | Array<string>; } | undefined = {}) {
|
2019-11-18 18:18:28 -08:00
|
|
|
// We rely upon the fact that document.open() will trigger Page.loadEventFired.
|
2019-11-27 12:38:26 -08:00
|
|
|
const watchDog = new NextNavigationWatchdog(this, frame, 1000);
|
|
|
|
|
await frame.evaluate(html => {
|
2019-11-18 18:18:28 -08:00
|
|
|
document.open();
|
|
|
|
|
document.write(html);
|
|
|
|
|
document.close();
|
|
|
|
|
}, html);
|
|
|
|
|
await watchDog.waitForNavigation();
|
|
|
|
|
}
|
2019-12-09 09:48:54 -08:00
|
|
|
|
|
|
|
|
async _onConsoleMessage(event: Protocol.Console.messageAddedPayload) {
|
|
|
|
|
const { type, level, text, parameters, url, line: lineNumber, column: columnNumber } = event.message;
|
|
|
|
|
let derivedType: string = type;
|
|
|
|
|
if (type === 'log')
|
|
|
|
|
derivedType = level;
|
|
|
|
|
else if (type === 'timing')
|
|
|
|
|
derivedType = 'timeEnd';
|
|
|
|
|
const mainFrameContext = await this.mainFrame().executionContext();
|
|
|
|
|
const handles = (parameters || []).map(p => {
|
|
|
|
|
let context: js.ExecutionContext | null = null;
|
|
|
|
|
if (p.objectId) {
|
|
|
|
|
const objectId = JSON.parse(p.objectId);
|
|
|
|
|
context = this._contextIdToContext.get(objectId.injectedScriptId);
|
|
|
|
|
} else {
|
|
|
|
|
context = mainFrameContext;
|
|
|
|
|
}
|
|
|
|
|
return context._createHandle(p);
|
|
|
|
|
});
|
|
|
|
|
this._page._addConsoleMessage(derivedType, handles, { url, lineNumber, columnNumber }, handles.length ? undefined : text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDialog(event: Protocol.Dialog.javascriptDialogOpeningPayload) {
|
|
|
|
|
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
|
|
|
|
event.type as dialog.DialogType,
|
|
|
|
|
event.message,
|
|
|
|
|
async (accept: boolean, promptText?: string) => {
|
|
|
|
|
await this._session.send('Dialog.handleJavaScriptDialog', { accept, promptText });
|
|
|
|
|
},
|
|
|
|
|
event.defaultPrompt));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async _onFileChooserOpened(event: {frameId: Protocol.Network.FrameId, element: Protocol.Runtime.RemoteObject}) {
|
|
|
|
|
const context = await this.frame(event.frameId)._utilityContext();
|
|
|
|
|
const handle = context._createHandle(event.element).asElement()!;
|
|
|
|
|
this._page._onFileChooserOpened(handle);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-09 15:45:32 -08:00
|
|
|
setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void> {
|
|
|
|
|
return this._networkManager.setExtraHTTPHeaders(extraHTTPHeaders);
|
2019-12-09 09:48:54 -08:00
|
|
|
}
|
|
|
|
|
|
2019-12-09 15:45:32 -08:00
|
|
|
async setUserAgent(userAgent: string): Promise<void> {
|
|
|
|
|
await this._session.send('Page.overrideUserAgent', { value: userAgent });
|
2019-12-09 09:48:54 -08:00
|
|
|
}
|
|
|
|
|
|
2019-12-09 15:45:32 -08:00
|
|
|
async setJavaScriptEnabled(enabled: boolean): Promise<void> {
|
2019-12-09 09:48:54 -08:00
|
|
|
await this._session.send('Emulation.setJavaScriptEnabled', { enabled });
|
|
|
|
|
}
|
2019-12-09 15:45:32 -08:00
|
|
|
|
|
|
|
|
setBypassCSP(enabled: boolean): Promise<void> {
|
|
|
|
|
throw new Error('Not implemented');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async setViewport(viewport: types.Viewport): Promise<void> {
|
|
|
|
|
if (viewport.isMobile || viewport.isLandscape || viewport.hasTouch)
|
|
|
|
|
throw new Error('Not implemented');
|
|
|
|
|
const width = viewport.width;
|
|
|
|
|
const height = viewport.height;
|
|
|
|
|
await this._session.send('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: viewport.deviceScaleFactor || 1 });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise<void> {
|
|
|
|
|
if (mediaColorScheme !== null)
|
|
|
|
|
throw new Error('Not implemented');
|
|
|
|
|
await this._session.send('Page.setEmulatedMedia', { media: mediaType || '' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setCacheEnabled(enabled: boolean): Promise<void> {
|
|
|
|
|
return this._networkManager.setCacheEnabled(enabled);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async reload(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
|
|
|
|
const [response] = await Promise.all([
|
|
|
|
|
this._page.waitForNavigation(options),
|
|
|
|
|
this._session.send('Page.reload')
|
|
|
|
|
]);
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async _go<T extends keyof Protocol.CommandParameters>(command: T, options?: frames.NavigateOptions): Promise<network.Response | null> {
|
|
|
|
|
const [response] = await Promise.all([
|
|
|
|
|
this._page.waitForNavigation(options),
|
|
|
|
|
this._session.send(command).then(() => null),
|
|
|
|
|
]).catch(error => {
|
|
|
|
|
if (error instanceof Error && error.message.includes(`Protocol error (${command}): Failed to go`))
|
|
|
|
|
return [null];
|
|
|
|
|
throw error;
|
|
|
|
|
});
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
goBack(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
|
|
|
|
return this._go('Page.goBack', options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
goForward(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
|
|
|
|
return this._go('Page.goForward', options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exposeBinding(name: string, bindingFunction: string): Promise<void> {
|
|
|
|
|
throw new Error('Not implemented');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async evaluateOnNewDocument(script: string): Promise<void> {
|
|
|
|
|
this._bootstrapScripts.push(script);
|
|
|
|
|
const source = this._bootstrapScripts.join(';');
|
|
|
|
|
// TODO(yurys): support process swap on navigation.
|
|
|
|
|
await this._session.send('Page.setBootstrapScript', { source });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async closePage(runBeforeUnload: boolean): Promise<void> {
|
|
|
|
|
if (runBeforeUnload)
|
|
|
|
|
throw new Error('Not implemented');
|
|
|
|
|
this._page.browser()._closePage(this._page);
|
|
|
|
|
}
|
2019-11-18 18:18:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @internal
|
|
|
|
|
*/
|
|
|
|
|
class NextNavigationWatchdog {
|
2019-11-27 12:38:26 -08:00
|
|
|
_frameManager: FrameManager;
|
2019-11-27 16:03:51 -08:00
|
|
|
_frame: frames.Frame;
|
2019-11-18 18:18:28 -08:00
|
|
|
_newDocumentNavigationPromise: Promise<unknown>;
|
|
|
|
|
_newDocumentNavigationCallback: (value?: unknown) => void;
|
|
|
|
|
_sameDocumentNavigationPromise: Promise<unknown>;
|
|
|
|
|
_sameDocumentNavigationCallback: (value?: unknown) => void;
|
|
|
|
|
_navigationRequest: any;
|
|
|
|
|
_eventListeners: RegisteredListener[];
|
|
|
|
|
_timeoutPromise: Promise<unknown>;
|
|
|
|
|
_timeoutId: NodeJS.Timer;
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
constructor(frameManager: FrameManager, frame: frames.Frame, timeout) {
|
2019-11-27 12:38:26 -08:00
|
|
|
this._frameManager = frameManager;
|
2019-11-18 18:18:28 -08:00
|
|
|
this._frame = frame;
|
|
|
|
|
this._newDocumentNavigationPromise = new Promise(fulfill => {
|
|
|
|
|
this._newDocumentNavigationCallback = fulfill;
|
|
|
|
|
});
|
|
|
|
|
this._sameDocumentNavigationPromise = new Promise(fulfill => {
|
|
|
|
|
this._sameDocumentNavigationCallback = fulfill;
|
|
|
|
|
});
|
|
|
|
|
/** @type {?Request} */
|
|
|
|
|
this._navigationRequest = null;
|
|
|
|
|
this._eventListeners = [
|
2019-11-27 12:38:26 -08:00
|
|
|
helper.addEventListener(frameManager._page, Events.Page.Load, event => this._newDocumentNavigationCallback()),
|
|
|
|
|
helper.addEventListener(frameManager, FrameManagerEvents.FrameNavigatedWithinDocument, frame => this._onSameDocumentNavigation(frame)),
|
|
|
|
|
helper.addEventListener(frameManager, FrameManagerEvents.TargetSwappedOnNavigation, event => this._onTargetReconnected()),
|
|
|
|
|
helper.addEventListener(frameManager.networkManager(), NetworkManagerEvents.Request, this._onRequest.bind(this)),
|
2019-11-18 18:18:28 -08:00
|
|
|
];
|
|
|
|
|
const timeoutError = new TimeoutError('Navigation Timeout Exceeded: ' + timeout + 'ms');
|
|
|
|
|
let timeoutCallback;
|
|
|
|
|
this._timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
|
|
|
|
this._timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async waitForNavigation() {
|
|
|
|
|
const error = await Promise.race([
|
|
|
|
|
this._timeoutPromise,
|
|
|
|
|
this._newDocumentNavigationPromise,
|
|
|
|
|
this._sameDocumentNavigationPromise
|
|
|
|
|
]);
|
|
|
|
|
// TODO: handle exceptions
|
|
|
|
|
this.dispose();
|
|
|
|
|
if (error)
|
|
|
|
|
throw error;
|
|
|
|
|
return this.navigationResponse();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async _onTargetReconnected() {
|
|
|
|
|
// In case web process change we migh have missed load event. Check current ready
|
|
|
|
|
// state to mitigate that.
|
|
|
|
|
try {
|
|
|
|
|
const context = await this._frame.executionContext();
|
|
|
|
|
const readyState = await context.evaluate(() => document.readyState);
|
|
|
|
|
switch (readyState) {
|
2019-11-27 12:38:26 -08:00
|
|
|
case 'loading':
|
2019-11-18 18:18:28 -08:00
|
|
|
case 'interactive':
|
|
|
|
|
case 'complete':
|
|
|
|
|
this._newDocumentNavigationCallback();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
debugError('_onTargetReconnected ' + e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onSameDocumentNavigation(frame) {
|
|
|
|
|
if (this._frame === frame)
|
|
|
|
|
this._sameDocumentNavigationCallback();
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
_onRequest(request: network.Request) {
|
2019-11-18 18:18:28 -08:00
|
|
|
if (request.frame() !== this._frame || !request.isNavigationRequest())
|
|
|
|
|
return;
|
|
|
|
|
this._navigationRequest = request;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-27 16:03:51 -08:00
|
|
|
navigationResponse(): network.Response | null {
|
2019-11-18 18:18:28 -08:00
|
|
|
return this._navigationRequest ? this._navigationRequest.response() : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dispose() {
|
|
|
|
|
helper.removeEventListeners(this._eventListeners);
|
|
|
|
|
clearTimeout(this._timeoutId);
|
|
|
|
|
}
|
|
|
|
|
}
|