mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-05 10:15:12 +03:00
feat(firefox): roll firefox (#1273)
This commit is contained in:
parent
3c35d7b058
commit
aee6324bba
@ -9,7 +9,7 @@
|
|||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"playwright": {
|
"playwright": {
|
||||||
"chromium_revision": "747023",
|
"chromium_revision": "747023",
|
||||||
"firefox_revision": "1039",
|
"firefox_revision": "1040",
|
||||||
"webkit_revision": "1168"
|
"webkit_revision": "1168"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -29,6 +29,8 @@ import { headersArray } from './ffNetworkManager';
|
|||||||
import { FFPage } from './ffPage';
|
import { FFPage } from './ffPage';
|
||||||
import { Protocol } from './protocol';
|
import { Protocol } from './protocol';
|
||||||
|
|
||||||
|
const kAttachedToTarget = Symbol('kAttachedToTarget');
|
||||||
|
|
||||||
export class FFBrowser extends platform.EventEmitter implements Browser {
|
export class FFBrowser extends platform.EventEmitter implements Browser {
|
||||||
_connection: FFConnection;
|
_connection: FFConnection;
|
||||||
_targets: Map<string, Target>;
|
_targets: Map<string, Target>;
|
||||||
@ -36,10 +38,10 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
|
|||||||
readonly _contexts: Map<string, FFBrowserContext>;
|
readonly _contexts: Map<string, FFBrowserContext>;
|
||||||
private _eventListeners: RegisteredListener[];
|
private _eventListeners: RegisteredListener[];
|
||||||
|
|
||||||
static async connect(transport: ConnectionTransport, slowMo?: number): Promise<FFBrowser> {
|
static async connect(transport: ConnectionTransport, attachToDefaultContext: boolean, slowMo?: number): Promise<FFBrowser> {
|
||||||
const connection = new FFConnection(SlowMoTransport.wrap(transport, slowMo));
|
const connection = new FFConnection(SlowMoTransport.wrap(transport, slowMo));
|
||||||
const browser = new FFBrowser(connection);
|
const browser = new FFBrowser(connection);
|
||||||
await connection.send('Target.enable');
|
await connection.send('Browser.enable', { attachToDefaultContext });
|
||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,10 +58,8 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
|
|||||||
this.emit(Events.Browser.Disconnected);
|
this.emit(Events.Browser.Disconnected);
|
||||||
});
|
});
|
||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(this._connection, 'Target.targetCreated', this._onTargetCreated.bind(this)),
|
helper.addEventListener(this._connection, 'Browser.attachedToTarget', this._onAttachedToTarget.bind(this)),
|
||||||
helper.addEventListener(this._connection, 'Target.targetDestroyed', this._onTargetDestroyed.bind(this)),
|
helper.addEventListener(this._connection, 'Browser.detachedFromTarget', this._onDetachedFromTarget.bind(this)),
|
||||||
helper.addEventListener(this._connection, 'Target.targetInfoChanged', this._onTargetInfoChanged.bind(this)),
|
|
||||||
helper.addEventListener(this._connection, 'Target.attachedToTarget', this._onAttachedToTarget.bind(this)),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
|
|||||||
hasTouch: false,
|
hasTouch: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const { browserContextId } = await this._connection.send('Target.createBrowserContext', {
|
const { browserContextId } = await this._connection.send('Browser.createBrowserContext', {
|
||||||
userAgent: options.userAgent,
|
userAgent: options.userAgent,
|
||||||
bypassCSP: options.bypassCSP,
|
bypassCSP: options.bypassCSP,
|
||||||
javaScriptDisabled: options.javaScriptEnabled === false ? true : undefined,
|
javaScriptDisabled: options.javaScriptEnabled === false ? true : undefined,
|
||||||
@ -121,13 +121,13 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
|
|||||||
return existingTarget;
|
return existingTarget;
|
||||||
let resolve: (t: Target) => void;
|
let resolve: (t: Target) => void;
|
||||||
const targetPromise = new Promise<Target>(x => resolve = x);
|
const targetPromise = new Promise<Target>(x => resolve = x);
|
||||||
this.on('targetchanged', check);
|
this.on(kAttachedToTarget, check);
|
||||||
try {
|
try {
|
||||||
if (!timeout)
|
if (!timeout)
|
||||||
return await targetPromise;
|
return await targetPromise;
|
||||||
return await helper.waitWithTimeout(targetPromise, 'target', timeout);
|
return await helper.waitWithTimeout(targetPromise, 'target', timeout);
|
||||||
} finally {
|
} finally {
|
||||||
this.removeListener('targetchanged', check);
|
this.removeListener(kAttachedToTarget, check);
|
||||||
}
|
}
|
||||||
|
|
||||||
function check(target: Target) {
|
function check(target: Target) {
|
||||||
@ -140,33 +140,23 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
|
|||||||
return Array.from(this._targets.values());
|
return Array.from(this._targets.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onTargetCreated(payload: Protocol.Target.targetCreatedPayload) {
|
_onDetachedFromTarget(payload: Protocol.Browser.detachedFromTargetPayload) {
|
||||||
const {targetId, url, browserContextId, openerId, type} = payload;
|
|
||||||
const context = browserContextId ? this._contexts.get(browserContextId)! : this._defaultContext;
|
|
||||||
const target = new Target(this._connection, this, context, targetId, type, url, openerId);
|
|
||||||
this._targets.set(targetId, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onTargetDestroyed(payload: Protocol.Target.targetDestroyedPayload) {
|
|
||||||
const {targetId} = payload;
|
const {targetId} = payload;
|
||||||
const target = this._targets.get(targetId)!;
|
const target = this._targets.get(targetId)!;
|
||||||
this._targets.delete(targetId);
|
this._targets.delete(targetId);
|
||||||
target._didClose();
|
target._didClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onTargetInfoChanged(payload: Protocol.Target.targetInfoChangedPayload) {
|
async _onAttachedToTarget(payload: Protocol.Browser.attachedToTargetPayload) {
|
||||||
const {targetId, url} = payload;
|
const {targetId, browserContextId, openerId, type} = payload.targetInfo;
|
||||||
const target = this._targets.get(targetId)!;
|
const context = browserContextId ? this._contexts.get(browserContextId)! : this._defaultContext;
|
||||||
target._url = url;
|
const target = new Target(this, context, type, '', openerId);
|
||||||
}
|
this._targets.set(targetId, target);
|
||||||
|
target._initPagePromise(this._connection.createSession(payload.sessionId, type));
|
||||||
async _onAttachedToTarget(payload: Protocol.Target.attachedToTargetPayload) {
|
|
||||||
const {targetId} = payload.targetInfo;
|
|
||||||
const target = this._targets.get(targetId)!;
|
|
||||||
target._initPagePromise(this._connection.getSession(payload.sessionId)!);
|
|
||||||
|
|
||||||
const pageEvent = new PageEvent(target.pageOrError());
|
const pageEvent = new PageEvent(target.pageOrError());
|
||||||
target.context().emit(Events.BrowserContext.Page, pageEvent);
|
target.context().emit(Events.BrowserContext.Page, pageEvent);
|
||||||
|
this.emit(kAttachedToTarget, target);
|
||||||
|
|
||||||
const opener = target.opener();
|
const opener = target.opener();
|
||||||
if (!opener)
|
if (!opener)
|
||||||
@ -194,23 +184,22 @@ class Target {
|
|||||||
_ffPage: FFPage | null = null;
|
_ffPage: FFPage | null = null;
|
||||||
private readonly _browser: FFBrowser;
|
private readonly _browser: FFBrowser;
|
||||||
private readonly _context: FFBrowserContext;
|
private readonly _context: FFBrowserContext;
|
||||||
private readonly _connection: FFConnection;
|
|
||||||
private readonly _targetId: string;
|
|
||||||
private readonly _type: 'page' | 'browser';
|
private readonly _type: 'page' | 'browser';
|
||||||
_url: string;
|
_url: string;
|
||||||
private readonly _openerId: string | undefined;
|
private readonly _openerId: string | undefined;
|
||||||
|
private _session?: FFSession;
|
||||||
|
|
||||||
constructor(connection: any, browser: FFBrowser, context: FFBrowserContext, targetId: string, type: 'page' | 'browser', url: string, openerId: string | undefined) {
|
constructor(browser: FFBrowser, context: FFBrowserContext, type: 'page' | 'browser', url: string, openerId: string | undefined) {
|
||||||
this._browser = browser;
|
this._browser = browser;
|
||||||
this._context = context;
|
this._context = context;
|
||||||
this._connection = connection;
|
|
||||||
this._targetId = targetId;
|
|
||||||
this._type = type;
|
this._type = type;
|
||||||
this._url = url;
|
this._url = url;
|
||||||
this._openerId = openerId;
|
this._openerId = openerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
_didClose() {
|
_didClose() {
|
||||||
|
if (this._session)
|
||||||
|
this._session.dispose();
|
||||||
if (this._ffPage)
|
if (this._ffPage)
|
||||||
this._ffPage.didClose();
|
this._ffPage.didClose();
|
||||||
}
|
}
|
||||||
@ -234,12 +223,11 @@ class Target {
|
|||||||
async pageOrError(): Promise<Page | Error> {
|
async pageOrError(): Promise<Page | Error> {
|
||||||
if (this._type !== 'page')
|
if (this._type !== 'page')
|
||||||
throw new Error(`Cannot create page for "${this._type}" target`);
|
throw new Error(`Cannot create page for "${this._type}" target`);
|
||||||
if (!this._pagePromise)
|
|
||||||
await this._connection.send('Target.attachToTarget', {targetId: this._targetId});
|
|
||||||
return this._pagePromise!;
|
return this._pagePromise!;
|
||||||
}
|
}
|
||||||
|
|
||||||
_initPagePromise(session: FFSession) {
|
_initPagePromise(session: FFSession) {
|
||||||
|
this._session = session;
|
||||||
this._pagePromise = new Promise(async f => {
|
this._pagePromise = new Promise(async f => {
|
||||||
this._ffPage = new FFPage(session, this._context, async () => {
|
this._ffPage = new FFPage(session, this._context, async () => {
|
||||||
const openerTarget = this.opener();
|
const openerTarget = this.opener();
|
||||||
@ -318,7 +306,7 @@ export class FFBrowserContext extends BrowserContextBase {
|
|||||||
|
|
||||||
async newPage(): Promise<Page> {
|
async newPage(): Promise<Page> {
|
||||||
assertBrowserContextIsNotOwned(this);
|
assertBrowserContextIsNotOwned(this);
|
||||||
const {targetId} = await this._browser._connection.send('Target.newPage', {
|
const {targetId} = await this._browser._connection.send('Browser.newPage', {
|
||||||
browserContextId: this._browserContextId || undefined
|
browserContextId: this._browserContextId || undefined
|
||||||
});
|
});
|
||||||
const target = this._browser._targets.get(targetId)!;
|
const target = this._browser._targets.get(targetId)!;
|
||||||
@ -410,7 +398,7 @@ export class FFBrowserContext extends BrowserContextBase {
|
|||||||
if (this._closed)
|
if (this._closed)
|
||||||
return;
|
return;
|
||||||
assert(this._browserContextId, 'Non-incognito profiles cannot be closed!');
|
assert(this._browserContextId, 'Non-incognito profiles cannot be closed!');
|
||||||
await this._browser._connection.send('Target.removeBrowserContext', { browserContextId: this._browserContextId });
|
await this._browser._connection.send('Browser.removeBrowserContext', { browserContextId: this._browserContextId });
|
||||||
this._browser._contexts.delete(this._browserContextId);
|
this._browser._contexts.delete(this._browserContextId);
|
||||||
this._didCloseInternal();
|
this._didCloseInternal();
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ export class FFConnection extends platform.EventEmitter {
|
|||||||
private _lastId: number;
|
private _lastId: number;
|
||||||
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
|
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
|
||||||
private _transport: ConnectionTransport;
|
private _transport: ConnectionTransport;
|
||||||
private _sessions: Map<string, FFSession>;
|
readonly _sessions: Map<string, FFSession>;
|
||||||
_debugProtocol: (message: string) => void = platform.debug('pw:protocol');
|
_debugProtocol: (message: string) => void = platform.debug('pw:protocol');
|
||||||
_closed: boolean;
|
_closed: boolean;
|
||||||
|
|
||||||
@ -60,14 +60,6 @@ export class FFConnection extends platform.EventEmitter {
|
|||||||
this.once = super.once;
|
this.once = super.once;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromSession(session: FFSession): FFConnection {
|
|
||||||
return session._connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
session(sessionId: string): FFSession | null {
|
|
||||||
return this._sessions.get(sessionId) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async send<T extends keyof Protocol.CommandParameters>(
|
async send<T extends keyof Protocol.CommandParameters>(
|
||||||
method: T,
|
method: T,
|
||||||
params?: Protocol.CommandParameters[T]
|
params?: Protocol.CommandParameters[T]
|
||||||
@ -94,17 +86,6 @@ export class FFConnection extends platform.EventEmitter {
|
|||||||
const object = JSON.parse(message);
|
const object = JSON.parse(message);
|
||||||
if (object.id === kBrowserCloseMessageId)
|
if (object.id === kBrowserCloseMessageId)
|
||||||
return;
|
return;
|
||||||
if (object.method === 'Target.attachedToTarget') {
|
|
||||||
const sessionId = object.params.sessionId;
|
|
||||||
const session = new FFSession(this, object.params.targetInfo.type, sessionId, message => this._rawSend({...message, sessionId}));
|
|
||||||
this._sessions.set(sessionId, session);
|
|
||||||
} else if (object.method === 'Target.detachedFromTarget') {
|
|
||||||
const session = this._sessions.get(object.params.sessionId);
|
|
||||||
if (session) {
|
|
||||||
session._onClosed();
|
|
||||||
this._sessions.delete(object.params.sessionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (object.sessionId) {
|
if (object.sessionId) {
|
||||||
const session = this._sessions.get(object.sessionId);
|
const session = this._sessions.get(object.sessionId);
|
||||||
if (session)
|
if (session)
|
||||||
@ -132,7 +113,7 @@ export class FFConnection extends platform.EventEmitter {
|
|||||||
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
|
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
|
||||||
this._callbacks.clear();
|
this._callbacks.clear();
|
||||||
for (const session of this._sessions.values())
|
for (const session of this._sessions.values())
|
||||||
session._onClosed();
|
session.dispose();
|
||||||
this._sessions.clear();
|
this._sessions.clear();
|
||||||
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
|
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
|
||||||
}
|
}
|
||||||
@ -142,8 +123,10 @@ export class FFConnection extends platform.EventEmitter {
|
|||||||
this._transport.close();
|
this._transport.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
getSession(sessionId: string): FFSession | null {
|
createSession(sessionId: string, type: string): FFSession {
|
||||||
return this._sessions.get(sessionId) || null;
|
const session = new FFSession(this, type, sessionId, message => this._rawSend({...message, sessionId}));
|
||||||
|
this._sessions.set(sessionId, session);
|
||||||
|
return session;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,11 +189,12 @@ export class FFSession extends platform.EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onClosed() {
|
dispose() {
|
||||||
for (const callback of this._callbacks.values())
|
for (const callback of this._callbacks.values())
|
||||||
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
|
callback.reject(rewriteError(callback.error, `Protocol error (${callback.method}): Target closed.`));
|
||||||
this._callbacks.clear();
|
this._callbacks.clear();
|
||||||
this._disposed = true;
|
this._disposed = true;
|
||||||
|
this._connection._sessions.delete(this._sessionId);
|
||||||
Promise.resolve().then(() => this.emit(FFSessionEvents.Disconnected));
|
Promise.resolve().then(() => this.emit(FFSessionEvents.Disconnected));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -226,7 +226,7 @@ export class FFPage implements PageDelegate {
|
|||||||
const worker = this._workers.get(workerId);
|
const worker = this._workers.get(workerId);
|
||||||
if (!worker)
|
if (!worker)
|
||||||
return;
|
return;
|
||||||
worker.session._onClosed();
|
worker.session.dispose();
|
||||||
this._workers.delete(workerId);
|
this._workers.delete(workerId);
|
||||||
this._page._removeWorker(workerId);
|
this._page._removeWorker(workerId);
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ export class Firefox implements BrowserType {
|
|||||||
throw new Error('userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistent` instead');
|
throw new Error('userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistent` instead');
|
||||||
const browserServer = await this._launchServer(options, 'local');
|
const browserServer = await this._launchServer(options, 'local');
|
||||||
const browser = await platform.connectToWebsocket(browserServer.wsEndpoint()!, transport => {
|
const browser = await platform.connectToWebsocket(browserServer.wsEndpoint()!, transport => {
|
||||||
return FFBrowser.connect(transport, options && options.slowMo);
|
return FFBrowser.connect(transport, false, options && options.slowMo);
|
||||||
});
|
});
|
||||||
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
|
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
|
||||||
browser.close = () => browserServer.close();
|
browser.close = () => browserServer.close();
|
||||||
@ -81,7 +81,7 @@ export class Firefox implements BrowserType {
|
|||||||
const { timeout = 30000 } = options || {};
|
const { timeout = 30000 } = options || {};
|
||||||
const browserServer = await this._launchServer(options, 'persistent', userDataDir);
|
const browserServer = await this._launchServer(options, 'persistent', userDataDir);
|
||||||
const browser = await platform.connectToWebsocket(browserServer.wsEndpoint()!, transport => {
|
const browser = await platform.connectToWebsocket(browserServer.wsEndpoint()!, transport => {
|
||||||
return FFBrowser.connect(transport);
|
return FFBrowser.connect(transport, true);
|
||||||
});
|
});
|
||||||
await helper.waitWithTimeout(browser._waitForTarget(t => t.type() === 'page'), 'first page', timeout);
|
await helper.waitWithTimeout(browser._waitForTarget(t => t.type() === 'page'), 'first page', timeout);
|
||||||
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
|
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
|
||||||
@ -166,7 +166,7 @@ export class Firefox implements BrowserType {
|
|||||||
|
|
||||||
async connect(options: ConnectOptions): Promise<FFBrowser> {
|
async connect(options: ConnectOptions): Promise<FFBrowser> {
|
||||||
return await platform.connectToWebsocket(options.wsEndpoint, transport => {
|
return await platform.connectToWebsocket(options.wsEndpoint, transport => {
|
||||||
return FFBrowser.connect(transport, options.slowMo);
|
return FFBrowser.connect(transport, false, options.slowMo);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ const connect = {
|
|||||||
firefox: {
|
firefox: {
|
||||||
connect: async (url: string) => {
|
connect: async (url: string) => {
|
||||||
return await platform.connectToWebsocket(url, transport => {
|
return await platform.connectToWebsocket(url, transport => {
|
||||||
return FirefoxBrowser.connect(transport);
|
return FirefoxBrowser.connect(transport, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user