Skip to content

Commit b0bb104

Browse files
authored
chore: merge setStorageState and resetStorage (microsoft#36860)
1 parent c2763fe commit b0bb104

File tree

6 files changed

+67
-90
lines changed

6 files changed

+67
-90
lines changed

packages/injected/src/storageScript.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,13 +176,35 @@ export class StorageScript {
176176
}));
177177
}
178178

179-
async restore(originState: channels.SetOriginStorage) {
179+
async restore(originState: channels.SetOriginStorage | undefined) {
180+
// Clean Service Workers.
181+
const registrations = this._global.navigator.serviceWorker ? await this._global.navigator.serviceWorker.getRegistrations() : [];
182+
await Promise.all(registrations.map(async r => {
183+
// Heuristic for service workers that stalled during main script fetch or importScripts:
184+
// Waiting for them to finish unregistering takes ages so we do not await.
185+
// However, they will unregister immediately after fetch finishes and should not affect next page load.
186+
// Unfortunately, loading next page in Chromium still takes 5 seconds waiting for
187+
// some operation on this bogus service worker to finish.
188+
if (!r.installing && !r.waiting && !r.active)
189+
r.unregister().catch(() => {});
190+
else
191+
await r.unregister().catch(() => {});
192+
}));
193+
180194
try {
181-
await Promise.all((originState.indexedDB ?? []).map(dbInfo => this._restoreDB(dbInfo)));
195+
for (const db of await this._global.indexedDB.databases?.() || []) {
196+
// Do not wait for the callback - it is called on timer in Chromium (slow).
197+
if (db.name)
198+
this._global.indexedDB.deleteDatabase(db.name!);
199+
}
200+
await Promise.all((originState?.indexedDB ?? []).map(dbInfo => this._restoreDB(dbInfo)));
182201
} catch (e) {
183202
throw new Error('Unable to restore IndexedDB: ' + e.message);
184203
}
185-
for (const { name, value } of (originState.localStorage || []))
204+
205+
this._global.sessionStorage.clear();
206+
this._global.localStorage.clear();
207+
for (const { name, value } of (originState?.localStorage || []))
186208
this._global.localStorage.setItem(name, value);
187209
}
188210
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,7 @@ export abstract class Browser extends SdkObject {
105105
context._clientCertificatesProxy = clientCertificatesProxy;
106106
if ((options as any).__testHookBeforeSetStorageState)
107107
await progress.race((options as any).__testHookBeforeSetStorageState());
108-
if (options.storageState)
109-
await context.setStorageState(progress, options.storageState);
108+
await context.setStorageState(progress, options.storageState, 'initial');
110109
this.emit(Browser.Events.Context, context);
111110
return context;
112111
} catch (error) {

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

Lines changed: 38 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export abstract class BrowserContext extends SdkObject {
8585
readonly fetchRequest: BrowserContextAPIRequestContext;
8686
private _customCloseHandler?: () => Promise<any>;
8787
readonly _tempDirs: string[] = [];
88-
private _settingStorageState = false;
88+
private _creatingStorageStatePage = false;
8989
bindingsInitScript?: InitScript;
9090
initScripts: InitScript[] = [];
9191
private _routesInFlight = new Set<network.Route>();
@@ -209,15 +209,11 @@ export abstract class BrowserContext extends SdkObject {
209209

210210
// Note: we only need to reset properties from the "paramsThatAllowContextReuse" list.
211211
// All other properties force a new context.
212-
await this._resetStorage(progress);
213212
await this.clock.uninstall(progress);
214213
await progress.race(this.setUserAgent(this._options.userAgent));
215-
await progress.race(this.clearCache());
216-
await progress.race(this.doClearCookies());
217214
await progress.race(this.doUpdateDefaultEmulatedMedia());
218215
await progress.race(this.doUpdateDefaultViewport());
219-
if (this._options.storageState?.cookies)
220-
await progress.race(this.addCookies(this._options.storageState?.cookies));
216+
await this.setStorageState(progress, this._options.storageState, 'reset');
221217

222218
await page?.resetForReuse(progress);
223219
}
@@ -612,68 +608,62 @@ export abstract class BrowserContext extends SdkObject {
612608
return result;
613609
}
614610

615-
async _resetStorage(progress: Progress) {
616-
const oldOrigins = this._origins;
617-
const newOrigins = new Map(this._options.storageState?.origins?.map(p => [p.origin, p]) || []);
618-
if (!oldOrigins.size && !newOrigins.size)
619-
return;
620-
let page = this.pages()[0];
621-
622-
// Do not mark this page as internal, because we will leave it for later reuse
623-
// as a user-visible page.
624-
page = page || await this.newPage(progress, false);
625-
const interceptor = (route: network.Route) => {
626-
route.fulfill({ body: '<html></html>' }).catch(() => {});
627-
};
628-
await page.addRequestInterceptor(progress, interceptor, 'prepend');
611+
isCreatingStorageStatePage(): boolean {
612+
return this._creatingStorageStatePage;
613+
}
629614

615+
async setStorageState(progress: Progress, state: channels.BrowserNewContextParams['storageState'], mode: 'initial' | 'reset') {
616+
let page: Page | undefined;
617+
let interceptor: network.RouteHandler | undefined;
630618
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)));
619+
if (mode === 'reset') {
620+
await progress.race(this.clearCache());
621+
await progress.race(this.doClearCookies());
635622
}
636623

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-
}
642-
}
624+
if (state?.cookies)
625+
await progress.race(this.addCookies(state.cookies));
643626

