mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(webkit): pause and configure provisional pages on creation (#200)
This commit is contained in:
parent
fe6addc71a
commit
e8ec7e5118
@ -78,7 +78,7 @@ export class Page<Browser, BrowserContext extends BrowserContextInterface<Browse
|
||||
private _closedPromise: Promise<void>;
|
||||
private _disconnected = false;
|
||||
private _disconnectedCallback: (e: Error) => void;
|
||||
private _disconnectedPromise: Promise<Error>;
|
||||
readonly _disconnectedPromise: Promise<Error>;
|
||||
private _browserContext: BrowserContext;
|
||||
readonly keyboard: input.Keyboard;
|
||||
readonly mouse: input.Mouse;
|
||||
|
@ -59,6 +59,12 @@ export class Browser extends EventEmitter {
|
||||
helper.addEventListener(this._connection, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)),
|
||||
helper.addEventListener(this._connection, 'Target.didCommitProvisionalTarget', this._onProvisionalTargetCommitted.bind(this)),
|
||||
];
|
||||
|
||||
// Intercept provisional targets during cross-process navigation.
|
||||
this._connection.send('Target.setPauseOnStart', { pauseOnStart: true }).catch(e => {
|
||||
debugError(e);
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
async userAgent(): Promise<string> {
|
||||
@ -157,6 +163,11 @@ export class Browser extends EventEmitter {
|
||||
context = this._defaultContext;
|
||||
const target = new Target(session, targetInfo, context);
|
||||
this._targets.set(targetInfo.targetId, target);
|
||||
if (targetInfo.isProvisional) {
|
||||
const oldTarget = this._targets.get(targetInfo.oldTargetId);
|
||||
if (oldTarget)
|
||||
oldTarget._initializeSession(session);
|
||||
}
|
||||
this._privateEvents.emit(BrowserEvents.TargetCreated, target);
|
||||
}
|
||||
|
||||
@ -185,7 +196,7 @@ export class Browser extends EventEmitter {
|
||||
async _onProvisionalTargetCommitted({oldTargetId, newTargetId}) {
|
||||
const oldTarget = this._targets.get(oldTargetId);
|
||||
const newTarget = this._targets.get(newTargetId);
|
||||
newTarget._swappedIn(oldTarget);
|
||||
newTarget._swapWith(oldTarget);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
|
@ -15,26 +15,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {assert} from '../helper';
|
||||
import {assert, debugError} from '../helper';
|
||||
import * as debug from 'debug';
|
||||
import {EventEmitter} from 'events';
|
||||
import { ConnectionTransport } from '../types';
|
||||
import { Protocol } from './protocol';
|
||||
import { throws } from 'assert';
|
||||
|
||||
const debugProtocol = debug('playwright:protocol');
|
||||
const debugWrappedMessage = require('debug')('wrapped');
|
||||
|
||||
export const ConnectionEvents = {
|
||||
Disconnected: Symbol('ConnectionEvents.Disconnected'),
|
||||
TargetCreated: Symbol('ConnectionEvents.TargetCreated')
|
||||
};
|
||||
|
||||
export class Connection extends EventEmitter {
|
||||
_lastId = 0;
|
||||
private _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
|
||||
private _delay: number;
|
||||
private _transport: ConnectionTransport;
|
||||
private _sessions = new Map<string, TargetSession>();
|
||||
private readonly _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
|
||||
private readonly _delay: number;
|
||||
private readonly _transport: ConnectionTransport;
|
||||
private readonly _sessions = new Map<string, TargetSession>();
|
||||
private _incomingMessageQueue: string[] = [];
|
||||
private _dispatchTimerId?: NodeJS.Timer;
|
||||
|
||||
_closed = false;
|
||||
|
||||
constructor(transport: ConnectionTransport, delay: number | undefined = 0) {
|
||||
@ -68,12 +71,48 @@ export class Connection extends EventEmitter {
|
||||
return id;
|
||||
}
|
||||
|
||||
async _onMessage(message: string) {
|
||||
if (this._delay)
|
||||
await new Promise(f => setTimeout(f, this._delay));
|
||||
private _onMessage(message: string) {
|
||||
if (this._incomingMessageQueue.length || this._delay)
|
||||
this._enqueueMessage(message);
|
||||
else
|
||||
this._dispatchMessage(message);
|
||||
}
|
||||
|
||||
private _enqueueMessage(message: string) {
|
||||
this._incomingMessageQueue.push(message);
|
||||
this._scheduleQueueDispatch();
|
||||
}
|
||||
|
||||
private _enqueueMessages(messages: string[]) {
|
||||
this._incomingMessageQueue = this._incomingMessageQueue.concat(messages);
|
||||
this._scheduleQueueDispatch();
|
||||
}
|
||||
|
||||
private _scheduleQueueDispatch() {
|
||||
if (this._dispatchTimerId)
|
||||
return;
|
||||
if (!this._incomingMessageQueue.length)
|
||||
return;
|
||||
const delay = this._delay || 0;
|
||||
this._dispatchTimerId = setTimeout(() => {
|
||||
this._dispatchTimerId = undefined;
|
||||
this._dispatchOneMessageFromQueue()
|
||||
}, delay);
|
||||
}
|
||||
|
||||
private _dispatchOneMessageFromQueue() {
|
||||
const message = this._incomingMessageQueue.shift();
|
||||
try {
|
||||
this._dispatchMessage(message);
|
||||
} finally {
|
||||
this._scheduleQueueDispatch();
|
||||
}
|
||||
}
|
||||
|
||||
private _dispatchMessage(message: string) {
|
||||
debugProtocol('◀ RECV ' + message);
|
||||
const object = JSON.parse(message);
|
||||
this._dispatchTargetMessageToSession(object);
|
||||
this._dispatchTargetMessageToSession(object, message);
|
||||
if (object.id) {
|
||||
const callback = this._callbacks.get(object.id);
|
||||
// Callbacks could be all rejected if someone has called `.dispose()`.
|
||||
@ -91,12 +130,14 @@ export class Connection extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
_dispatchTargetMessageToSession(object: {method: string, params: any}) {
|
||||
_dispatchTargetMessageToSession(object: {method: string, params: any}, wrappedMessage: string) {
|
||||
if (object.method === 'Target.targetCreated') {
|
||||
const {targetId, type} = object.params.targetInfo;
|
||||
const session = new TargetSession(this, type, targetId);
|
||||
this._sessions.set(targetId, session);
|
||||
const targetInfo = object.params.targetInfo as Protocol.Target.TargetInfo;
|
||||
const session = new TargetSession(this, targetInfo);
|
||||
this._sessions.set(session._sessionId, session);
|
||||
this.emit(ConnectionEvents.TargetCreated, session, object.params.targetInfo);
|
||||
if (targetInfo.isPaused)
|
||||
this.send('Target.resume', { targetId: targetInfo.targetId }).catch(debugError);
|
||||
} else if (object.method === 'Target.targetDestroyed') {
|
||||
const session = this._sessions.get(object.params.targetId);
|
||||
if (session) {
|
||||
@ -104,12 +145,16 @@ export class Connection extends EventEmitter {
|
||||
this._sessions.delete(object.params.targetId);
|
||||
}
|
||||
} else if (object.method === 'Target.dispatchMessageFromTarget') {
|
||||
const session = this._sessions.get(object.params.targetId);
|
||||
const {targetId, message} = object.params as Protocol.Target.dispatchMessageFromTargetPayload;
|
||||
const session = this._sessions.get(targetId);
|
||||
if (!session)
|
||||
throw new Error('Unknown target: ' + object.params.targetId);
|
||||
session._dispatchMessageFromTarget(object.params.message);
|
||||
throw new Error('Unknown target: ' + targetId);
|
||||
if (session.isProvisional())
|
||||
session._addProvisionalMessage(wrappedMessage);
|
||||
else
|
||||
session._dispatchMessageFromTarget(message);
|
||||
} else if (object.method === 'Target.didCommitProvisionalTarget') {
|
||||
const {oldTargetId, newTargetId} = object.params;
|
||||
const {oldTargetId, newTargetId} = object.params as Protocol.Target.didCommitProvisionalTargetPayload;
|
||||
const newSession = this._sessions.get(newTargetId);
|
||||
if (!newSession)
|
||||
throw new Error('Unknown new target: ' + newTargetId);
|
||||
@ -117,6 +162,7 @@ export class Connection extends EventEmitter {
|
||||
if (!oldSession)
|
||||
throw new Error('Unknown old target: ' + oldTargetId);
|
||||
oldSession._swappedOut = true;
|
||||
this._enqueueMessages(newSession._takeProvisionalMessagesAndCommit());
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +178,6 @@ export class Connection extends EventEmitter {
|
||||
for (const session of this._sessions.values())
|
||||
session._onClosed();
|
||||
this._sessions.clear();
|
||||
this.emit(ConnectionEvents.Disconnected);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@ -149,19 +194,27 @@ export class TargetSession extends EventEmitter {
|
||||
_connection: Connection;
|
||||
private _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
|
||||
private _targetType: string;
|
||||
private _sessionId: string;
|
||||
_sessionId: string;
|
||||
_swappedOut = false;
|
||||
private _provisionalMessages?: string[];
|
||||
on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
addListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
off: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
|
||||
|
||||
constructor(connection: Connection, targetType: string, sessionId: string) {
|
||||
constructor(connection: Connection, targetInfo: Protocol.Target.TargetInfo) {
|
||||
super();
|
||||
const {targetId, type, isProvisional} = targetInfo;
|
||||
this._connection = connection;
|
||||
this._targetType = targetType;
|
||||
this._sessionId = sessionId;
|
||||
this._targetType = type;
|
||||
this._sessionId = targetId;
|
||||
if (isProvisional)
|
||||
this._provisionalMessages = [];
|
||||
}
|
||||
|
||||
isProvisional() : boolean {
|
||||
return !!this._provisionalMessages;
|
||||
}
|
||||
|
||||
send<T extends keyof Protocol.CommandParameters>(
|
||||
@ -194,7 +247,18 @@ export class TargetSession extends EventEmitter {
|
||||
return result;
|
||||
}
|
||||
|
||||
_addProvisionalMessage(message: string) {
|
||||
this._provisionalMessages.push(message);
|
||||
}
|
||||
|
||||
_takeProvisionalMessagesAndCommit() : string[] {
|
||||
const messages = this._provisionalMessages;
|
||||
this._provisionalMessages = undefined;
|
||||
return messages;
|
||||
}
|
||||
|
||||
_dispatchMessageFromTarget(message: string) {
|
||||
console.assert(!this.isProvisional());
|
||||
const object = JSON.parse(message);
|
||||
debugWrappedMessage('◀ RECV ' + JSON.stringify(object, null, 2));
|
||||
if (object.id && this._callbacks.has(object.id)) {
|
||||
|
@ -40,7 +40,6 @@ const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
|
||||
export const FrameManagerEvents = {
|
||||
FrameNavigatedWithinDocument: Symbol('FrameNavigatedWithinDocument'),
|
||||
TargetSwappedOnNavigation: Symbol('TargetSwappedOnNavigation'),
|
||||
FrameAttached: Symbol('FrameAttached'),
|
||||
FrameDetached: Symbol('FrameDetached'),
|
||||
FrameNavigated: Symbol('FrameNavigated'),
|
||||
@ -58,14 +57,14 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
||||
readonly rawKeyboard: RawKeyboardImpl;
|
||||
readonly screenshotterDelegate: WKScreenshotDelegate;
|
||||
_session: TargetSession;
|
||||
_page: Page<Browser, BrowserContext>;
|
||||
_networkManager: NetworkManager;
|
||||
_frames: Map<string, frames.Frame>;
|
||||
_contextIdToContext: Map<number, js.ExecutionContext>;
|
||||
_isolatedWorlds: Set<string>;
|
||||
_sessionListeners: RegisteredListener[] = [];
|
||||
_mainFrame: frames.Frame;
|
||||
private _bootstrapScripts: string[] = [];
|
||||
readonly _page: Page<Browser, BrowserContext>;
|
||||
private readonly _networkManager: NetworkManager;
|
||||
private readonly _frames: Map<string, frames.Frame>;
|
||||
private readonly _contextIdToContext: Map<number, js.ExecutionContext>;
|
||||
private _isolatedWorlds: Set<string>;
|
||||
private _sessionListeners: RegisteredListener[] = [];
|
||||
private _mainFrame: frames.Frame;
|
||||
private readonly _bootstrapScripts: string[] = [];
|
||||
|
||||
constructor(browserContext: BrowserContext) {
|
||||
super();
|
||||
@ -83,34 +82,43 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
||||
this._networkManager.on(NetworkManagerEvents.RequestFinished, event => this._page.emit(Events.Page.RequestFinished, event));
|
||||
}
|
||||
|
||||
async initialize(session: TargetSession) {
|
||||
setSession(session: TargetSession) {
|
||||
helper.removeEventListeners(this._sessionListeners);
|
||||
this.disconnectFromTarget();
|
||||
this._session = session;
|
||||
this.rawKeyboard.setSession(session);
|
||||
this.rawMouse.setSession(session);
|
||||
this.screenshotterDelegate.initialize(session);
|
||||
this.screenshotterDelegate.setSession(session);
|
||||
this._addSessionListeners();
|
||||
this.emit(FrameManagerEvents.TargetSwappedOnNavigation);
|
||||
const [,{frameTree}] = await Promise.all([
|
||||
this._networkManager.setSession(session);
|
||||
this._isolatedWorlds = new Set();
|
||||
}
|
||||
|
||||
// This method is called for provisional targets as well. The session passed as the parameter
|
||||
// may be different from the current session and may be destroyed without becoming current.
|
||||
async _initializeSession(session: TargetSession) {
|
||||
const promises : Promise<any>[] = [
|
||||
// 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').then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),
|
||||
this._session.send('Console.enable'),
|
||||
this._session.send('Dialog.enable'),
|
||||
this._session.send('Page.setInterceptFileChooserDialog', { enabled: true }),
|
||||
this._networkManager.initialize(session),
|
||||
]);
|
||||
session.send('Page.enable'),
|
||||
session.send('Page.getResourceTree').then(({frameTree}) => this._handleFrameTree(frameTree)),
|
||||
// Resource tree should be received before first execution context.
|
||||
session.send('Runtime.enable').then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),
|
||||
session.send('Console.enable'),
|
||||
session.send('Page.setInterceptFileChooserDialog', { enabled: true }),
|
||||
this._networkManager.initializeSession(session),
|
||||
];
|
||||
if (!session.isProvisional()) {
|
||||
// FIXME: move dialog agent to web process.
|
||||
// Dialog agent resides in the UI process and should not be re-enabled on navigation.
|
||||
promises.push(session.send('Dialog.enable'));
|
||||
}
|
||||
if (this._page._state.userAgent !== null)
|
||||
await this._session.send('Page.overrideUserAgent', { value: this._page._state.userAgent });
|
||||
promises.push(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 || '' });
|
||||
promises.push(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 });
|
||||
promises.push(session.send('Emulation.setJavaScriptEnabled', { enabled: this._page._state.javascriptEnabled }));
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
didClose() {
|
||||
@ -511,24 +519,24 @@ export class FrameManager extends EventEmitter implements frames.FrameDelegate,
|
||||
* @internal
|
||||
*/
|
||||
class NextNavigationWatchdog {
|
||||
_frameManager: FrameManager;
|
||||
_frame: frames.Frame;
|
||||
_newDocumentNavigationPromise: Promise<Error | null>;
|
||||
_newDocumentNavigationCallback: (value?: unknown) => void;
|
||||
_sameDocumentNavigationPromise: Promise<Error | null>;
|
||||
_sameDocumentNavigationCallback: (value?: unknown) => void;
|
||||
private _lifecyclePromise: Promise<void>;
|
||||
private readonly _frameManager: FrameManager;
|
||||
private readonly _frame: frames.Frame;
|
||||
private readonly _newDocumentNavigationPromise: Promise<Error | null>;
|
||||
private _newDocumentNavigationCallback: (value?: unknown) => void;
|
||||
private readonly _sameDocumentNavigationPromise: Promise<Error | null>;
|
||||
private _sameDocumentNavigationCallback: (value?: unknown) => void;
|
||||
private readonly _lifecyclePromise: Promise<void>;
|
||||
private _lifecycleCallback: () => void;
|
||||
private _terminationPromise: Promise<Error | null>;
|
||||
private _terminationCallback: (err: Error | null) => void;
|
||||
_navigationRequest: any;
|
||||
_eventListeners: RegisteredListener[];
|
||||
_timeoutPromise: Promise<Error | null>;
|
||||
_timeoutId: NodeJS.Timer;
|
||||
_hasSameDocumentNavigation = false;
|
||||
_expectedLifecycle: frames.LifecycleEvent[];
|
||||
_initialLoaderId: string;
|
||||
_disconnectedListener: RegisteredListener;
|
||||
private readonly _frameDetachPromise: Promise<Error | null>;
|
||||
private _frameDetachCallback: (err: Error | null) => void;
|
||||
private readonly _initialSession: TargetSession;
|
||||
private _navigationRequest?: network.Request = null;
|
||||
private readonly _eventListeners: RegisteredListener[];
|
||||
private readonly _timeoutPromise: Promise<Error | null>;
|
||||
private readonly _timeoutId: NodeJS.Timer;
|
||||
private _hasSameDocumentNavigation = false;
|
||||
private readonly _expectedLifecycle: frames.LifecycleEvent[];
|
||||
private readonly _initialLoaderId: string;
|
||||
|
||||
constructor(frameManager: FrameManager, frame: frames.Frame, waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[], timeout) {
|
||||
if (Array.isArray(waitUntil))
|
||||
@ -539,6 +547,7 @@ class NextNavigationWatchdog {
|
||||
this._frameManager = frameManager;
|
||||
this._frame = frame;
|
||||
this._initialLoaderId = frameManager._frameData(frame).loaderId;
|
||||
this._initialSession = frameManager._session;
|
||||
this._newDocumentNavigationPromise = new Promise(fulfill => {
|
||||
this._newDocumentNavigationCallback = fulfill;
|
||||
});
|
||||
@ -548,23 +557,19 @@ class NextNavigationWatchdog {
|
||||
this._lifecyclePromise = new Promise(fulfill => {
|
||||
this._lifecycleCallback = fulfill;
|
||||
});
|
||||
/** @type {?Request} */
|
||||
this._navigationRequest = null;
|
||||
this._eventListeners = [
|
||||
helper.addEventListener(frameManager, FrameManagerEvents.LifecycleEvent, frame => this._onLifecycleEvent(frame)),
|
||||
helper.addEventListener(frameManager, FrameManagerEvents.FrameNavigated, frame => this._onLifecycleEvent(frame)),
|
||||
helper.addEventListener(frameManager, FrameManagerEvents.FrameNavigatedWithinDocument, frame => this._onSameDocumentNavigation(frame)),
|
||||
helper.addEventListener(frameManager, FrameManagerEvents.TargetSwappedOnNavigation, event => this._onTargetReconnected()),
|
||||
helper.addEventListener(frameManager, FrameManagerEvents.FrameDetached, frame => this._onFrameDetached(frame)),
|
||||
helper.addEventListener(frameManager.networkManager(), NetworkManagerEvents.Request, this._onRequest.bind(this)),
|
||||
];
|
||||
this._registerDisconnectedListener();
|
||||
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;
|
||||
this._terminationPromise = new Promise(fulfill => {
|
||||
this._terminationCallback = fulfill;
|
||||
this._frameDetachPromise = new Promise(fulfill => {
|
||||
this._frameDetachCallback = fulfill;
|
||||
});
|
||||
}
|
||||
|
||||
@ -581,37 +586,11 @@ class NextNavigationWatchdog {
|
||||
}
|
||||
|
||||
timeoutOrTerminationPromise(): Promise<Error | null> {
|
||||
return Promise.race([this._timeoutPromise, this._terminationPromise]);
|
||||
}
|
||||
|
||||
_registerDisconnectedListener() {
|
||||
if (this._disconnectedListener)
|
||||
helper.removeEventListeners([this._disconnectedListener]);
|
||||
const session = this._frameManager._session;
|
||||
this._disconnectedListener = helper.addEventListener(this._frameManager._session, TargetSessionEvents.Disconnected, () => {
|
||||
// Session may change on swap out, check that it's current.
|
||||
if (session === this._frameManager._session)
|
||||
this._terminationCallback(new Error('Navigation failed because browser has disconnected!'));
|
||||
});
|
||||
}
|
||||
|
||||
async _onTargetReconnected() {
|
||||
this._registerDisconnectedListener();
|
||||
// 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) {
|
||||
case 'loading':
|
||||
case 'interactive':
|
||||
case 'complete':
|
||||
this._newDocumentNavigationCallback();
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
debugError('_onTargetReconnected ' + e);
|
||||
}
|
||||
return Promise.race([
|
||||
this._timeoutPromise,
|
||||
this._frameDetachPromise,
|
||||
this._frameManager._page._disconnectedPromise
|
||||
]);
|
||||
}
|
||||
|
||||
_onLifecycleEvent(frame: frames.Frame) {
|
||||
@ -648,13 +627,14 @@ class NextNavigationWatchdog {
|
||||
this._lifecycleCallback();
|
||||
if (this._hasSameDocumentNavigation)
|
||||
this._sameDocumentNavigationCallback();
|
||||
if (this._frameManager._frameData(this._frame).loaderId !== this._initialLoaderId)
|
||||
if (this._frameManager._frameData(this._frame).loaderId !== this._initialLoaderId ||
|
||||
this._initialSession !== this._frameManager._session)
|
||||
this._newDocumentNavigationCallback();
|
||||
}
|
||||
|
||||
_onFrameDetached(frame: frames.Frame) {
|
||||
if (this._frame === frame) {
|
||||
this._terminationCallback.call(null, new Error('Navigating frame was detached'));
|
||||
this._frameDetachCallback.call(null, new Error('Navigating frame was detached'));
|
||||
return;
|
||||
}
|
||||
this._checkLifecycle();
|
||||
|
@ -44,7 +44,7 @@ export class NetworkManager extends EventEmitter {
|
||||
this._frameManager = frameManager;
|
||||
}
|
||||
|
||||
async initialize(session: TargetSession) {
|
||||
setSession(session: TargetSession) {
|
||||
helper.removeEventListeners(this._sessionListeners);
|
||||
this._session = session;
|
||||
this._sessionListeners = [
|
||||
@ -53,8 +53,13 @@ export class NetworkManager extends EventEmitter {
|
||||
helper.addEventListener(this._session, 'Network.loadingFinished', this._onLoadingFinished.bind(this)),
|
||||
helper.addEventListener(this._session, 'Network.loadingFailed', this._onLoadingFailed.bind(this)),
|
||||
];
|
||||
await this._session.send('Network.enable');
|
||||
await this._session.send('Network.setExtraHTTPHeaders', { headers: this._extraHTTPHeaders });
|
||||
}
|
||||
|
||||
async initializeSession(session: TargetSession) {
|
||||
await Promise.all([
|
||||
session.send('Network.enable'),
|
||||
session.send('Network.setExtraHTTPHeaders', { headers: this._extraHTTPHeaders }),
|
||||
]);
|
||||
}
|
||||
|
||||
async setExtraHTTPHeaders(extraHTTPHeaders: { [s: string]: string; }) {
|
||||
|
@ -11,7 +11,7 @@ import { TargetSession } from './Connection';
|
||||
export class WKScreenshotDelegate implements ScreenshotterDelegate {
|
||||
private _session: TargetSession;
|
||||
|
||||
initialize(session: TargetSession) {
|
||||
setSession(session: TargetSession) {
|
||||
this._session = session;
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,18 @@ export class Target {
|
||||
this._page._didClose();
|
||||
}
|
||||
|
||||
async _swappedIn(oldTarget: Target) {
|
||||
async _initializeSession(session: TargetSession) {
|
||||
if (!this._page)
|
||||
return;
|
||||
await (this._page._delegate as FrameManager)._initializeSession(session).catch(e => {
|
||||
// Swallow initialization errors due to newer target swap in,
|
||||
// since we will reinitialize again.
|
||||
if (!isSwappedOutError(e))
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
async _swapWith(oldTarget: Target) {
|
||||
if (!oldTarget._pagePromise)
|
||||
return;
|
||||
this._pagePromise = oldTarget._pagePromise;
|
||||
@ -59,22 +70,17 @@ export class Target {
|
||||
// old target does not close the page on connection reset.
|
||||
oldTarget._pagePromise = null;
|
||||
oldTarget._page = null;
|
||||
await this._adoptPage();
|
||||
this._adoptPage();
|
||||
}
|
||||
|
||||
private async _adoptPage() {
|
||||
private _adoptPage() {
|
||||
(this._page as any)[targetSymbol] = this;
|
||||
this._session.once(TargetSessionEvents.Disconnected, () => {
|
||||
// Once swapped out, we reset _page and won't call _didDisconnect for old session.
|
||||
if (this._page)
|
||||
this._page._didDisconnect();
|
||||
});
|
||||
await (this._page._delegate as FrameManager).initialize(this._session).catch(e => {
|
||||
// Swallow initialization errors due to newer target swap in,
|
||||
// since we will reinitialize again.
|
||||
if (!isSwappedOutError(e))
|
||||
throw e;
|
||||
});
|
||||
(this._page._delegate as FrameManager).setSession(this._session);
|
||||
}
|
||||
|
||||
async page(): Promise<Page<Browser, BrowserContext>> {
|
||||
@ -86,7 +92,8 @@ export class Target {
|
||||
const page = frameManager._page;
|
||||
this._page = page;
|
||||
this._pagePromise = new Promise(async f => {
|
||||
await this._adoptPage();
|
||||
this._adoptPage();
|
||||
await this._initializeSession(this._session);
|
||||
if (browser._defaultViewport)
|
||||
await page.setViewport(browser._defaultViewport);
|
||||
f(page);
|
||||
|
Loading…
x
Reference in New Issue
Block a user