Skip to content

Commit e8e761f

Browse files
authored
chore: use internal BrowserOptions to unify browsers (#2230)
1 parent 696b40a commit e8e761f

File tree

11 files changed

+128
-105
lines changed

11 files changed

+128
-105
lines changed

src/browser.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ import { Download } from './download';
2121
import type { BrowserServer } from './server/browserServer';
2222
import { Events } from './events';
2323
import { InnerLogger, Log } from './logger';
24+
import * as types from './types';
25+
26+
export type BrowserOptions = {
27+
logger: InnerLogger,
28+
downloadsPath: string,
29+
headful?: boolean,
30+
persistent?: boolean,
31+
slowMo?: number,
32+
viewport?: types.Size | null,
33+
ownedServer?: BrowserServer,
34+
};
2435

2536
export interface Browser extends EventEmitter {
2637
newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
@@ -31,14 +42,12 @@ export interface Browser extends EventEmitter {
3142
}
3243

3344
export abstract class BrowserBase extends EventEmitter implements Browser, InnerLogger {
34-
_downloadsPath: string = '';
45+
readonly _options: BrowserOptions;
3546
private _downloads = new Map<string, Download>();
36-
_ownedServer: BrowserServer | null = null;
37-
readonly _logger: InnerLogger;
3847

39-
constructor(logger: InnerLogger) {
48+
constructor(options: BrowserOptions) {
4049
super();
41-
this._logger = logger;
50+
this._options = options;
4251
}
4352

4453
abstract newContext(options?: BrowserContextOptions): Promise<BrowserContext>;
@@ -54,7 +63,7 @@ export abstract class BrowserBase extends EventEmitter implements Browser, Inner
5463
}
5564

5665
_downloadCreated(page: Page, uuid: string, url: string, suggestedFilename?: string) {
57-
const download = new Download(page, this._downloadsPath, uuid, url, suggestedFilename);
66+
const download = new Download(page, this._options.downloadsPath, uuid, url, suggestedFilename);
5867
this._downloads.set(uuid, download);
5968
}
6069

@@ -74,8 +83,8 @@ export abstract class BrowserBase extends EventEmitter implements Browser, Inner
7483
}
7584

7685
async close() {
77-
if (this._ownedServer) {
78-
await this._ownedServer.close();
86+
if (this._options.ownedServer) {
87+
await this._options.ownedServer.close();
7988
} else {
8089
await Promise.all(this.contexts().map(context => context.close()));
8190
this._disconnect();
@@ -85,11 +94,11 @@ export abstract class BrowserBase extends EventEmitter implements Browser, Inner
8594
}
8695

8796
_isLogEnabled(log: Log): boolean {
88-
return this._logger._isLogEnabled(log);
97+
return this._options.logger._isLogEnabled(log);
8998
}
9099

91100
_log(log: Log, message: string | Error, ...args: any[]) {
92-
return this._logger._log(log, message, ...args);
101+
return this._options.logger._log(log, message, ...args);
93102
}
94103
}
95104

src/chromium/crBrowser.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { BrowserBase } from '../browser';
18+
import { BrowserBase, BrowserOptions } from '../browser';
1919
import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, BrowserContextOptions, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
2020
import { Events as CommonEvents } from '../events';
2121
import { assert, helper } from '../helper';
@@ -29,7 +29,7 @@ import { readProtocolStream } from './crProtocolHelper';
2929
import { Events } from './events';
3030
import { Protocol } from './protocol';
3131
import { CRExecutionContext } from './crExecutionContext';
32-
import { InnerLogger, logError } from '../logger';
32+
import { logError } from '../logger';
3333

3434
export class CRBrowser extends BrowserBase {
3535
readonly _connection: CRConnection;
@@ -44,13 +44,12 @@ export class CRBrowser extends BrowserBase {
4444
private _tracingRecording = false;
4545
private _tracingPath: string | null = '';
4646
private _tracingClient: CRSession | undefined;
47-
readonly _isHeadful: boolean;
4847

49-
static async connect(transport: ConnectionTransport, isPersistent: boolean, logger: InnerLogger, options: { slowMo?: number, headless?: boolean, viewport?: types.Size | null } = {}): Promise<CRBrowser> {
50-
const connection = new CRConnection(SlowMoTransport.wrap(transport, options.slowMo), logger);
51-
const browser = new CRBrowser(connection, logger, { persistent: isPersistent, headful: !options.headless, viewport: options.viewport });
48+
static async connect(transport: ConnectionTransport, options: BrowserOptions): Promise<CRBrowser> {
49+
const connection = new CRConnection(SlowMoTransport.wrap(transport, options.slowMo), options.logger);
50+
const browser = new CRBrowser(connection, options);
5251
const session = connection.rootSession;
53-
if (!isPersistent) {
52+
if (!options.persistent) {
5453
await session.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true });
5554
return browser;
5655
}
@@ -83,14 +82,13 @@ export class CRBrowser extends BrowserBase {
8382
return browser;
8483
}
8584

86-
constructor(connection: CRConnection, logger: InnerLogger, options: { headful?: boolean, persistent?: boolean, viewport?: types.Size | null } = {}) {
87-
super(logger);
85+
constructor(connection: CRConnection, options: BrowserOptions) {
86+
super(options);
8887
this._connection = connection;
8988
this._session = this._connection.rootSession;
9089

9190
if (options.persistent)
9291
this._defaultContext = new CRBrowserContext(this, null, validateBrowserContextOptions({ viewport: options.viewport }));
93-
this._isHeadful = !!options.headful;
9492
this._connection.on(ConnectionEvents.Disconnected, () => {
9593
for (const context of this._contexts.values())
9694
context._browserClosed();
@@ -150,7 +148,7 @@ export class CRBrowser extends BrowserBase {
150148

151149
if (targetInfo.type === 'page') {
152150
const opener = targetInfo.openerId ? this._crPages.get(targetInfo.openerId) || null : null;
153-
const crPage = new CRPage(session, targetInfo.targetId, context, opener, this._isHeadful);
151+
const crPage = new CRPage(session, targetInfo.targetId, context, opener, !!this._options.headful);
154152
this._crPages.set(targetInfo.targetId, crPage);
155153
crPage.pageOrError().then(() => {
156154
context!.emit(CommonEvents.BrowserContext.Page, crPage._page);
@@ -289,7 +287,7 @@ export class CRBrowserContext extends BrowserContextBase {
289287
this._browser._session.send('Browser.setDownloadBehavior', {
290288
behavior: this._options.acceptDownloads ? 'allowAndName' : 'deny',
291289
browserContextId: this._browserContextId || undefined,
292-
downloadPath: this._browser._downloadsPath
290+
downloadPath: this._browser._options.downloadsPath
293291
})
294292
];
295293
if (this._options.permissions)

src/firefox/ffBrowser.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { BrowserBase } from '../browser';
18+
import { BrowserBase, BrowserOptions } from '../browser';
1919
import { assertBrowserContextIsNotOwned, BrowserContext, BrowserContextBase, BrowserContextOptions, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
2020
import { Events } from '../events';
2121
import { assert, helper, RegisteredListener } from '../helper';
@@ -27,7 +27,6 @@ import { ConnectionEvents, FFConnection } from './ffConnection';
2727
import { headersArray } from './ffNetworkManager';
2828
import { FFPage } from './ffPage';
2929
import { Protocol } from './protocol';
30-
import { InnerLogger } from '../logger';
3130

3231
export class FFBrowser extends BrowserBase {
3332
_connection: FFConnection;
@@ -36,19 +35,19 @@ export class FFBrowser extends BrowserBase {
3635
readonly _contexts: Map<string, FFBrowserContext>;
3736
private _eventListeners: RegisteredListener[];
3837

39-
static async connect(transport: ConnectionTransport, logger: InnerLogger, attachToDefaultContext: boolean, slowMo?: number): Promise<FFBrowser> {
40-
const connection = new FFConnection(SlowMoTransport.wrap(transport, slowMo), logger);
41-
const browser = new FFBrowser(connection, logger, attachToDefaultContext);
42-
await connection.send('Browser.enable', { attachToDefaultContext });
38+
static async connect(transport: ConnectionTransport, options: BrowserOptions): Promise<FFBrowser> {
39+
const connection = new FFConnection(SlowMoTransport.wrap(transport, options.slowMo), options.logger);
40+
const browser = new FFBrowser(connection, options);
41+
await connection.send('Browser.enable', { attachToDefaultContext: !!options.persistent });
4342
return browser;
4443
}
4544

46-
constructor(connection: FFConnection, logger: InnerLogger, isPersistent: boolean) {
47-
super(logger);
45+
constructor(connection: FFConnection, options: BrowserOptions) {
46+
super(options);
4847
this._connection = connection;
4948
this._ffPages = new Map();
5049

51-
if (isPersistent)
50+
if (options.persistent)
5251
this._defaultContext = new FFBrowserContext(this, null, validateBrowserContextOptions({}));
5352
this._contexts = new Map();
5453
this._connection.on(ConnectionEvents.Disconnected, () => {
@@ -159,7 +158,7 @@ export class FFBrowserContext extends BrowserContextBase {
159158
browserContextId,
160159
downloadOptions: {
161160
behavior: this._options.acceptDownloads ? 'saveToDisk' : 'cancel',
162-
downloadsDir: this._browser._downloadsPath,
161+
downloadsDir: this._browser._options.downloadsPath,
163162
},
164163
}),
165164
];

src/server/browserType.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ type LaunchOptionsBase = BrowserArgOptions & {
3636
env?: {[key: string]: string|number|boolean}
3737
};
3838

39+
export function processBrowserArgOptions(options: LaunchOptionsBase): { devtools: boolean, headless: boolean } {
40+
const { devtools = false, headless = !devtools } = options;
41+
return { devtools, headless };
42+
}
43+
3944
export type ConnectOptions = {
4045
wsEndpoint: string,
4146
slowMo?: number,

src/server/chromium.ts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import * as ws from 'ws';
2525
import { launchProcess } from './processLauncher';
2626
import { kBrowserCloseMessageId } from '../chromium/crConnection';
2727
import { PipeTransport } from './pipeTransport';
28-
import { LaunchOptions, BrowserArgOptions, ConnectOptions, LaunchServerOptions, AbstractBrowserType } from './browserType';
28+
import { LaunchOptions, BrowserArgOptions, ConnectOptions, LaunchServerOptions, AbstractBrowserType, processBrowserArgOptions } from './browserType';
2929
import { LaunchType } from '../browser';
3030
import { BrowserServer, WebSocketWrapper } from './browserServer';
3131
import { Events } from '../events';
@@ -48,10 +48,13 @@ export class Chromium extends AbstractBrowserType<CRBrowser> {
4848
return await browserServer._initializeOrClose(deadline, async () => {
4949
if ((options as any).__testHookBeforeCreateBrowser)
5050
await (options as any).__testHookBeforeCreateBrowser();
51-
const browser = await CRBrowser.connect(transport!, false, logger, options);
52-
browser._ownedServer = browserServer;
53-
browser._downloadsPath = downloadsPath;
54-
return browser;
51+
return await CRBrowser.connect(transport!, {
52+
slowMo: options.slowMo,
53+
headful: !processBrowserArgOptions(options).headless,
54+
logger,
55+
downloadsPath,
56+
ownedServer: browserServer
57+
});
5558
});
5659
}
5760

@@ -62,12 +65,18 @@ export class Chromium extends AbstractBrowserType<CRBrowser> {
6265
async launchPersistentContext(userDataDir: string, options: LaunchOptions = {}): Promise<BrowserContext> {
6366
const { timeout = 30000 } = options;
6467
const deadline = TimeoutSettings.computeDeadline(timeout);
65-
const { transport, browserServer, logger } = await this._launchServer(options, 'persistent', userDataDir);
68+
const { transport, browserServer, downloadsPath, logger } = await this._launchServer(options, 'persistent', userDataDir);
6669
return await browserServer._initializeOrClose(deadline, async () => {
6770
if ((options as any).__testHookBeforeCreateBrowser)
6871
await (options as any).__testHookBeforeCreateBrowser();
69-
const browser = await CRBrowser.connect(transport!, true, logger, options);
70-
browser._ownedServer = browserServer;
72+
const browser = await CRBrowser.connect(transport!, {
73+
slowMo: options.slowMo,
74+
persistent: true,
75+
logger,
76+
downloadsPath,
77+
headful: !processBrowserArgOptions(options).headless,
78+
ownedServer: browserServer
79+
});
7180
const context = browser._defaultContext!;
7281
if (!options.ignoreDefaultArgs || Array.isArray(options.ignoreDefaultArgs))
7382
await context._loadDefaultContext();
@@ -109,6 +118,11 @@ export class Chromium extends AbstractBrowserType<CRBrowser> {
109118
const chromeExecutable = executablePath || this.executablePath();
110119
if (!chromeExecutable)
111120
throw new Error(`No executable path is specified. Pass "executablePath" option directly.`);
121+
122+
// Note: it is important to define these variables before launchProcess, so that we don't get
123+
// "Cannot access 'browserServer' before initialization" if something went wrong.
124+
let transport: PipeTransport | undefined = undefined;
125+
let browserServer: BrowserServer | undefined = undefined;
112126
const { launchedProcess, gracefullyClose, downloadsPath } = await launchProcess({
113127
executablePath: chromeExecutable,
114128
args: chromeArguments,
@@ -134,8 +148,6 @@ export class Chromium extends AbstractBrowserType<CRBrowser> {
134148
},
135149
});
136150

137-
let transport: PipeTransport | undefined = undefined;
138-
let browserServer: BrowserServer | undefined = undefined;
139151
const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream];
140152
transport = new PipeTransport(stdio[3], stdio[4], logger);
141153
browserServer = new BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? wrapTransportWithWebSocket(transport, logger, port) : null);
@@ -146,16 +158,13 @@ export class Chromium extends AbstractBrowserType<CRBrowser> {
146158
return await WebSocketTransport.connect(options.wsEndpoint, async transport => {
147159
if ((options as any).__testHookBeforeCreateBrowser)
148160
await (options as any).__testHookBeforeCreateBrowser();
149-
return CRBrowser.connect(transport, false, new RootLogger(options.logger), options);
161+
return CRBrowser.connect(transport, { slowMo: options.slowMo, logger: new RootLogger(options.logger), downloadsPath: '' });
150162
});
151163
}
152164

153165
private _defaultArgs(options: BrowserArgOptions = {}, launchType: LaunchType, userDataDir: string): string[] {
154-
const {
155-
devtools = false,
156-
headless = !devtools,
157-
args = [],
158-
} = options;
166+
const { devtools, headless } = processBrowserArgOptions(options);
167+
const { args = [] } = options;
159168
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
160169
if (userDataDirArg)
161170
throw new Error('Pass userDataDir parameter instead of specifying --user-data-dir argument');

src/server/electron.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,7 @@ export class Electron {
204204
return transport;
205205
});
206206
const browserServer = new BrowserServer(launchedProcess, gracefullyClose, null);
207-
const browser = await CRBrowser.connect(chromeTransport, true, logger, { ...options, viewport: null });
208-
browser._ownedServer = browserServer;
207+
const browser = await CRBrowser.connect(chromeTransport, { headful: true, logger, persistent: true, viewport: null, ownedServer: browserServer, downloadsPath: '' });
209208
app = new ElectronApplication(logger, browser, nodeConnection);
210209
await app._init();
211210
return app;

0 commit comments

Comments
 (0)