fix(webkit): move UI process agents to page proxy (#416)

This commit is contained in:
Yury Semikhatsky 2020-01-07 17:16:27 -08:00 committed by GitHub
parent eab5108719
commit 80ffd92552
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 87 additions and 77 deletions

View file

@ -10,7 +10,7 @@
"playwright": {
"chromium_revision": "724623",
"firefox_revision": "1009",
"webkit_revision": "1068"
"webkit_revision": "1070"
},
"scripts": {
"unit": "node test/test.js",

View file

@ -96,7 +96,7 @@ export class WKBrowser extends browser.Browser {
}
if (!context)
context = this._defaultContext;
const pageProxy = new WKPageProxy(this, session, context);
const pageProxy = new WKPageProxy(session, context);
this._pageProxies.set(pageProxyInfo.pageProxyId, pageProxy);
if (pageProxyInfo.openerId) {

View file

@ -139,8 +139,9 @@ export const WKTargetSessionEvents = {
export class WKPageProxySession extends platform.EventEmitter {
_connection: WKConnection;
private readonly _sessions = new Map<string, WKTargetSession>();
private readonly _callbacks = new Map<number, {resolve:(o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
private readonly _pageProxyId: string;
private readonly _closePromise: Promise<void>;
private _closePromiseCallback: () => void;
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;
@ -151,6 +152,7 @@ export class WKPageProxySession extends platform.EventEmitter {
super();
this._connection = connection;
this._pageProxyId = pageProxyId;
this._closePromise = new Promise(r => this._closePromiseCallback = r);
}
send<T extends keyof Protocol.CommandParameters>(
@ -159,11 +161,10 @@ export class WKPageProxySession extends platform.EventEmitter {
): Promise<Protocol.CommandReturnValues[T]> {
if (!this._connection)
return Promise.reject(new Error(`Protocol error (${method}): Session closed. Most likely the pageProxy has been closed.`));
return this._connection.send(method, params, this._pageProxyId).catch(e => {
// There is a possible race of the connection closure. We may have received
// targetDestroyed notification before response for the command, in that
// case it's safe to swallow the exception.
}) as Promise<Protocol.CommandReturnValues[T]>;
return Promise.race([
this._closePromise.then(() => { throw new Error('Page proxy closed'); }),
this._connection.send(method, params, this._pageProxyId)
]);
}
_dispatchEvent(object: {method: string, params: any, pageProxyId?: string}, wrappedMessage: string) {
@ -205,11 +206,16 @@ export class WKPageProxySession extends platform.EventEmitter {
}
}
isClosed() {
return !this._connection;
}
dispose() {
for (const session of this._sessions.values())
session._onClosed();
this._sessions.clear();
this._closePromiseCallback();
this._connection = null;
}
}

View file

@ -18,7 +18,7 @@
import * as input from '../input';
import { helper } from '../helper';
import { macEditingCommands } from '../usKeyboardLayout';
import { WKTargetSession } from './wkConnection';
import { WKPageProxySession, WKTargetSession } from './wkConnection';
function toModifiersMask(modifiers: Set<input.Modifier>): number {
// From Source/WebKit/Shared/WebEvent.h
@ -35,8 +35,13 @@ function toModifiersMask(modifiers: Set<input.Modifier>): number {
}
export class RawKeyboardImpl implements input.RawKeyboard {
private readonly _pageProxySession: WKPageProxySession;
private _session: WKTargetSession;
constructor(session: WKPageProxySession) {
this._pageProxySession = session;
}
setSession(session: WKTargetSession) {
this._session = session;
}
@ -52,7 +57,7 @@ export class RawKeyboardImpl implements input.RawKeyboard {
let commands = macEditingCommands[shortcut];
if (helper.isString(commands))
commands = [commands];
await this._session.send('Input.dispatchKeyEvent', {
await this._pageProxySession.send('Input.dispatchKeyEvent', {
type: 'keyDown',
modifiers: toModifiersMask(modifiers),
windowsVirtualKeyCode: keyCode,
@ -67,7 +72,7 @@ export class RawKeyboardImpl implements input.RawKeyboard {
}
async keyup(modifiers: Set<input.Modifier>, code: string, keyCode: number, keyCodeWithoutLocation: number, key: string, location: number): Promise<void> {
await this._session.send('Input.dispatchKeyEvent', {
await this._pageProxySession.send('Input.dispatchKeyEvent', {
type: 'keyUp',
modifiers: toModifiersMask(modifiers),
key,
@ -83,14 +88,14 @@ export class RawKeyboardImpl implements input.RawKeyboard {
}
export class RawMouseImpl implements input.RawMouse {
private _client: WKTargetSession;
private readonly _pageProxySession: WKPageProxySession;
setSession(client: WKTargetSession) {
this._client = client;
constructor(session: WKPageProxySession) {
this._pageProxySession = session;
}
async move(x: number, y: number, button: input.Button | 'none', buttons: Set<input.Button>, modifiers: Set<input.Modifier>): Promise<void> {
await this._client.send('Input.dispatchMouseEvent', {
await this._pageProxySession.send('Input.dispatchMouseEvent', {
type: 'move',
button,
x,
@ -100,7 +105,7 @@ export class RawMouseImpl implements input.RawMouse {
}
async down(x: number, y: number, button: input.Button, buttons: Set<input.Button>, modifiers: Set<input.Modifier>, clickCount: number): Promise<void> {
await this._client.send('Input.dispatchMouseEvent', {
await this._pageProxySession.send('Input.dispatchMouseEvent', {
type: 'down',
button,
x,
@ -111,7 +116,7 @@ export class RawMouseImpl implements input.RawMouse {
}
async up(x: number, y: number, button: input.Button, buttons: Set<input.Button>, modifiers: Set<input.Modifier>, clickCount: number): Promise<void> {
await this._client.send('Input.dispatchMouseEvent', {
await this._pageProxySession.send('Input.dispatchMouseEvent', {
type: 'up',
button,
x,

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { WKTargetSession } from './wkConnection';
import { WKTargetSession, WKPageProxySession } from './wkConnection';
import { Page } from '../page';
import { helper, RegisteredListener, assert } from '../helper';
import { Protocol } from './protocol';
@ -25,14 +25,20 @@ import * as types from '../types';
import * as platform from '../platform';
export class WKNetworkManager {
private readonly _page: Page;
private readonly _pageProxySession: WKPageProxySession;
private _session: WKTargetSession;
_page: Page;
private _requestIdToRequest = new Map<string, InterceptableRequest>();
private readonly _requestIdToRequest = new Map<string, InterceptableRequest>();
private _userCacheDisabled = false;
private _sessionListeners: RegisteredListener[] = [];
constructor(page: Page) {
constructor(page: Page, pageProxySession: WKPageProxySession) {
this._page = page;
this._pageProxySession = pageProxySession;
}
async initializePageProxySession(credentials: types.Credentials | null) {
await this.authenticate(credentials);
}
setSession(session: WKTargetSession) {
@ -47,17 +53,13 @@ export class WKNetworkManager {
];
}
async initializeSession(session: WKTargetSession, interceptNetwork: boolean | null, offlineMode: boolean | null, credentials: types.Credentials | null) {
async initializeSession(session: WKTargetSession, interceptNetwork: boolean | null, offlineMode: boolean | null) {
const promises = [];
promises.push(session.send('Network.enable'));
if (interceptNetwork)
promises.push(session.send('Network.setInterceptionEnabled', { enabled: true }));
if (offlineMode)
promises.push(session.send('Network.setEmulateOfflineState', { offline: true }));
if (credentials)
promises.push(session.send('Emulation.setAuthCredentials', { ...credentials }));
else
promises.push(session.send('Emulation.setAuthCredentials', { username: '', password: '' }));
await Promise.all(promises);
}
@ -160,7 +162,7 @@ export class WKNetworkManager {
}
async authenticate(credentials: types.Credentials | null) {
await this._session.send('Emulation.setAuthCredentials', { ...(credentials || {}) });
await this._pageProxySession.send('Emulation.setAuthCredentials', { ...(credentials || { username: '', password: '' }) });
}
async setOfflineMode(value: boolean): Promise<void> {

View file

@ -19,7 +19,7 @@ import * as frames from '../frames';
import { debugError, helper, RegisteredListener } from '../helper';
import * as dom from '../dom';
import * as network from '../network';
import { WKTargetSession, WKTargetSessionEvents } from './wkConnection';
import { WKTargetSession, WKTargetSessionEvents, WKPageProxySession } from './wkConnection';
import { Events } from '../events';
import { WKExecutionContext, EVALUATION_SCRIPT_URL } from './wkExecutionContext';
import { WKNetworkManager } from './wkNetworkManager';
@ -43,31 +43,43 @@ export class WKPage implements PageDelegate {
readonly rawKeyboard: RawKeyboardImpl;
_session: WKTargetSession;
readonly _page: Page;
private _browser: WKBrowser;
private _networkManager: WKNetworkManager;
private _workers: WKWorkers;
private _contextIdToContext: Map<number, dom.FrameExecutionContext>;
private readonly _pageProxySession: WKPageProxySession;
private readonly _networkManager: WKNetworkManager;
private readonly _workers: WKWorkers;
private readonly _contextIdToContext: Map<number, dom.FrameExecutionContext>;
private _isolatedWorlds: Set<string>;
private _sessionListeners: RegisteredListener[] = [];
private _bootstrapScripts: string[] = [];
private readonly _bootstrapScripts: string[] = [];
constructor(browser: WKBrowser, browserContext: BrowserContext) {
this._browser = browser;
this.rawKeyboard = new RawKeyboardImpl();
this.rawMouse = new RawMouseImpl();
constructor(browserContext: BrowserContext, pageProxySession: WKPageProxySession) {
this._pageProxySession = pageProxySession;
this.rawKeyboard = new RawKeyboardImpl(pageProxySession);
this.rawMouse = new RawMouseImpl(pageProxySession);
this._contextIdToContext = new Map();
this._isolatedWorlds = new Set();
this._page = new Page(this, browserContext);
this._networkManager = new WKNetworkManager(this._page);
this._networkManager = new WKNetworkManager(this._page, pageProxySession);
this._workers = new WKWorkers(this._page);
}
async _initializePageProxySession() {
const promises : Promise<any>[] = [
this._pageProxySession.send('Dialog.enable'),
this._networkManager.initializePageProxySession(this._page._state.credentials)
];
const contextOptions = this._page.browserContext()._options;
if (contextOptions.javaScriptEnabled === false)
promises.push(this._pageProxySession.send('Emulation.setJavaScriptEnabled', { enabled: false }));
if (this._page._state.viewport)
promises.push(this.setViewport(this._page._state.viewport));
await Promise.all(promises);
}
setSession(session: WKTargetSession) {
helper.removeEventListeners(this._sessionListeners);
this.disconnectFromTarget();
this._session = session;
this.rawKeyboard.setSession(session);
this.rawMouse.setSession(session);
this._addSessionListeners();
this._networkManager.setSession(session);
this._workers.setSession(session);
@ -90,30 +102,28 @@ export class WKPage implements PageDelegate {
session.send('Runtime.enable').then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),
session.send('Console.enable'),
session.send('Page.setInterceptFileChooserDialog', { enabled: true }),
this._networkManager.initializeSession(session, this._page._state.interceptNetwork, this._page._state.offlineMode, this._page._state.credentials),
this._workers.initializeSession(session)
this._networkManager.initializeSession(session, this._page._state.interceptNetwork, this._page._state.offlineMode),
this._workers.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'));
}
const contextOptions = this._page.browserContext()._options;
if (contextOptions.userAgent)
promises.push(session.send('Page.overrideUserAgent', { value: contextOptions.userAgent }));
if (this._page._state.mediaType || this._page._state.colorScheme)
promises.push(this._setEmulateMedia(session, this._page._state.mediaType, this._page._state.colorScheme));
if (contextOptions.javaScriptEnabled === false)
promises.push(session.send('Emulation.setJavaScriptEnabled', { enabled: false }));
if (session.isProvisional())
promises.push(this._setBootstrapScripts(session));
if (contextOptions.bypassCSP)
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
if (this._page._state.extraHTTPHeaders !== null)
promises.push(this._setExtraHTTPHeaders(session, this._page._state.extraHTTPHeaders));
if (this._page._state.viewport)
promises.push(WKPage._setViewport(session, this._page._state.viewport));
await Promise.all(promises);
await Promise.all(promises).catch(e => {
if (session.isClosed())
return;
// Swallow initialization errors due to newer target swap in,
// since we will reinitialize again.
if (this._session === session)
throw e;
});
}
didClose(crashed: boolean) {
@ -137,7 +147,7 @@ export class WKPage implements PageDelegate {
helper.addEventListener(this._session, 'Page.domContentEventFired', event => this._onLifecycleEvent(event.frameId, 'domcontentloaded')),
helper.addEventListener(this._session, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
helper.addEventListener(this._session, 'Console.messageAdded', event => this._onConsoleMessage(event)),
helper.addEventListener(this._session, 'Dialog.javascriptDialogOpening', event => this._onDialog(event)),
helper.addEventListener(this._pageProxySession, 'Dialog.javascriptDialogOpening', event => this._onDialog(event)),
helper.addEventListener(this._session, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
helper.addEventListener(this._session, WKTargetSessionEvents.Disconnected, event => this._page._didDisconnect()),
];
@ -259,7 +269,7 @@ export class WKPage implements PageDelegate {
event.type as dialog.DialogType,
event.message,
async (accept: boolean, promptText?: string) => {
await this._session.send('Dialog.handleJavaScriptDialog', { accept, promptText });
await this._pageProxySession.send('Dialog.handleJavaScriptDialog', { accept, promptText });
},
event.defaultPrompt));
}
@ -307,15 +317,11 @@ export class WKPage implements PageDelegate {
}
async setViewport(viewport: types.Viewport): Promise<void> {
return WKPage._setViewport(this._session, viewport);
}
private static async _setViewport(session: WKTargetSession, 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 session.send('Emulation.setDeviceMetricsOverride', { width, height, fixedLayout: false, deviceScaleFactor: viewport.deviceScaleFactor || 1 });
await this._pageProxySession.send('Emulation.setDeviceMetricsOverride', { width, height, fixedLayout: false, deviceScaleFactor: viewport.deviceScaleFactor || 1 });
}
setCacheEnabled(enabled: boolean): Promise<void> {
@ -402,7 +408,7 @@ export class WKPage implements PageDelegate {
}
async resetViewport(oldSize: types.Size): Promise<void> {
await this._session.send('Emulation.setDeviceMetricsOverride', { ...oldSize, fixedLayout: false, deviceScaleFactor: 0 });
await this._pageProxySession.send('Emulation.setDeviceMetricsOverride', { ...oldSize, fixedLayout: false, deviceScaleFactor: 0 });
}
async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {

View file

@ -7,12 +7,10 @@ import { Page } from '../page';
import { Protocol } from './protocol';
import { WKPageProxySession, WKPageProxySessionEvents, WKTargetSession } from './wkConnection';
import { WKPage } from './wkPage';
import { WKBrowser } from './wkBrowser';
import { RegisteredListener, helper, assert, debugError } from '../helper';
import { Events } from '../events';
export class WKPageProxy {
private readonly _browser: WKBrowser;
private readonly _pageProxySession: WKPageProxySession;
readonly _browserContext: BrowserContext;
private _pagePromise: Promise<Page> | null = null;
@ -22,8 +20,7 @@ export class WKPageProxy {
private readonly _targetSessions = new Map<string, WKTargetSession>();
private readonly _eventListeners: RegisteredListener[];
constructor(browser: WKBrowser, session: WKPageProxySession, browserContext: BrowserContext) {
this._browser = browser;
constructor(session: WKPageProxySession, browserContext: BrowserContext) {
this._pageProxySession = session;
this._browserContext = browserContext;
this._firstTargetPromise = new Promise(r => this._firstTargetCallback = r);
@ -35,6 +32,8 @@ export class WKPageProxy {
// Intercept provisional targets during cross-process navigation.
this._pageProxySession.send('Target.setPauseOnStart', { pauseOnStart: true }).catch(e => {
if (this._pageProxySession.isClosed())
return;
debugError(e);
throw e;
});
@ -68,23 +67,15 @@ export class WKPageProxy {
}
}
assert(session, 'One non-provisional target session must exist');
this._wkPage = new WKPage(this._browser, this._browserContext);
this._wkPage = new WKPage(this._browserContext, this._pageProxySession);
this._wkPage.setSession(session);
await this._initializeSession(session);
await Promise.all([
this._wkPage._initializePageProxySession(),
this._wkPage._initializeSession(session)
]);
return this._wkPage._page;
}
private _initializeSession(session: WKTargetSession) : Promise<void> {
return this._wkPage._initializeSession(session).catch(e => {
if (session.isClosed())
return;
// Swallow initialization errors due to newer target swap in,
// since we will reinitialize again.
if (this._wkPage._session === session)
throw e;
});
}
private _onTargetCreated(session: WKTargetSession, targetInfo: Protocol.Target.TargetInfo) {
assert(targetInfo.type === 'page', 'Only page targets are expected in WebKit, received: ' + targetInfo.type);
this._targetSessions.set(targetInfo.targetId, session);
@ -93,7 +84,7 @@ export class WKPageProxy {
this._firstTargetCallback = null;
}
if (targetInfo.isProvisional && this._wkPage)
this._initializeSession(session);
this._wkPage._initializeSession(session);
if (targetInfo.isPaused)
this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(debugError);
}