Skip to content

Commit 7306f9d

Browse files
committed
more cleanup, carefully try/catch
1 parent 277c207 commit 7306f9d

File tree

6 files changed

+166
-134
lines changed

6 files changed

+166
-134
lines changed

packages/playwright-core/src/server/android/android.ts

Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { removeFolders } from '../utils/fileUtils';
3434
import { helper } from '../helper';
3535
import { SdkObject, serverSideCallMetadata } from '../instrumentation';
3636
import { gracefullyCloseSet } from '../utils/processLauncher';
37-
import { isAbortError, Progress, ProgressController } from '../progress';
37+
import { isAbortError, Progress, ProgressController, raceUncancellableOperationWithCleanup } from '../progress';
3838
import { registry } from '../registry';
3939

4040
import type { BrowserOptions, BrowserProcess } from '../browser';
@@ -272,8 +272,13 @@ export class AndroidDevice extends SdkObject {
272272
await progress.race(this._backend.runCommand(`shell:echo "${Buffer.from(commandLine).toString('base64')}" | base64 -d > /data/local/tmp/chrome-command-line`));
273273
await progress.race(this._backend.runCommand(`shell:am start -a android.intent.action.VIEW -d about:blank ${pkg}`));
274274
const browserContext = await this._connectToBrowser(progress, socketName, options);
275-
await progress.race(this._backend.runCommand(`shell:rm /data/local/tmp/chrome-command-line`));
276-
return browserContext;
275+
try {
276+
await progress.race(this._backend.runCommand(`shell:rm /data/local/tmp/chrome-command-line`));
277+
return browserContext;
278+
} catch (error) {
279+
await browserContext.close({ reason: 'Failed to launch' });
280+
throw error;
281+
}
277282
}
278283

