2019-11-26 01:42:37 +01:00
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
|
|
|
|
|
2019-11-27 22:31:13 +01:00
|
|
|
import { EventEmitter } from 'events';
|
|
|
|
|
import { TimeoutError } from '../Errors';
|
2019-11-27 21:38:26 +01:00
|
|
|
import * as frames from '../frames';
|
2019-12-07 01:34:27 +01:00
|
|
|
import { assert, helper, RegisteredListener, debugError } from '../helper';
|
2019-11-27 21:39:53 +01:00
|
|
|
import * as js from '../javascript';
|
2019-11-28 01:02:31 +01:00
|
|
|
import * as dom from '../dom';
|
2019-11-27 22:31:13 +01:00
|
|
|
import { JugglerSession } from './Connection';
|
2019-11-28 01:03:51 +01:00
|
|
|
import { ExecutionContextDelegate } from './ExecutionContext';
|
2019-11-27 22:31:13 +01:00
|
|
|
import { NavigationWatchdog, NextNavigationWatchdog } from './NavigationWatchdog';
|
2019-12-09 23:53:17 +01:00
|
|
|
import { Page, PageDelegate } from '../page';
|
|
|
|
|
import { NetworkManager, NetworkManagerEvents } from './NetworkManager';
|
2019-11-28 21:50:52 +01:00
|
|
|
import { DOMWorldDelegate } from './JSHandle';
|
2019-12-07 01:34:27 +01:00
|
|
|
import { Events } from './events';
|
2019-12-09 23:53:17 +01:00
|
|
|
import { Events as CommonEvents } from '../events';
|
2019-12-07 01:34:27 +01:00
|
|
|
import * as dialog from '../dialog';
|
|
|
|
|
import { Protocol } from './protocol';
|
2019-12-09 23:53:17 +01:00
|
|
|
import * as input from '../input';
|
|
|
|
|
import { RawMouseImpl, RawKeyboardImpl } from './Input';
|
|
|
|
|
import { FFScreenshotDelegate } from './Screenshotter';
|
|
|
|
|
import { Browser, BrowserContext } from './Browser';
|
|
|
|
|
import { Interception } from './features/interception';
|
|
|
|
|
import { Accessibility } from './features/accessibility';
|
|
|
|
|
import * as network from '../network';
|
|
|
|
|
import * as types from '../types';
|
2019-11-26 17:57:53 +01:00
|
|
|
|
2019-11-19 03:18:28 +01:00
|
|
|
export const FrameManagerEvents = {
|
|
|
|
|
FrameNavigated: Symbol('FrameManagerEvents.FrameNavigated'),
|
|
|
|
|
FrameAttached: Symbol('FrameManagerEvents.FrameAttached'),
|
|
|
|
|
FrameDetached: Symbol('FrameManagerEvents.FrameDetached'),
|
|
|
|
|
Load: Symbol('FrameManagerEvents.Load'),
|
|
|
|
|
DOMContentLoaded: Symbol('FrameManagerEvents.DOMContentLoaded'),
|
|
|
|
|
};
|
2019-11-27 21:38:26 +01:00
|
|
|
|
|
|
|
|
const frameDataSymbol = Symbol('frameData');
|
|
|
|
|
type FrameData = {
|
|
|
|
|
frameId: string,
|
|
|
|
|
lastCommittedNavigationId: string,
|
|
|
|
|
};
|
|
|
|
|
|
2019-12-09 23:53:17 +01:00
|
|
|
export class FrameManager extends EventEmitter implements frames.FrameDelegate, PageDelegate {
|
|
|
|
|
readonly rawMouse: RawMouseImpl;
|
|
|
|
|
readonly rawKeyboard: RawKeyboardImpl;
|
|
|
|
|
readonly screenshotterDelegate: FFScreenshotDelegate;
|
|
|
|
|
readonly _session: JugglerSession;
|
|
|
|
|
readonly _page: Page<Browser, BrowserContext>;
|
|
|
|
|
private readonly _networkManager: NetworkManager;
|
|
|
|
|
private _mainFrame: frames.Frame;
|
|
|
|
|
private readonly _frames: Map<string, frames.Frame>;
|
|
|
|
|
private readonly _contextIdToContext: Map<string, js.ExecutionContext>;
|
|
|
|
|
private _eventListeners: RegisteredListener[];
|
|
|
|
|
|
|
|
|
|
constructor(session: JugglerSession, browserContext: BrowserContext) {
|
2019-11-19 03:18:28 +01:00
|
|
|
super();
|
|
|
|
|
this._session = session;
|
2019-12-09 23:53:17 +01:00
|
|
|
this.rawKeyboard = new RawKeyboardImpl(session);
|
|
|
|
|
this.rawMouse = new RawMouseImpl(session);
|
|
|
|
|
this.screenshotterDelegate = new FFScreenshotDelegate(session, this);
|
|
|
|
|
this._networkManager = new NetworkManager(session, this);
|
2019-11-19 03:18:28 +01:00
|
|
|
this._mainFrame = null;
|
|
|
|
|
this._frames = new Map();
|
|
|
|
|
this._contextIdToContext = new Map();
|
|
|
|
|
this._eventListeners = [
|
|
|
|
|
helper.addEventListener(this._session, 'Page.eventFired', this._onEventFired.bind(this)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.frameAttached', this._onFrameAttached.bind(this)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.frameDetached', this._onFrameDetached.bind(this)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.navigationCommitted', this._onNavigationCommitted.bind(this)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)),
|
|
|
|
|
helper.addEventListener(this._session, 'Runtime.executionContextCreated', this._onExecutionContextCreated.bind(this)),
|
|
|
|
|
helper.addEventListener(this._session, 'Runtime.executionContextDestroyed', this._onExecutionContextDestroyed.bind(this)),
|
2019-12-07 01:34:27 +01:00
|
|
|
helper.addEventListener(this._session, 'Page.uncaughtError', this._onUncaughtError.bind(this)),
|
|
|
|
|
helper.addEventListener(this._session, 'Runtime.console', this._onConsole.bind(this)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.dialogOpened', this._onDialogOpened.bind(this)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.bindingCalled', this._onBindingCalled.bind(this)),
|
|
|
|
|
helper.addEventListener(this._session, 'Page.fileChooserOpened', this._onFileChooserOpened.bind(this)),
|
2019-12-09 23:53:17 +01:00
|
|
|
helper.addEventListener(this._networkManager, NetworkManagerEvents.Request, request => this._page.emit(CommonEvents.Page.Request, request)),
|
|
|
|
|
helper.addEventListener(this._networkManager, NetworkManagerEvents.Response, response => this._page.emit(CommonEvents.Page.Response, response)),
|
|
|
|
|
helper.addEventListener(this._networkManager, NetworkManagerEvents.RequestFinished, request => this._page.emit(CommonEvents.Page.RequestFinished, request)),
|
|
|
|
|
helper.addEventListener(this._networkManager, NetworkManagerEvents.RequestFailed, request => this._page.emit(CommonEvents.Page.RequestFailed, request)),
|
2019-11-19 03:18:28 +01:00
|
|
|
];
|
2019-12-09 23:53:17 +01:00
|
|
|
this._page = new Page(this, browserContext);
|
|
|
|
|
(this._page as any).interception = new Interception(this._networkManager);
|
|
|
|
|
(this._page as any).accessibility = new Accessibility(session);
|
2019-11-19 03:18:28 +01:00
|
|
|
}
|
|
|
|
|
|
2019-12-09 19:16:30 +01:00
|
|
|
async _initialize() {
|
|
|
|
|
await Promise.all([
|
|
|
|
|
this._session.send('Runtime.enable'),
|
|
|
|
|
this._session.send('Network.enable'),
|
|
|
|
|
this._session.send('Page.enable'),
|
|
|
|
|
this._session.send('Page.setInterceptFileChooserDialog', { enabled: true })
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-19 03:18:28 +01:00
|
|
|
executionContextById(executionContextId) {
|
|
|
|
|
return this._contextIdToContext.get(executionContextId) || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onExecutionContextCreated({executionContextId, auxData}) {
|
|
|
|
|
const frameId = auxData ? auxData.frameId : null;
|
|
|
|
|
const frame = this._frames.get(frameId) || null;
|
2019-11-28 21:50:52 +01:00
|
|
|
const context = new js.ExecutionContext(new ExecutionContextDelegate(this._session, executionContextId));
|
2019-11-27 21:38:26 +01:00
|
|
|
if (frame) {
|
2019-11-28 21:50:52 +01:00
|
|
|
context._domWorld = new dom.DOMWorld(context, new DOMWorldDelegate(this, frame));
|
2019-11-27 21:38:26 +01:00
|
|
|
frame._contextCreated('main', context);
|
|
|
|
|
frame._contextCreated('utility', context);
|
|
|
|
|
}
|
2019-11-19 03:18:28 +01:00
|
|
|
this._contextIdToContext.set(executionContextId, context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onExecutionContextDestroyed({executionContextId}) {
|
|
|
|
|
const context = this._contextIdToContext.get(executionContextId);
|
|
|
|
|
if (!context)
|
|
|
|
|
return;
|
|
|
|
|
this._contextIdToContext.delete(executionContextId);
|
2019-11-27 21:38:26 +01:00
|
|
|
if (context.frame())
|
|
|
|
|
context.frame()._contextDestroyed(context);
|
2019-11-19 03:18:28 +01:00
|
|
|
}
|
|
|
|
|
|
2019-11-28 01:03:51 +01:00
|
|
|
_frameData(frame: frames.Frame): FrameData {
|
2019-11-27 21:38:26 +01:00
|
|
|
return (frame as any)[frameDataSymbol];
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-28 01:03:51 +01:00
|
|
|
frame(frameId: string): frames.Frame {
|
2019-11-19 03:18:28 +01:00
|
|
|
return this._frames.get(frameId);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-28 01:03:51 +01:00
|
|
|
mainFrame(): frames.Frame {
|
2019-11-19 03:18:28 +01:00
|
|
|
return this._mainFrame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
frames() {
|
2019-11-28 01:03:51 +01:00
|
|
|
const frames: Array<frames.Frame> = [];
|
2019-11-19 03:18:28 +01:00
|
|
|
collect(this._mainFrame);
|
|
|
|
|
return frames;
|
|
|
|
|
|
2019-11-28 01:03:51 +01:00
|
|
|
function collect(frame: frames.Frame) {
|
2019-11-19 03:18:28 +01:00
|
|
|
frames.push(frame);
|
2019-11-27 21:38:26 +01:00
|
|
|
for (const subframe of frame.childFrames())
|
2019-11-19 03:18:28 +01:00
|
|
|
collect(subframe);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onNavigationCommitted(params) {
|
|
|
|
|
const frame = this._frames.get(params.frameId);
|
2019-11-27 21:38:26 +01:00
|
|
|
frame._navigated(params.url, params.name);
|
|
|
|
|
const data = this._frameData(frame);
|
|
|
|
|
data.lastCommittedNavigationId = params.navigationId;
|
2019-12-10 01:34:42 +01:00
|
|
|
frame._firedLifecycleEvents.clear();
|
2019-11-19 03:18:28 +01:00
|
|
|
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
2019-12-09 23:53:17 +01:00
|
|
|
this._page.emit(CommonEvents.Page.FrameNavigated, frame);
|
2019-11-19 03:18:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onSameDocumentNavigation(params) {
|
|
|
|
|
const frame = this._frames.get(params.frameId);
|
2019-11-27 21:38:26 +01:00
|
|
|
frame._navigated(params.url, frame.name());
|
2019-11-19 03:18:28 +01:00
|
|
|
this.emit(FrameManagerEvents.FrameNavigated, frame);
|
2019-12-09 23:53:17 +01:00
|
|
|
this._page.emit(CommonEvents.Page.FrameNavigated, frame);
|
2019-11-19 03:18:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onFrameAttached(params) {
|
|
|
|
|
const parentFrame = this._frames.get(params.parentFrameId) || null;
|
2019-12-09 23:53:17 +01:00
|
|
|
const frame = new frames.Frame(this, this._page._timeoutSettings, parentFrame);
|
2019-11-27 21:38:26 +01:00
|
|
|
const data: FrameData = {
|
|
|
|
|
frameId: params.frameId,
|
|
|
|
|
lastCommittedNavigationId: '',
|
|
|
|
|
};
|
|
|
|
|
frame[frameDataSymbol] = data;
|
|
|
|
|
if (!parentFrame) {
|
2019-11-19 03:18:28 +01:00
|
|
|
assert(!this._mainFrame, 'INTERNAL ERROR: re-attaching main frame!');
|
|
|
|
|
this._mainFrame = frame;
|
|
|
|
|
}
|
|
|
|
|
this._frames.set(params.frameId, frame);
|
|
|
|
|
this.emit(FrameManagerEvents.FrameAttached, frame);
|
2019-12-09 23:53:17 +01:00
|
|
|
this._page.emit(CommonEvents.Page.FrameAttached, frame);
|
2019-11-19 03:18:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onFrameDetached(params) {
|
|
|
|
|
const frame = this._frames.get(params.frameId);
|
|
|
|
|
this._frames.delete(params.frameId);
|
|
|
|
|
frame._detach();
|
|
|
|
|
this.emit(FrameManagerEvents.FrameDetached, frame);
|
2019-12-09 23:53:17 +01:00
|
|
|
this._page.emit(CommonEvents.Page.FrameDetached, frame);
|
2019-11-19 03:18:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onEventFired({frameId, name}) {
|
|
|
|
|
const frame = this._frames.get(frameId);
|
2019-12-10 01:34:42 +01:00
|
|
|
if (name === 'load') {
|
|
|
|
|
frame._firedLifecycleEvents.add('load');
|
|
|
|
|
if (frame === this._mainFrame) {
|
2019-11-19 03:18:28 +01:00
|
|
|
this.emit(FrameManagerEvents.Load);
|
2019-12-09 23:53:17 +01:00
|
|
|
this._page.emit(CommonEvents.Page.Load);
|
2019-12-10 01:34:42 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (name === 'DOMContentLoaded') {
|
|
|
|
|
frame._firedLifecycleEvents.add('domcontentloaded');
|
|
|
|
|
if (frame === this._mainFrame) {
|
2019-11-19 03:18:28 +01:00
|
|
|
this.emit(FrameManagerEvents.DOMContentLoaded);
|
2019-12-09 23:53:17 +01:00
|
|
|
this._page.emit(CommonEvents.Page.DOMContentLoaded);
|
|
|
|
|
}
|
2019-11-19 03:18:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-07 01:34:27 +01:00
|
|
|
_onUncaughtError(params) {
|
|
|
|
|
const error = new Error(params.message);
|
|
|
|
|
error.stack = params.stack;
|
|
|
|
|
this._page.emit(Events.Page.PageError, error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onConsole({type, args, executionContextId, location}) {
|
|
|
|
|
const context = this.executionContextById(executionContextId);
|
|
|
|
|
this._page._addConsoleMessage(type, args.map(arg => context._createHandle(arg)), location);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onDialogOpened(params) {
|
|
|
|
|
this._page.emit(Events.Page.Dialog, new dialog.Dialog(
|
|
|
|
|
params.type as dialog.DialogType,
|
|
|
|
|
params.message,
|
|
|
|
|
async (accept: boolean, promptText?: string) => {
|
|
|
|
|
await this._session.send('Page.handleDialog', { dialogId: params.dialogId, accept, promptText }).catch(debugError);
|
|
|
|
|
},
|
|
|
|
|
params.defaultValue));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_onBindingCalled(event: Protocol.Page.bindingCalledPayload) {
|
|
|
|
|
const context = this.executionContextById(event.executionContextId);
|
|
|
|
|
this._page._onBindingCalled(event.payload, context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async _onFileChooserOpened({executionContextId, element}) {
|
|
|
|
|
const context = this.executionContextById(executionContextId);
|
|
|
|
|
const handle = context._createHandle(element).asElement()!;
|
|
|
|
|
this._page._onFileChooserOpened(handle);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-09 23:53:17 +01:00
|
|
|
async exposeBinding(name: string, bindingFunction: string): Promise<void> {
|
2019-12-07 01:34:27 +01:00
|
|
|
await this._session.send('Page.addBinding', {name: name});
|
|
|
|
|
await this._session.send('Page.addScriptToEvaluateOnNewDocument', {script: bindingFunction});
|
|
|
|
|
await Promise.all(this.frames().map(frame => frame.evaluate(bindingFunction).catch(debugError)));
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-09 23:53:17 +01:00
|
|
|
didClose() {
|
2019-11-19 03:18:28 +01:00
|
|
|
helper.removeEventListeners(this._eventListeners);
|
2019-12-09 23:53:17 +01:00
|
|
|
this._networkManager.dispose();
|
2019-11-19 03:18:28 +01:00
|
|
|
}
|
|
|
|
|
|
2019-12-09 23:53:17 +01:00
|
|
|
async waitForFrameNavigation(frame: frames.Frame, options: frames.NavigateOptions = {}) {
|
2019-11-19 03:18:28 +01:00
|
|
|
const {
|
2019-12-09 23:53:17 +01:00
|
|
|
timeout = this._page._timeoutSettings.navigationTimeout(),
|
2019-12-10 01:34:42 +01:00
|
|
|
waitUntil = (['load'] as frames.LifecycleEvent[]),
|
2019-11-19 03:18:28 +01:00
|
|
|
} = options;
|
|
|
|
|
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
|
|
|
|
|
|
|
|
|
const timeoutError = new TimeoutError('Navigation timeout of ' + timeout + ' ms exceeded');
|
|
|
|
|
let timeoutCallback;
|
|
|
|
|
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
|
|
|
|
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
|
|
|
|
|
2019-11-27 21:38:26 +01:00
|
|
|
const nextNavigationDog = new NextNavigationWatchdog(this, frame);
|
2019-11-19 03:18:28 +01:00
|
|
|
const error1 = await Promise.race([
|
|
|
|
|
nextNavigationDog.promise(),
|
|
|
|
|
timeoutPromise,
|
|
|
|
|
]);
|
|
|
|
|
nextNavigationDog.dispose();
|
|
|
|
|
|
|
|
|
|
// If timeout happened first - throw.
|
|
|
|
|
if (error1) {
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
throw error1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const {navigationId, url} = nextNavigationDog.navigation();
|
|
|
|
|
|
|
|
|
|
if (!navigationId) {
|
|
|
|
|
// Same document navigation happened.
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-27 21:38:26 +01:00
|
|
|
const watchDog = new NavigationWatchdog(this, frame, this._networkManager, navigationId, url, normalizedWaitUntil);
|
2019-11-19 03:18:28 +01:00
|
|
|
const error = await Promise.race([
|
|
|
|
|
timeoutPromise,
|
|
|
|
|
watchDog.promise(),
|
|
|
|
|
]);
|
|
|
|
|
watchDog.dispose();
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
if (error)
|
|
|
|
|
throw error;
|
|
|
|
|
return watchDog.navigationResponse();
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-09 23:53:17 +01:00
|
|
|
async navigateFrame(frame: frames.Frame, url: string, options: frames.GotoOptions = {}) {
|
2019-11-19 03:18:28 +01:00
|
|
|
const {
|
2019-12-09 23:53:17 +01:00
|
|
|
timeout = this._page._timeoutSettings.navigationTimeout(),
|
2019-12-10 01:34:42 +01:00
|
|
|
waitUntil = (['load'] as frames.LifecycleEvent[]),
|
2019-11-19 03:18:28 +01:00
|
|
|
referer,
|
|
|
|
|
} = options;
|
|
|
|
|
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
|
|
|
|
const {navigationId} = await this._session.send('Page.navigate', {
|
2019-11-27 21:38:26 +01:00
|
|
|
frameId: this._frameData(frame).frameId,
|
2019-11-19 03:18:28 +01:00
|
|
|
referer,
|
|
|
|
|
url,
|
|
|
|
|
});
|
|
|
|
|
if (!navigationId)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const timeoutError = new TimeoutError('Navigation timeout of ' + timeout + ' ms exceeded');
|
|
|
|
|
let timeoutCallback;
|
|
|
|
|
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
|
|
|
|
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
|
|
|
|
|
2019-11-27 21:38:26 +01:00
|
|
|
const watchDog = new NavigationWatchdog(this, frame, this._networkManager, navigationId, url, normalizedWaitUntil);
|
2019-11-19 03:18:28 +01:00
|
|
|
const error = await Promise.race([
|
|
|
|
|
timeoutPromise,
|
|
|
|
|
watchDog.promise(),
|
|
|
|
|
]);
|
|
|
|
|
watchDog.dispose();
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
if (error)
|
|
|
|
|
throw error;
|
|
|
|
|
return watchDog.navigationResponse();
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-28 01:03:51 +01:00
|
|
|
async setFrameContent(frame: frames.Frame, html: string) {
|
2019-11-27 21:38:26 +01:00
|
|
|
const context = await frame._utilityContext();
|
2019-11-26 17:57:53 +01:00
|
|
|
await context.evaluate(html => {
|
|
|
|
|
document.open();
|
|
|
|
|
document.write(html);
|
|
|
|
|
document.close();
|
|
|
|
|
}, html);
|
2019-11-19 03:18:28 +01:00
|
|
|
}
|
2019-12-09 23:53:17 +01:00
|
|
|
|
|
|
|
|
setExtraHTTPHeaders(extraHTTPHeaders: network.Headers): Promise<void> {
|
|
|
|
|
return this._networkManager.setExtraHTTPHeaders(extraHTTPHeaders);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async setUserAgent(userAgent: string): Promise<void> {
|
|
|
|
|
await this._session.send('Page.setUserAgent', { userAgent });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async setJavaScriptEnabled(enabled: boolean): Promise<void> {
|
|
|
|
|
await this._session.send('Page.setJavascriptEnabled', { enabled });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async setBypassCSP(enabled: boolean): Promise<void> {
|
|
|
|
|
await this._session.send('Page.setBypassCSP', { enabled });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async setViewport(viewport: types.Viewport): Promise<void> {
|
|
|
|
|
const {
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
isMobile = false,
|
|
|
|
|
deviceScaleFactor = 1,
|
|
|
|
|
hasTouch = false,
|
|
|
|
|
isLandscape = false,
|
|
|
|
|
} = viewport;
|
|
|
|
|
await this._session.send('Page.setViewport', {
|
|
|
|
|
viewport: { width, height, isMobile, deviceScaleFactor, hasTouch, isLandscape },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async setEmulateMedia(mediaType: input.MediaType | null, mediaColorScheme: input.MediaColorScheme | null): Promise<void> {
|
|
|
|
|
await this._session.send('Page.setEmulatedMedia', {
|
|
|
|
|
type: mediaType === null ? undefined : mediaType,
|
|
|
|
|
colorScheme: mediaColorScheme === null ? undefined : mediaColorScheme
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async setCacheEnabled(enabled: boolean): Promise<void> {
|
|
|
|
|
await this._session.send('Page.setCacheDisabled', {cacheDisabled: !enabled});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async _go(action: () => Promise<{ navigationId: string | null, navigationURL: string | null }>, options: frames.NavigateOptions = {}) {
|
|
|
|
|
const {
|
|
|
|
|
timeout = this._page._timeoutSettings.navigationTimeout(),
|
2019-12-10 01:34:42 +01:00
|
|
|
waitUntil = (['load'] as frames.LifecycleEvent[]),
|
2019-12-09 23:53:17 +01:00
|
|
|
} = options;
|
|
|
|
|
const frame = this.mainFrame();
|
|
|
|
|
const normalizedWaitUntil = normalizeWaitUntil(waitUntil);
|
|
|
|
|
const { navigationId, navigationURL } = await action();
|
|
|
|
|
if (!navigationId)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
const timeoutError = new TimeoutError('Navigation timeout of ' + timeout + ' ms exceeded');
|
|
|
|
|
let timeoutCallback;
|
|
|
|
|
const timeoutPromise = new Promise(resolve => timeoutCallback = resolve.bind(null, timeoutError));
|
|
|
|
|
const timeoutId = timeout ? setTimeout(timeoutCallback, timeout) : null;
|
|
|
|
|
|
|
|
|
|
const watchDog = new NavigationWatchdog(this, frame, this._networkManager, navigationId, navigationURL, normalizedWaitUntil);
|
|
|
|
|
const error = await Promise.race([
|
|
|
|
|
timeoutPromise,
|
|
|
|
|
watchDog.promise(),
|
|
|
|
|
]);
|
|
|
|
|
watchDog.dispose();
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
if (error)
|
|
|
|
|
throw error;
|
|
|
|
|
return watchDog.navigationResponse();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reload(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
|
|
|
|
return this._go(() => this._session.send('Page.reload', { frameId: this._frameData(this.mainFrame()).frameId }), options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
goBack(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
|
|
|
|
return this._go(() => this._session.send('Page.goBack', { frameId: this._frameData(this.mainFrame()).frameId }), options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
goForward(options?: frames.NavigateOptions): Promise<network.Response | null> {
|
|
|
|
|
return this._go(() => this._session.send('Page.goForward', { frameId: this._frameData(this.mainFrame()).frameId }), options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async evaluateOnNewDocument(source: string): Promise<void> {
|
|
|
|
|
await this._session.send('Page.addScriptToEvaluateOnNewDocument', { script: source });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async closePage(runBeforeUnload: boolean): Promise<void> {
|
|
|
|
|
await this._session.send('Page.close', { runBeforeUnload });
|
|
|
|
|
}
|
2019-11-19 03:18:28 +01:00
|
|
|
}
|
|
|
|
|
|
2019-12-10 01:34:42 +01:00
|
|
|
export function normalizeWaitUntil(waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[]): frames.LifecycleEvent[] {
|
2019-11-19 03:18:28 +01:00
|
|
|
if (!Array.isArray(waitUntil))
|
|
|
|
|
waitUntil = [waitUntil];
|
|
|
|
|
for (const condition of waitUntil) {
|
|
|
|
|
if (condition !== 'load' && condition !== 'domcontentloaded')
|
|
|
|
|
throw new Error('Unknown waitUntil condition: ' + condition);
|
|
|
|
|
}
|
|
|
|
|
return waitUntil;
|
|
|
|
|
}
|