Skip to content

Commit a547aa7

Browse files
authored
feat(connect): allow multiple webkit connections over web socket (#863)
1 parent f49d63f commit a547aa7

File tree

15 files changed

+236
-163
lines changed

15 files changed

+236
-163
lines changed

docs/api.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ See [ChromiumBrowser], [FirefoxBrowser] and [WebKitBrowser] for browser-specific
147147
- [event: 'disconnected'](#event-disconnected)
148148
- [browser.browserContexts()](#browserbrowsercontexts)
149149
- [browser.close()](#browserclose)
150-
- [browser.disconnect()](#browserdisconnect)
151150
- [browser.isConnected()](#browserisconnected)
152151
- [browser.newContext(options)](#browsernewcontextoptions)
153152
- [browser.newPage(url, [options])](#browsernewpageurl-options)
@@ -168,12 +167,11 @@ a single instance of [BrowserContext].
168167
#### browser.close()
169168
- returns: <[Promise]>
170169

171-
Closes browser and all of its pages (if any were opened). The [Browser] object itself is considered to be disposed and cannot be used anymore.
170+
In case this browser is obtained using [browserType.launch](#browsertypelaunchoptions), closes the browser and all of its pages (if any were opened).
172171

173-
#### browser.disconnect()
174-
- returns: <[Promise]>
172+
In case this browser is obtained using [browserType.connect](#browsertypeconnectoptions), clears all created contexts belonging to this browser and disconnects from the browser server.
175173

176-
Disconnects Browser from the browser application, but leaves the application process running. After calling `disconnect`, the [Browser] object is considered disposed and cannot be used anymore.
174+
The [Browser] object itself is considered to be disposed and cannot be used anymore.
177175

178176
#### browser.isConnected()
179177

@@ -3468,12 +3466,11 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
34683466

34693467
<!-- GEN:toc -->
34703468
- [browserType.connect(options)](#browsertypeconnectoptions)
3471-
- [browserType.defaultArgs([options])](#browsertypedefaultargsoptions)
34723469
- [browserType.devices](#browsertypedevices)
34733470
- [browserType.errors](#browsertypeerrors)
34743471
- [browserType.executablePath()](#browsertypeexecutablepath)
34753472
- [browserType.launch([options])](#browsertypelaunchoptions)
3476-
- [browserType.launchPersistent([options])](#browsertypelaunchpersistentoptions)
3473+
- [browserType.launchPersistent(userDataDir, [options])](#browsertypelaunchpersistentuserdatadir-options)
34773474
- [browserType.launchServer([options])](#browsertypelaunchserveroptions)
34783475
- [browserType.name()](#browsertypename)
34793476
<!-- GEN:stop -->
@@ -3648,7 +3645,6 @@ await browser.stopTracing();
36483645
- [event: 'disconnected'](#event-disconnected)
36493646
- [browser.browserContexts()](#browserbrowsercontexts)
36503647
- [browser.close()](#browserclose)
3651-
- [browser.disconnect()](#browserdisconnect)
36523648
- [browser.isConnected()](#browserisconnected)
36533649
- [browser.newContext(options)](#browsernewcontextoptions)
36543650
- [browser.newPage(url, [options])](#browsernewpageurl-options)
@@ -3816,7 +3812,6 @@ Firefox browser instance does not expose Firefox-specific features.
38163812
- [event: 'disconnected'](#event-disconnected)
38173813
- [browser.browserContexts()](#browserbrowsercontexts)
38183814
- [browser.close()](#browserclose)
3819-
- [browser.disconnect()](#browserdisconnect)
38203815
- [browser.isConnected()](#browserisconnected)
38213816
- [browser.newContext(options)](#browsernewcontextoptions)
38223817
- [browser.newPage(url, [options])](#browsernewpageurl-options)
@@ -3833,7 +3828,6 @@ WebKit browser instance does not expose WebKit-specific features.
38333828
- [event: 'disconnected'](#event-disconnected)
38343829
- [browser.browserContexts()](#browserbrowsercontexts)
38353830
- [browser.close()](#browserclose)
3836-
- [browser.disconnect()](#browserdisconnect)
38373831
- [browser.isConnected()](#browserisconnected)
38383832
- [browser.newContext(options)](#browsernewcontextoptions)
38393833
- [browser.newPage(url, [options])](#browsernewpageurl-options)

docs/web.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ API consists of a single `connect` function, similar to [browserType.connect(opt
1212
async function usePlaywright() {
1313
const browser = await window.playwrightweb.chromium.connect(options); // or 'firefox', 'webkit'
1414
// ... drive automation ...
15-
await browser.disconnect();
15+
await browser.close();
1616
}
1717
</script>
1818
```

src/browser.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export interface Browser extends platform.EventEmitterType {
2323
browserContexts(): BrowserContext[];
2424
pages(): Promise<Page[]>;
2525
newPage(url?: string, options?: BrowserContextOptions): Promise<Page>;
26-
disconnect(): Promise<void>;
2726
isConnected(): boolean;
2827
close(): Promise<void>;
2928
}

src/chromium/crBrowser.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,23 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
4444

4545
static async connect(transport: ConnectionTransport, slowMo?: number): Promise<CRBrowser> {
4646
const connection = new CRConnection(SlowMoTransport.wrap(transport, slowMo));
47-
const { browserContextIds } = await connection.rootSession.send('Target.getBrowserContexts');
48-
const browser = new CRBrowser(connection, browserContextIds);
47+
const browser = new CRBrowser(connection);
4948
await connection.rootSession.send('Target.setDiscoverTargets', { discover: true });
5049
await browser.waitForTarget(t => t.type() === 'page');
5150
return browser;
5251
}
5352

54-
constructor(connection: CRConnection, contextIds: string[]) {
53+
constructor(connection: CRConnection) {
5554
super();
5655
this._connection = connection;
5756
this._client = connection.rootSession;
5857

5958
this._defaultContext = this._createBrowserContext(null, {});
60-
for (const contextId of contextIds)
61-
this._contexts.set(contextId, this._createBrowserContext(contextId, {}));
62-
6359
this._connection.on(ConnectionEvents.Disconnected, () => this.emit(CommonEvents.Browser.Disconnected));
6460
this._client.on('Target.targetCreated', this._targetCreated.bind(this));
6561
this._client.on('Target.targetDestroyed', this._targetDestroyed.bind(this));
6662
this._client.on('Target.targetInfoChanged', this._targetInfoChanged.bind(this));
6763
}
68-
6964
_createBrowserContext(contextId: string | null, options: BrowserContextOptions): BrowserContext {
7065
const context = new BrowserContext({
7166
pages: async (): Promise<Page[]> => {
@@ -245,7 +240,8 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
245240

246241
async close() {
247242
const disconnected = new Promise(f => this._connection.once(ConnectionEvents.Disconnected, f));
248-
await this._connection.rootSession.send('Browser.close');
243+
await Promise.all(this.browserContexts().map(context => context.close()));
244+
this._connection.close();
249245
await disconnected;
250246
}
251247

@@ -305,12 +301,6 @@ export class CRBrowser extends platform.EventEmitter implements Browser {
305301
return CRTarget.fromPage(page);
306302
}
307303

308-
async disconnect() {
309-
const disconnected = new Promise(f => this.once(CommonEvents.Browser.Disconnected, f));
310-
this._connection.close();
311-
await disconnected;
312-
}
313-
314304
isConnected(): boolean {
315305
return !this._connection._closed;
316306
}

src/firefox/ffBrowser.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,19 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
3737

3838
static async connect(transport: ConnectionTransport, slowMo?: number): Promise<FFBrowser> {
3939
const connection = new FFConnection(SlowMoTransport.wrap(transport, slowMo));
40-
const { browserContextIds } = await connection.send('Target.getBrowserContexts');
41-
const browser = new FFBrowser(connection, browserContextIds);
40+
const browser = new FFBrowser(connection);
4241
await connection.send('Target.enable');
4342
await browser._waitForTarget(t => t.type() === 'page');
4443
return browser;
4544
}
4645

47-
constructor(connection: FFConnection, browserContextIds: Array<string>) {
46+
constructor(connection: FFConnection) {
4847
super();
4948
this._connection = connection;
5049
this._targets = new Map();
5150

5251
this._defaultContext = this._createBrowserContext(null, {});
5352
this._contexts = new Map();
54-
for (const browserContextId of browserContextIds)
55-
this._contexts.set(browserContextId, this._createBrowserContext(browserContextId, {}));
56-
5753
this._connection.on(ConnectionEvents.Disconnected, () => this.emit(Events.Browser.Disconnected));
5854

5955
this._eventListeners = [
@@ -63,12 +59,6 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
6359
];
6460
}
6561

66-
async disconnect() {
67-
const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f));
68-
this._connection.close();
69-
await disconnected;
70-
}
71-
7262
isConnected(): boolean {
7363
return !this._connection._closed;
7464
}
@@ -154,9 +144,10 @@ export class FFBrowser extends platform.EventEmitter implements Browser {
154144
}
155145

156146
async close() {
147+
await Promise.all(this.browserContexts().map(context => context.close()));
157148
helper.removeEventListeners(this._eventListeners);
158-
const disconnected = new Promise(f => this._connection.once(ConnectionEvents.Disconnected, f));
159-
await this._connection.send('Browser.close');
149+
const disconnected = new Promise(f => this.once(Events.Browser.Disconnected, f));
150+
this._connection.close();
160151
await disconnected;
161152
}
162153

src/platform.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -276,14 +276,22 @@ export function fetchUrl(url: string): Promise<string> {
276276
});
277277
}
278278

279-
class WebSocketTransport implements ConnectionTransport {
279+
export class WebSocketTransport implements ConnectionTransport {
280280
private _ws: WebSocket;
281281

282282
onmessage?: (message: string) => void;
283283
onclose?: () => void;
284+
private _connect: Promise<void>;
284285

285-
constructor(ws: WebSocket) {
286-
this._ws = ws;
286+
constructor(url: string) {
287+
this._ws = (isNode ? new NodeWebSocket(url, [], {
288+
perMessageDeflate: false,
289+
maxPayload: 256 * 1024 * 1024, // 256Mb
290+
}) : new WebSocket(url)) as WebSocket;
291+
this._connect = new Promise((fulfill, reject) => {
292+
this._ws.addEventListener('open', () => fulfill());
293+
this._ws.addEventListener('error', event => reject(new Error(event.toString())));
294+
});
287295
this._ws.addEventListener('message', event => {
288296
if (this.onmessage)
289297
this.onmessage.call(null, event.data);
@@ -296,22 +304,12 @@ class WebSocketTransport implements ConnectionTransport {
296304
this._ws.addEventListener('error', () => {});
297305
}
298306

299-
send(message: string) {
307+
async send(message: string) {
308+
await this._connect;
300309
this._ws.send(message);
301310
}
302311

303312
close() {
304313
this._ws.close();
305314
}
306315
}
307-
308-
export function createWebSocketTransport(url: string): Promise<ConnectionTransport> {
309-
return new Promise((resolve, reject) => {
310-
const ws = (isNode ? new NodeWebSocket(url, [], {
311-
perMessageDeflate: false,
312-
maxPayload: 256 * 1024 * 1024, // 256Mb
313-
}) : new WebSocket(url)) as WebSocket;
314-
ws.addEventListener('open', () => resolve(new WebSocketTransport(ws)));
315-
ws.addEventListener('error', reject);
316-
});
317-
}

src/server/chromium.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,9 @@ export class Chromium implements BrowserType {
122122
// We try to gracefully close to prevent crash reporting and core dumps.
123123
// Note that it's fine to reuse the pipe transport, since
124124
// our connection ignores kBrowserCloseMessageId.
125-
const t = transport || await platform.createWebSocketTransport(browserWSEndpoint!);
125+
const t = transport || new platform.WebSocketTransport(browserWSEndpoint!);
126126
const message = { method: 'Browser.close', id: kBrowserCloseMessageId };
127-
t.send(JSON.stringify(message));
127+
await t.send(JSON.stringify(message));
128128
},
129129
onkill: (exitCode, signal) => {
130130
if (browserServer)
@@ -147,7 +147,7 @@ export class Chromium implements BrowserType {
147147
}
148148

149149
async connect(options: ConnectOptions): Promise<CRBrowser> {
150-
const transport = await platform.createWebSocketTransport(options.wsEndpoint);
150+
const transport = new platform.WebSocketTransport(options.wsEndpoint);
151151
return CRBrowser.connect(transport, options.slowMo);
152152
}
153153

src/server/firefox.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class Firefox implements BrowserType {
7070
return browserContext;
7171
}
7272

73-
private async _launchServer(options: LaunchOptions = {}, launchType: LaunchType, userDataDir?: string, port?: number): Promise<{ browserServer: BrowserServer, transport?: ConnectionTransport }> {
73+
private async _launchServer(options: LaunchOptions = {}, connectionType: LaunchType, userDataDir?: string, port?: number): Promise<{ browserServer: BrowserServer, transport?: ConnectionTransport }> {
7474
const {
7575
ignoreDefaultArgs = false,
7676
args = [],
@@ -128,9 +128,9 @@ export class Firefox implements BrowserType {
128128
// We try to gracefully close to prevent crash reporting and core dumps.
129129
// Note that it's fine to reuse the pipe transport, since
130130
// our connection ignores kBrowserCloseMessageId.
131-
const transport = await platform.createWebSocketTransport(browserWSEndpoint);
131+
const transport = new platform.WebSocketTransport(browserWSEndpoint);
132132
const message = { method: 'Browser.close', params: {}, id: kBrowserCloseMessageId };
133-
transport.send(JSON.stringify(message));
133+
await transport.send(JSON.stringify(message));
134134
},
135135
onkill: (exitCode, signal) => {
136136
if (browserServer)
@@ -141,12 +141,12 @@ export class Firefox implements BrowserType {
141141
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Firefox!`);
142142
const match = await waitForLine(launchedProcess, launchedProcess.stdout, /^Juggler listening on (ws:\/\/.*)$/, timeout, timeoutError);
143143
const browserWSEndpoint = match[1];
144-
browserServer = new BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? browserWSEndpoint : null);
145-
return { browserServer, transport: launchType === 'server' ? undefined : await platform.createWebSocketTransport(browserWSEndpoint) };
144+
browserServer = new BrowserServer(launchedProcess, gracefullyClose, connectionType === 'server' ? browserWSEndpoint : null);
145+
return { browserServer, transport: connectionType === 'server' ? undefined : new platform.WebSocketTransport(browserWSEndpoint) };
146146
}
147147

148148
async connect(options: ConnectOptions): Promise<FFBrowser> {
149-
const transport = await platform.createWebSocketTransport(options.wsEndpoint);
149+
const transport = new platform.WebSocketTransport(options.wsEndpoint);
150150
return FFBrowser.connect(transport, options.slowMo);
151151
}
152152

0 commit comments

Comments
 (0)