279284
private _defaultArgs(options: channels.AndroidDeviceLaunchBrowserParams, socketName: string): string[] {
@@ -314,60 +319,52 @@ export class AndroidDevice extends SdkObject {
314319

315320
private async _connectToBrowser(progress: Progress, socketName: string, options: types.BrowserContextOptions = {}): Promise<BrowserContext> {
316321
const socket = await this._waitForLocalAbstract(progress, socketName);
317-
const androidBrowser = new AndroidBrowser(this, socket);
318-
progress.cleanupWhenAborted(() => androidBrowser.close());
319-
await progress.race(androidBrowser._init());
320-
this._browserConnections.add(androidBrowser);
321-
322-
const artifactsDir = await progress.race(fs.promises.mkdtemp(ARTIFACTS_FOLDER));
323-
const cleanupArtifactsDir = async () => {
324-
const errors = (await removeFolders([artifactsDir])).filter(Boolean);
325-
for (let i = 0; i < (errors || []).length; ++i)
326-
debug('pw:android')(`exception while removing ${artifactsDir}: ${errors[i]}`);
327-
};
328-
progress.cleanupWhenAborted(cleanupArtifactsDir);
329-
gracefullyCloseSet.add(cleanupArtifactsDir);
330-
socket.on('close', async () => {
331-
gracefullyCloseSet.delete(cleanupArtifactsDir);
332-
cleanupArtifactsDir().catch(e => debug('pw:android')(`could not cleanup artifacts dir: ${e}`));
333-
});
334-
const browserOptions: BrowserOptions = {
335-
name: 'clank',
336-
isChromium: true,
337-
slowMo: 0,
338-
persistent: { ...options, noDefaultViewport: true },
339-
artifactsDir,
340-
downloadsPath: artifactsDir,
341-
tracesDir: artifactsDir,
342-
browserProcess: new ClankBrowserProcess(androidBrowser),
343-
proxy: options.proxy,
344-
protocolLogger: helper.debugProtocolLogger(),
345-
browserLogsCollector: new RecentLogsCollector(),
346-
originalLaunchOptions: {},
347-
};
348-
validateBrowserContextOptions(options, browserOptions);
349-
350-
const browser = await progress.race(CRBrowser.connect(this.attribution.playwright, androidBrowser, browserOptions));
351-
const defaultContext = browser._defaultContext!;
352-
await defaultContext._loadDefaultContextAsIs(progress);
353-
return defaultContext;
354-
}
355-
356-
private async _open(progress: Progress, command: string): Promise<SocketBackend> {
357-
let aborted = false;
358322
try {
359-
return await progress.race(this._backend.open(command).then(socket => {
360-
// Make sure to close the opened socket if progress was aborted before that.
361-
if (aborted)
362-
socket.close();
363-
return socket;
364-
}));
323+
const androidBrowser = new AndroidBrowser(this, socket);
324+
await progress.race(androidBrowser._init());
325+
this._browserConnections.add(androidBrowser);
326+
327+
const artifactsDir = await progress.race(fs.promises.mkdtemp(ARTIFACTS_FOLDER));
328+
const cleanupArtifactsDir = async () => {
329+
const errors = (await removeFolders([artifactsDir])).filter(Boolean);
330+
for (let i = 0; i < (errors || []).length; ++i)
331+
debug('pw:android')(`exception while removing ${artifactsDir}: ${errors[i]}`);
332+
};
333+
gracefullyCloseSet.add(cleanupArtifactsDir);
334+
socket.on('close', async () => {
335+
gracefullyCloseSet.delete(cleanupArtifactsDir);
336+
cleanupArtifactsDir().catch(e => debug('pw:android')(`could not cleanup artifacts dir: ${e}`));
337+
});
338+
const browserOptions: BrowserOptions = {
339+
name: 'clank',
340+
isChromium: true,
341+
slowMo: 0,
342+
persistent: { ...options, noDefaultViewport: true },
343+
artifactsDir,
344+
downloadsPath: artifactsDir,
345+
tracesDir: artifactsDir,
346+
browserProcess: new ClankBrowserProcess(androidBrowser),
347+
proxy: options.proxy,
348+
protocolLogger: helper.debugProtocolLogger(),
349+
browserLogsCollector: new RecentLogsCollector(),
350+
originalLaunchOptions: {},
351+
};
352+
validateBrowserContextOptions(options, browserOptions);
353+
354+
const browser = await progress.race(CRBrowser.connect(this.attribution.playwright, androidBrowser, browserOptions));
355+
const defaultContext = browser._defaultContext!;
356+
await defaultContext._loadDefaultContextAsIs(progress);
357+
return defaultContext;
365358
} catch (error) {
366-
aborted = true;
359+
socket.close();
367360
throw error;
368361
}
369362
}
370363

364+
private _open(progress: Progress, command: string): Promise<SocketBackend> {
365+
return raceUncancellableOperationWithCleanup(progress, () => this._backend.open(command), socket => socket.close());
366+
}
367+
371368
webViews(): channels.AndroidWebView[] {
372369
return [...this._webViews.values()];
373370
}

packages/playwright-core/src/server/browser.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,6 @@ export abstract class Browser extends SdkObject {
9292
return this.options.sdkLanguage || this.attribution.playwright.options.sdkLanguage;
9393
}
9494

95-
newContextFromMetadata(metadata: CallMetadata, options: types.BrowserContextOptions): Promise<BrowserContext> {
96-
const controller = new ProgressController(metadata, this);
97-
return controller.run(progress => this.newContext(progress, options));
98-
}
99-
10095
async newContext(progress: Progress, options: types.BrowserContextOptions): Promise<BrowserContext> {
10196
validateBrowserContextOptions(options, this.options);
10297
let clientCertificatesProxy: ClientCertificatesProxy | undefined;

packages/playwright-core/src/server/browserContext.ts

Lines changed: 63 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,14 @@ export abstract class BrowserContext extends SdkObject {
334334
const binding = new PageBinding(name, playwrightBinding, needsHandle);
335335
binding.forClient = forClient;
336336
this._pageBindings.set(name, binding);
337-
progress.cleanupWhenAborted(() => this._pageBindings.delete(name));
338-
await progress.race(this.doAddInitScript(binding.initScript));
339-
await progress.race(this.safeNonStallingEvaluateInAllFrames(binding.initScript.source, 'main'));
340-
return binding;
337+
try {
338+
await progress.race(this.doAddInitScript(binding.initScript));
339+
await progress.race(this.safeNonStallingEvaluateInAllFrames(binding.initScript.source, 'main'));
340+
return binding;
341+
} catch (error) {
342+
this._pageBindings.delete(name);
343+
throw error;
344+
}
341345
}
342346

343347
async removeExposedBindings(bindings: PageBinding[]) {
@@ -370,21 +374,27 @@ export abstract class BrowserContext extends SdkObject {
370374
async setExtraHTTPHeaders(progress: Progress, headers: types.HeadersArray) {
371375
const oldHeaders = this._options.extraHTTPHeaders;
372376
this._options.extraHTTPHeaders = headers;
373-
progress.cleanupWhenAborted(async () => {
377+
try {
378+
await progress.race(this.doUpdateExtraHTTPHeaders());
379+
} catch (error) {
374380
this._options.extraHTTPHeaders = oldHeaders;
375-
await this.doUpdateExtraHTTPHeaders();
376-
});
377-
await progress.race(this.doUpdateExtraHTTPHeaders());
381+
// Note: no await, headers will be reset in the background as soon as possible.
382+
this.doUpdateExtraHTTPHeaders().catch(() => {});
383+
throw error;
384+
}
378385
}
379386

380387
async setOffline(progress: Progress, offline: boolean) {
381388
const oldOffline = this._options.offline;
382389
this._options.offline = offline;
383-
progress.cleanupWhenAborted(async () => {
390+
try {
391+
await progress.race(this.doUpdateOffline());
392+
} catch (error) {
384393
this._options.offline = oldOffline;
385-
await this.doUpdateOffline();
386-
});
387-
await progress.race(this.doUpdateOffline());
394+
// Note: no await, offline will be reset in the background as soon as possible.
395+
this.doUpdateOffline().catch(() => {});
396+
throw error;
397+
}
388398
}
389399

390400
async _loadDefaultContextAsIs(progress: Progress): Promise<Page | undefined> {
@@ -442,13 +452,18 @@ export abstract class BrowserContext extends SdkObject {
442452
async addInitScript(progress: Progress | undefined, source: string) {
443453
const initScript = new InitScript(source);
444454
this.initScripts.push(initScript);
445-
progress?.cleanupWhenAborted(() => this.removeInitScripts([initScript]));
446-
const promise = this.doAddInitScript(initScript);
447-
if (progress)
448-
await progress.race(promise);
449-
else
450-
await promise;
451-
return initScript;
455+
try {
456+
const promise = this.doAddInitScript(initScript);
457+
if (progress)
458+
await progress.race(promise);
459+
else
460+
await promise;
461+
return initScript;
462+
} catch (error) {
463+
// Note: no await, init script will be removed in the background as soon as possible.
464+
this.removeInitScripts([initScript]).catch(() => {});
465+
throw error;
466+
}
452467
}
453468

454469
async removeInitScripts(initScripts: InitScript[]) {
@@ -528,9 +543,8 @@ export abstract class BrowserContext extends SdkObject {
528543
}
529544

530545
async newPage(progress: Progress, isServerSide: boolean): Promise<Page> {
531-
let page: Page | undefined;
546+
const page = await progress.race(this.doCreateNewPage(isServerSide));
532547
try {
533-
page = await progress.race(this.doCreateNewPage(isServerSide));
534548
const pageOrError = await progress.race(page.waitForInitializedOrError());
535549
if (pageOrError instanceof Page) {
536550
if (pageOrError.isClosed())
@@ -539,7 +553,7 @@ export abstract class BrowserContext extends SdkObject {
539553
}
540554
throw pageOrError;
541555
} catch (error) {
542-
await page?.close({ reason: 'Failed to create page' }).catch(() => {});
556+
await page.close({ reason: 'Failed to create page' }).catch(() => {});
543557
throw error;
544558
}
545559
}
@@ -580,17 +594,20 @@ export abstract class BrowserContext extends SdkObject {
580594
// If there are still origins to save, create a blank page to iterate over origins.
581595
if (originsToSave.size) {
582596
const page = await this.newPage(progress, true);
583-
await page.addRequestInterceptor(progress, route => {
584-
route.fulfill({ body: '<html></html>' }).catch(() => {});
585-
}, 'prepend');
586-
for (const origin of originsToSave) {
587-
const frame = page.mainFrame();
588-
await frame.gotoImpl(progress, origin, {});
589-
const storage: SerializedStorage = await progress.race(frame.evaluateExpression(collectScript, { world: 'utility' }));
590-
if (storage.localStorage.length || storage.indexedDB?.length)
591-
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB });
597+
try {
598+
await page.addRequestInterceptor(progress, route => {
599+
route.fulfill({ body: '<html></html>' }).catch(() => {});
600+
}, 'prepend');
601+
for (const origin of originsToSave) {
602+
const frame = page.mainFrame();
603+
await frame.gotoImpl(progress, origin, {});
604+
const storage: SerializedStorage = await progress.race(frame.evaluateExpression(collectScript, { world: 'utility' }));
605+
if (storage.localStorage.length || storage.indexedDB?.length)
606+
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB });
607+
}
608+
} finally {
609+
await page.close();
592610
}
593-
await page.close();
594611
}
595612
return result;
596613
}
@@ -608,33 +625,34 @@ export abstract class BrowserContext extends SdkObject {
608625
const interceptor = (route: network.Route) => {
609626
route.fulfill({ body: '<html></html>' }).catch(() => {});
610627
};
611-
612-
progress.cleanupWhenAborted(() => page.removeRequestInterceptor(interceptor));
613628
await page.addRequestInterceptor(progress, interceptor, 'prepend');
614629

615-
for (const origin of new Set([...oldOrigins, ...newOrigins.keys()])) {
616-
const frame = page.mainFrame();
617-
await frame.gotoImpl(progress, origin, {});
618-
await progress.race(frame.resetStorageForCurrentOriginBestEffort(newOrigins.get(origin)));
619-
}
620-
621-
await page.removeRequestInterceptor(interceptor);
630+
try {
631+
for (const origin of new Set([...oldOrigins, ...newOrigins.keys()])) {
632+
const frame = page.mainFrame();
633+
await frame.gotoImpl(progress, origin, {});
634+
await progress.race(frame.resetStorageForCurrentOriginBestEffort(newOrigins.get(origin)));
635+
}
622636

623-
this._origins = new Set([...newOrigins.keys()]);
624-
// It is safe to not restore the URL to about:blank since we are doing it in Page::resetForReuse.
637+
this._origins = new Set([...newOrigins.keys()]);
638+
// It is safe to not restore the URL to about:blank since we are doing it in Page::resetForReuse.
639+
} finally {
640+
await page.removeRequestInterceptor(interceptor);
641+
}
625642
}
626643

627644
isSettingStorageState(): boolean {
628645
return this._settingStorageState;
629646
}
630647

631648
async setStorageState(progress: Progress, state: NonNullable<channels.BrowserNewContextParams['storageState']>) {
649+
let page: Page | undefined;
632650
this._settingStorageState = true;
633651
try {
634652
if (state.cookies)
635653
await progress.race(this.addCookies(state.cookies));
636654
if (state.origins && state.origins.length) {
637-
const page = await this.newPage(progress, true);
655+
page = await this.newPage(progress, true);
638656
await page.addRequestInterceptor(progress, route => {
639657
route.fulfill({ body: '<html></html>' }).catch(() => {});
640658
}, 'prepend');
@@ -649,12 +667,12 @@ export abstract class BrowserContext extends SdkObject {
649667
})()`;
650668
await progress.race(frame.evaluateExpression(restoreScript, { world: 'utility' }));
651669
}
652-
await page.close();
653670
}
654671
} catch (error) {
655672
rewriteErrorMessage(error, `Error setting storage state:\n` + error.message);
656673
throw error;
657674
} finally {
675+
await page?.close();
658676
this._settingStorageState = false;
659677
}
660678
}

0 commit comments

Comments
 (0)