644-
isSettingStorageState(): boolean {
645-
return this._settingStorageState;
646-
}
627+
const newOrigins = new Map(state?.origins?.map(p => [p.origin, p]) || []);
628+
const allOrigins = new Set([...this._origins, ...newOrigins.keys()]);
629+
if (allOrigins.size) {
630+
if (mode === 'reset')
631+
page = this.pages()[0];
632+
if (!page) {
633+
try {
634+
this._creatingStorageStatePage = mode === 'initial';
635+
page = await this.newPage(progress, this._creatingStorageStatePage);
636+
} finally {
637+
this._creatingStorageStatePage = false;
638+
}
639+
}
647640

648-
async setStorageState(progress: Progress, state: NonNullable<channels.BrowserNewContextParams['storageState']>) {
649-
let page: Page | undefined;
650-
this._settingStorageState = true;
651-
try {
652-
if (state.cookies)
653-
await progress.race(this.addCookies(state.cookies));
654-
if (state.origins && state.origins.length) {
655-
page = await this.newPage(progress, true);
656-
await page.addRequestInterceptor(progress, route => {
641+
interceptor = (route: network.Route) => {
657642
route.fulfill({ body: '<html></html>' }).catch(() => {});
658-
}, 'prepend');
659-
for (const originState of state.origins) {
643+
};
644+
await page.addRequestInterceptor(progress, interceptor, 'prepend');
645+
646+
for (const origin of allOrigins) {
660647
const frame = page.mainFrame();
661-
await frame.gotoImpl(progress, originState.origin, {});
648+
await frame.gotoImpl(progress, origin, {});
662649
const restoreScript = `(() => {
663650
const module = {};
664651
${rawStorageSource.source}
665652
const script = new (module.exports.StorageScript())(${this._browser.options.name === 'firefox'});
666-
return script.restore(${JSON.stringify(originState)});
653+
return script.restore(${JSON.stringify(newOrigins.get(origin))});
667654
})()`;
668655
await progress.race(frame.evaluateExpression(restoreScript, { world: 'utility' }));
669656
}
670657
}
658+
this._origins = new Set([...newOrigins.keys()]);
671659
} catch (error) {
672660
rewriteErrorMessage(error, `Error setting storage state:\n` + error.message);
673661
throw error;
674662
} finally {
675-
await page?.close();
676-
this._settingStorageState = false;
663+
if (mode === 'initial')
664+
await page?.close();
665+
else if (interceptor)
666+
await page?.removeRequestInterceptor(interceptor);
677667
}
678668
}
679669

packages/playwright-core/src/server/chromium/crPage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ class FrameSession {
446446
}
447447

448448
async _initialize(hasUIWindow: boolean) {
449-
const isSettingStorageState = this._page.browserContext.isSettingStorageState();
449+
const isSettingStorageState = this._page.browserContext.isCreatingStorageStatePage();
450450
if (!isSettingStorageState && hasUIWindow &&
451451
!this._crPage._browserContext._browser.isClank() &&
452452
!this._crPage._browserContext._options.noDefaultViewport) {

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

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,40 +1627,6 @@ export class Frame extends SdkObject {
16271627
}, { source, arg });
16281628
}
16291629

1630-
async resetStorageForCurrentOriginBestEffort(newStorage: channels.SetOriginStorage | undefined) {
1631-
const context = await this._utilityContext();
1632-
await context.evaluate(async ({ ls }) => {
1633-
// Clean DOMStorage.
1634-
sessionStorage.clear();
1635-
localStorage.clear();
1636-
1637-
// Add new DOM Storage values.
1638-
for (const entry of ls || [])
1639-
localStorage[entry.name] = entry.value;
1640-
1641-
// Clean Service Workers
1642-
const registrations = navigator.serviceWorker ? await navigator.serviceWorker.getRegistrations() : [];
1643-
await Promise.all(registrations.map(async r => {
1644-
// Heuristic for service workers that stalled during main script fetch or importScripts:
1645-
// Waiting for them to finish unregistering takes ages so we do not await.
1646-
// However, they will unregister immediately after fetch finishes and should not affect next page load.
1647-
// Unfortunately, loading next page in Chromium still takes 5 seconds waiting for
1648-
// some operation on this bogus service worker to finish.
1649-
if (!r.installing && !r.waiting && !r.active)
1650-
r.unregister().catch(() => {});
1651-
else
1652-
await r.unregister().catch(() => {});
1653-
}));
1654-
1655-
// Clean IndexedDB
1656-
for (const db of await indexedDB.databases?.() || []) {
1657-
// Do not wait for the callback - it is called on timer in Chromium (slow).
1658-
if (db.name)
1659-
indexedDB.deleteDatabase(db.name!);
1660-
}
1661-
}, { ls: newStorage?.localStorage }).catch(() => {});
1662-
}
1663-
16641630
private _asLocator(selector: string) {
16651631
return asLocator(this._page.browserContext._browser.sdkLanguage(), selector);
16661632
}

packages/playwright-core/src/server/webkit/wkPage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export class WKPage implements PageDelegate {
113113
}
114114

115115
private async _initializePageProxySession() {
116-
if (this._page.browserContext.isSettingStorageState())
116+
if (this._page.browserContext.isCreatingStorageStatePage())
117117
return;
118118
const promises: Promise<any>[] = [
119119
this._pageProxySession.send('Dialog.enable'),
@@ -187,7 +187,7 @@ export class WKPage implements PageDelegate {
187187
promises.push(session.send('Network.setResourceCachingDisabled', { disabled: true }));
188188
promises.push(session.send('Network.addInterception', { url: '.*', stage: 'request', isRegex: true }));
189189
}
190-
if (this._page.browserContext.isSettingStorageState()) {
190+
if (this._page.browserContext.isCreatingStorageStatePage()) {
191191
await Promise.all(promises);
192192
return;
193193
}

0 commit comments

Comments
 (0)