Skip to content

Commit f4dcfd6

Browse files
committed
add encoded versions
1 parent 311625b commit f4dcfd6

File tree

8 files changed

+72
-16
lines changed

8 files changed

+72
-16
lines changed

docs/src/api/class-browsercontext.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,7 +1527,9 @@ Whether to emulate network being offline for the browser context.
15271527
- `multiEntry` <[boolean]>
15281528
- `records` <[Array]<[Object]>>
15291529
- `key` ?<[Object]>
1530+
- `keyEncoded` ?<[Object]> if `key` is not JSON-serializable, this contains an encoded version that preserves types.
15301531
- `value` <[Object]>
1532+
- `valueEncoded` ?<[Object]> if `value` is not JSON-serializable, this contains an encoded version that preserves types.
15311533

15321534
Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
15331535

packages/playwright-core/src/protocol/validator.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ scheme.IndexedDBDatabase = tObject({
152152
keyPathArray: tOptional(tArray(tString)),
153153
records: tArray(tObject({
154154
key: tOptional(tAny),
155-
value: tAny,
155+
keyEncoded: tOptional(tAny),
156+
value: tOptional(tAny),
157+
valueEncoded: tOptional(tAny),
156158
})),
157159
indexes: tArray(tObject({
158160
name: tString,

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { Clock } from './clock';
4444
import type { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
4545
import { RecorderApp } from './recorder/recorderApp';
4646
import * as storageScript from './storageScript';
47+
import * as utilityScriptSerializers from './isomorphic/utilityScriptSerializers';
4748

4849
export abstract class BrowserContext extends SdkObject {
4950
static Events = {
@@ -520,7 +521,7 @@ export abstract class BrowserContext extends SdkObject {
520521
if (!origin || !originsToSave.has(origin))
521522
continue;
522523
try {
523-
const storage: storageScript.Storage = await page.mainFrame().nonStallingEvaluateInExistingContext(`(${storageScript.collect})()`, 'utility');
524+
const storage: storageScript.Storage = await page.mainFrame().nonStallingEvaluateInExistingContext(`(${storageScript.collect})((${utilityScriptSerializers.source})())`, 'utility');
524525
if (storage.localStorage.length || storage.indexedDB.length)
525526
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB });
526527
originsToSave.delete(origin);
@@ -540,7 +541,7 @@ export abstract class BrowserContext extends SdkObject {
540541
for (const origin of originsToSave) {
541542
const frame = page.mainFrame();
542543
await frame.goto(internalMetadata, origin);
543-
const storage: Awaited<ReturnType<typeof storageScript.collect>> = await frame.evaluateExpression(`(${storageScript.collect})()`, { world: 'utility' });
544+
const storage: Awaited<ReturnType<typeof storageScript.collect>> = await frame.evaluateExpression(`(${storageScript.collect})((${utilityScriptSerializers.source})())`, { world: 'utility' });
544545
if (storage.localStorage.length || storage.indexedDB.length)
545546
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB });
546547
}
@@ -605,7 +606,7 @@ export abstract class BrowserContext extends SdkObject {
605606
for (const originState of state.origins) {
606607
const frame = page.mainFrame();
607608
await frame.goto(metadata, originState.origin);
608-
await frame.evaluateExpression(storageScript.restore.toString(), { isFunction: true, world: 'utility' }, originState);
609+
await frame.evaluateExpression(`(${storageScript.restore})(${JSON.stringify(originState)}, (${utilityScriptSerializers.source})())`, { world: 'utility' });
609610
}
610611
await page.close(internalMetadata);
611612
}

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

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
*/
1616

1717
import type * as channels from '@protocol/channels';
18+
import type { source } from './isomorphic/utilityScriptSerializers';
1819

1920
export type Storage = Omit<channels.OriginStorage, 'origin'>;
2021

21-
export async function collect(): Promise<Storage> {
22+
export async function collect(serializers: ReturnType<typeof source>): Promise<Storage> {
2223
const idbResult = await Promise.all((await indexedDB.databases()).map(async dbInfo => {
2324
if (!dbInfo.name)
2425
throw new Error('Database name is empty');
@@ -32,17 +33,53 @@ export async function collect(): Promise<Storage> {
3233
});
3334
}
3435

36+
function trySerialize(value: any): { trivial?: any, encoded?: any } {
37+
let trivial = true;
38+
const encoded = serializers.serializeAsCallArgument(value, v => {
39+
const isTrivial = (
40+
v?.constructor === Object
41+
|| Array.isArray(v)
42+
|| typeof v === 'string'
43+
|| typeof v === 'number'
44+
|| typeof v === 'boolean'
45+
|| Object.is(v, null)
46+
);
47+
48+
if (!isTrivial)
49+
trivial = false;
50+
51+
return { fallThrough: v };
52+
});
53+
if (trivial)
54+
return { trivial: value };
55+
return { encoded };
56+
}
57+
3558
const db = await idbRequestToPromise(indexedDB.open(dbInfo.name));
3659
const transaction = db.transaction(db.objectStoreNames, 'readonly');
3760
const stores = await Promise.all([...db.objectStoreNames].map(async storeName => {
3861
const objectStore = transaction.objectStore(storeName);
3962

4063
const keys = await idbRequestToPromise(objectStore.getAllKeys());
4164
const records = await Promise.all(keys.map(async key => {
42-
return {
43-
key: objectStore.keyPath === null ? key : undefined,
44-
value: await idbRequestToPromise(objectStore.get(key))
45-
};
65+
const record: channels.OriginStorage['indexedDB'][0]['stores'][0]['records'][0] = {};
66+
67+
if (objectStore.keyPath === null) {
68+
const { encoded, trivial } = trySerialize(key);
69+
if (trivial)
70+
record.key = trivial;
71+
else
72+
record.keyEncoded = encoded;
73+
}
74+
75+
const value = await idbRequestToPromise(objectStore.get(key));
76+
const { encoded, trivial } = trySerialize(value);
77+
if (trivial)
78+
record.value = trivial;
79+
else
80+
record.valueEncoded = encoded;
81+
82+
return record;
4683
}));
4784

4885
const indexes = [...objectStore.indexNames].map(indexName => {
@@ -81,7 +118,7 @@ export async function collect(): Promise<Storage> {
81118
};
82119
}
83120

84-
export async function restore(originState: channels.SetOriginStorage) {
121+
export async function restore(originState: channels.SetOriginStorage, serializers: ReturnType<typeof source>) {
85122
for (const { name, value } of (originState.localStorage || []))
86123
localStorage.setItem(name, value);
87124

@@ -111,8 +148,8 @@ export async function restore(originState: channels.SetOriginStorage) {
111148
await Promise.all(store.records.map(async record => {
112149
await idbRequestToPromise(
113150
objectStore.add(
114-
record.value,
115-
objectStore.keyPath === null ? record.key : undefined
151+
record.value ?? serializers.parseEvaluationResultValue(record.valueEncoded),
152+
record.key ?? serializers.parseEvaluationResultValue(record.keyEncoded),
116153
)
117154
);
118155
}));

packages/playwright-core/types/types.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9338,7 +9338,17 @@ export interface BrowserContext {
93389338
records: Array<{
93399339
key?: Object;
93409340

9341+
/**
9342+
* if `key` is not JSON-serializable, this contains an encoded version that preserves types.
9343+
*/
9344+
keyEncoded?: Object;
9345+
93419346
value: Object;
9347+
9348+
/**
9349+
* if `value` is not JSON-serializable, this contains an encoded version that preserves types.
9350+
*/
9351+
valueEncoded?: Object;
93429352
}>;
93439353
}>;
93449354
}>;

packages/protocol/src/channels.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,9 @@ export type IndexedDBDatabase = {
281281
keyPathArray?: string[],
282282
records: {
283283
key?: any,
284-
value: any,
284+
keyEncoded?: any,
285+
value?: any,
286+
valueEncoded?: any,
285287
}[],
286288
indexes: {
287289
name: string,

packages/protocol/src/protocol.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,9 @@ IndexedDBDatabase:
244244
type: object
245245
properties:
246246
key: json?
247-
value: json
247+
keyEncoded: json?
248+
value: json?
249+
valueEncoded: json?
248250
indexes:
249251
type: array
250252
items:

tests/library/browsercontext-storage-state.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ it('should round-trip through the file', async ({ contextFactory }, testInfo) =>
9696
openRequest.onsuccess = () => {
9797
const request = openRequest.result.transaction('store', 'readwrite')
9898
.objectStore('store')
99-
.put('foo', 'bar');
99+
.put({ name: 'foo', date: new Date(0) }, 'bar');
100100
request.addEventListener('success', resolve);
101101
request.addEventListener('error', reject);
102102
};
@@ -131,7 +131,7 @@ it('should round-trip through the file', async ({ contextFactory }, testInfo) =>
131131
});
132132
openRequest.addEventListener('error', () => reject(openRequest.error));
133133
}));
134-
expect(idbValue).toEqual('foo');
134+
expect(idbValue).toEqual({ name: 'foo', date: new Date(0) });
135135
await context2.close();
136136
});
137137

0 commit comments

Comments
 (0)