mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: support typed arrays in indexeddb (#34949)
This commit is contained in:
parent
febb95a638
commit
3340855109
@ -1527,10 +1527,6 @@ Returns storage state for this browser context, contains current cookies, local
|
|||||||
Set to `true` to include [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) in the storage state snapshot.
|
Set to `true` to include [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) in the storage state snapshot.
|
||||||
If your application uses IndexedDB to store authentication tokens, like Firebase Authentication, enable this.
|
If your application uses IndexedDB to store authentication tokens, like Firebase Authentication, enable this.
|
||||||
|
|
||||||
:::note
|
|
||||||
IndexedDBs with typed arrays are currently not supported.
|
|
||||||
:::
|
|
||||||
|
|
||||||
## property: BrowserContext.tracing
|
## property: BrowserContext.tracing
|
||||||
* since: v1.12
|
* since: v1.12
|
||||||
- type: <[Tracing]>
|
- type: <[Tracing]>
|
||||||
|
|||||||
3
packages/playwright-client/types/types.d.ts
vendored
3
packages/playwright-client/types/types.d.ts
vendored
@ -9274,9 +9274,6 @@ export interface BrowserContext {
|
|||||||
* Set to `true` to include [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) in the storage
|
* Set to `true` to include [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) in the storage
|
||||||
* state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase Authentication,
|
* state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase Authentication,
|
||||||
* enable this.
|
* enable this.
|
||||||
*
|
|
||||||
* **NOTE** IndexedDBs with typed arrays are currently not supported.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
indexedDB?: boolean;
|
indexedDB?: boolean;
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,10 @@ function innerParseSerializedValue(value: SerializedValue, handles: any[] | unde
|
|||||||
}
|
}
|
||||||
if (value.r !== undefined)
|
if (value.r !== undefined)
|
||||||
return new RegExp(value.r.p, value.r.f);
|
return new RegExp(value.r.p, value.r.f);
|
||||||
|
if (value.ta !== undefined) {
|
||||||
|
const ctor = typedArrayKindToConstructor[value.ta.k] as any;
|
||||||
|
return new ctor(value.ta.b.buffer, value.ta.b.byteOffset, value.ta.b.length);
|
||||||
|
}
|
||||||
|
|
||||||
if (value.a !== undefined) {
|
if (value.a !== undefined) {
|
||||||
const result: any[] = [];
|
const result: any[] = [];
|
||||||
@ -128,6 +132,10 @@ function innerSerializeValue(value: any, handleSerializer: (value: any) => Handl
|
|||||||
if (isRegExp(value))
|
if (isRegExp(value))
|
||||||
return { r: { p: value.source, f: value.flags } };
|
return { r: { p: value.source, f: value.flags } };
|
||||||
|
|
||||||
|
const typedArrayKind = constructorToTypedArrayKind.get(value.constructor);
|
||||||
|
if (typedArrayKind)
|
||||||
|
return { ta: { b: Buffer.from(value.buffer, value.byteOffset, value.length), k: typedArrayKind } };
|
||||||
|
|
||||||
const id = visitorInfo.visited.get(value);
|
const id = visitorInfo.visited.get(value);
|
||||||
if (id)
|
if (id)
|
||||||
return { ref: id };
|
return { ref: id };
|
||||||
@ -178,3 +186,20 @@ function isError(obj: any): obj is Error {
|
|||||||
const proto = obj ? Object.getPrototypeOf(obj) : null;
|
const proto = obj ? Object.getPrototypeOf(obj) : null;
|
||||||
return obj instanceof Error || proto?.name === 'Error' || (proto && isError(proto));
|
return obj instanceof Error || proto?.name === 'Error' || (proto && isError(proto));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type TypedArrayKind = NonNullable<SerializedValue['ta']>['k'];
|
||||||
|
const typedArrayKindToConstructor: Record<TypedArrayKind, Function> = {
|
||||||
|
i8: Int8Array,
|
||||||
|
ui8: Uint8Array,
|
||||||
|
ui8c: Uint8ClampedArray,
|
||||||
|
i16: Int16Array,
|
||||||
|
ui16: Uint16Array,
|
||||||
|
i32: Int32Array,
|
||||||
|
ui32: Uint32Array,
|
||||||
|
f32: Float32Array,
|
||||||
|
f64: Float64Array,
|
||||||
|
bi64: BigInt64Array,
|
||||||
|
bui64: BigUint64Array,
|
||||||
|
};
|
||||||
|
const constructorToTypedArrayKind: Map<Function, TypedArrayKind> = new Map(Object.entries(typedArrayKindToConstructor).map(([k, v]) => [v, k as TypedArrayKind]));
|
||||||
|
|||||||
@ -58,6 +58,10 @@ scheme.SerializedValue = tObject({
|
|||||||
d: tOptional(tString),
|
d: tOptional(tString),
|
||||||
u: tOptional(tString),
|
u: tOptional(tString),
|
||||||
bi: tOptional(tString),
|
bi: tOptional(tString),
|
||||||
|
ta: tOptional(tObject({
|
||||||
|
b: tBinary,
|
||||||
|
k: tEnum(['i8', 'ui8', 'ui8c', 'i16', 'ui16', 'i32', 'ui32', 'f32', 'f64', 'bi64', 'bui64']),
|
||||||
|
})),
|
||||||
e: tOptional(tObject({
|
e: tOptional(tObject({
|
||||||
m: tString,
|
m: tString,
|
||||||
n: tString,
|
n: tString,
|
||||||
|
|||||||
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
import type { Builtins } from './builtins';
|
import type { Builtins } from './builtins';
|
||||||
|
|
||||||
|
type TypedArrayKind = 'i8' | 'ui8' | 'ui8c' | 'i16' | 'ui16' | 'i32' | 'ui32' | 'f32' | 'f64' | 'bi64' | 'bui64';
|
||||||
|
|
||||||
export type SerializedValue =
|
export type SerializedValue =
|
||||||
undefined | boolean | number | string |
|
undefined | boolean | number | string |
|
||||||
{ v: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0' } |
|
{ v: 'null' | 'undefined' | 'NaN' | 'Infinity' | '-Infinity' | '-0' } |
|
||||||
@ -27,7 +29,8 @@ export type SerializedValue =
|
|||||||
{ a: SerializedValue[], id: number } |
|
{ a: SerializedValue[], id: number } |
|
||||||
{ o: { k: string, v: SerializedValue }[], id: number } |
|
{ o: { k: string, v: SerializedValue }[], id: number } |
|
||||||
{ ref: number } |
|
{ ref: number } |
|
||||||
{ h: number };
|
{ h: number } |
|
||||||
|
{ ta: { b: string, k: TypedArrayKind } };
|
||||||
|
|
||||||
type HandleOrValue = { h: number } | { fallThrough: any };
|
type HandleOrValue = { h: number } | { fallThrough: any };
|
||||||
|
|
||||||
@ -70,6 +73,48 @@ export function source(builtins: Builtins) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTypedArray(obj: any, constructor: Function): boolean {
|
||||||
|
try {
|
||||||
|
return obj instanceof constructor || Object.prototype.toString.call(obj) === `[object ${constructor.name}]`;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const typedArrayConstructors: Record<TypedArrayKind, Function> = {
|
||||||
|
i8: Int8Array,
|
||||||
|
ui8: Uint8Array,
|
||||||
|
ui8c: Uint8ClampedArray,
|
||||||
|
i16: Int16Array,
|
||||||
|
ui16: Uint16Array,
|
||||||
|
i32: Int32Array,
|
||||||
|
ui32: Uint32Array,
|
||||||
|
// TODO: add Float16Array once it's in baseline
|
||||||
|
f32: Float32Array,
|
||||||
|
f64: Float64Array,
|
||||||
|
bi64: BigInt64Array,
|
||||||
|
bui64: BigUint64Array,
|
||||||
|
};
|
||||||
|
|
||||||
|
function typedArrayToBase64(array: any) {
|
||||||
|
/**
|
||||||
|
* Firefox does not support iterating over typed arrays, so we use `.toBase64`.
|
||||||
|
* Error: 'Accessing TypedArray data over Xrays is slow, and forbidden in order to encourage performant code. To copy TypedArrays across origin boundaries, consider using Components.utils.cloneInto().'
|
||||||
|
*/
|
||||||
|
if ('toBase64' in array)
|
||||||
|
return array.toBase64();
|
||||||
|
const binary = Array.from(new Uint8Array(array.buffer, array.byteOffset, array.byteLength)).map(b => String.fromCharCode(b)).join('');
|
||||||
|
return btoa(binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
function base64ToTypedArray(base64: string, TypedArrayConstructor: any) {
|
||||||
|
const binary = atob(base64);
|
||||||
|
const bytes = new Uint8Array(binary.length);
|
||||||
|
for (let i = 0; i < binary.length; i++)
|
||||||
|
bytes[i] = binary.charCodeAt(i);
|
||||||
|
return new TypedArrayConstructor(bytes.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
function parseEvaluationResultValue(value: SerializedValue, handles: any[] = [], refs: Builtins.Map<number, object> = new builtins.Map()): any {
|
function parseEvaluationResultValue(value: SerializedValue, handles: any[] = [], refs: Builtins.Map<number, object> = new builtins.Map()): any {
|
||||||
if (Object.is(value, undefined))
|
if (Object.is(value, undefined))
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -121,6 +166,8 @@ export function source(builtins: Builtins) {
|
|||||||
}
|
}
|
||||||
if ('h' in value)
|
if ('h' in value)
|
||||||
return handles[value.h];
|
return handles[value.h];
|
||||||
|
if ('ta' in value)
|
||||||
|
return base64ToTypedArray(value.ta.b, typedArrayConstructors[value.ta.k]);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -191,6 +238,10 @@ export function source(builtins: Builtins) {
|
|||||||
return { u: value.toJSON() };
|
return { u: value.toJSON() };
|
||||||
if (isRegExp(value))
|
if (isRegExp(value))
|
||||||
return { r: { p: value.source, f: value.flags } };
|
return { r: { p: value.source, f: value.flags } };
|
||||||
|
for (const [k, ctor] of Object.entries(typedArrayConstructors) as [TypedArrayKind, Function][]) {
|
||||||
|
if (isTypedArray(value, ctor))
|
||||||
|
return { ta: { b: typedArrayToBase64(value), k } };
|
||||||
|
}
|
||||||
|
|
||||||
const id = visitorInfo.visited.get(value);
|
const id = visitorInfo.visited.get(value);
|
||||||
if (id)
|
if (id)
|
||||||
|
|||||||
3
packages/playwright-core/types/types.d.ts
vendored
3
packages/playwright-core/types/types.d.ts
vendored
@ -9274,9 +9274,6 @@ export interface BrowserContext {
|
|||||||
* Set to `true` to include [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) in the storage
|
* Set to `true` to include [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) in the storage
|
||||||
* state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase Authentication,
|
* state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase Authentication,
|
||||||
* enable this.
|
* enable this.
|
||||||
*
|
|
||||||
* **NOTE** IndexedDBs with typed arrays are currently not supported.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
indexedDB?: boolean;
|
indexedDB?: boolean;
|
||||||
|
|
||||||
|
|||||||
4
packages/protocol/src/channels.d.ts
vendored
4
packages/protocol/src/channels.d.ts
vendored
@ -180,6 +180,10 @@ export type SerializedValue = {
|
|||||||
d?: string,
|
d?: string,
|
||||||
u?: string,
|
u?: string,
|
||||||
bi?: string,
|
bi?: string,
|
||||||
|
ta?: {
|
||||||
|
b: Binary,
|
||||||
|
k: 'i8' | 'ui8' | 'ui8c' | 'i16' | 'ui16' | 'i32' | 'ui32' | 'f32' | 'f64' | 'bi64' | 'bui64',
|
||||||
|
},
|
||||||
e?: {
|
e?: {
|
||||||
m: string,
|
m: string,
|
||||||
n: string,
|
n: string,
|
||||||
|
|||||||
@ -82,6 +82,25 @@ SerializedValue:
|
|||||||
u: string?
|
u: string?
|
||||||
# String representation of BigInt.
|
# String representation of BigInt.
|
||||||
bi: string?
|
bi: string?
|
||||||
|
# Typed array.
|
||||||
|
ta:
|
||||||
|
type: object?
|
||||||
|
properties:
|
||||||
|
b: binary
|
||||||
|
k:
|
||||||
|
type: enum
|
||||||
|
literals:
|
||||||
|
- i8
|
||||||
|
- ui8
|
||||||
|
- ui8c
|
||||||
|
- i16
|
||||||
|
- ui16
|
||||||
|
- i32
|
||||||
|
- ui32
|
||||||
|
- f32
|
||||||
|
- f64
|
||||||
|
- bi64
|
||||||
|
- bui64
|
||||||
# Serialized Error object.
|
# Serialized Error object.
|
||||||
e:
|
e:
|
||||||
type: object?
|
type: object?
|
||||||
|
|||||||
@ -98,7 +98,7 @@ it('should round-trip through the file', async ({ contextFactory }, testInfo) =>
|
|||||||
.put({ name: 'foo', date: new Date(0), null: null });
|
.put({ name: 'foo', date: new Date(0), null: null });
|
||||||
transaction
|
transaction
|
||||||
.objectStore('store2')
|
.objectStore('store2')
|
||||||
.put('bar', 'foo');
|
.put(new TextEncoder().encode('bar'), 'foo');
|
||||||
transaction.addEventListener('complete', resolve);
|
transaction.addEventListener('complete', resolve);
|
||||||
transaction.addEventListener('error', reject);
|
transaction.addEventListener('error', reject);
|
||||||
};
|
};
|
||||||
@ -124,16 +124,18 @@ it('should round-trip through the file', async ({ contextFactory }, testInfo) =>
|
|||||||
expect(cookie).toEqual('username=John Doe');
|
expect(cookie).toEqual('username=John Doe');
|
||||||
const idbValues = await page2.evaluate(() => new Promise((resolve, reject) => {
|
const idbValues = await page2.evaluate(() => new Promise((resolve, reject) => {
|
||||||
const openRequest = indexedDB.open('db', 42);
|
const openRequest = indexedDB.open('db', 42);
|
||||||
openRequest.addEventListener('success', () => {
|
openRequest.addEventListener('success', async () => {
|
||||||
const db = openRequest.result;
|
const db = openRequest.result;
|
||||||
const transaction = db.transaction(['store', 'store2'], 'readonly');
|
const transaction = db.transaction(['store', 'store2'], 'readonly');
|
||||||
const request1 = transaction.objectStore('store').get('foo');
|
const request1 = transaction.objectStore('store').get('foo');
|
||||||
const request2 = transaction.objectStore('store2').get('foo');
|
const request2 = transaction.objectStore('store2').get('foo');
|
||||||
|
|
||||||
Promise.all([request1, request2].map(request => new Promise((resolve, reject) => {
|
const [result1, result2] = await Promise.all([request1, request2].map(request => new Promise((resolve, reject) => {
|
||||||
request.addEventListener('success', () => resolve(request.result));
|
request.addEventListener('success', () => resolve(request.result));
|
||||||
request.addEventListener('error', () => reject(request.error));
|
request.addEventListener('error', () => reject(request.error));
|
||||||
}))).then(resolve, reject);
|
})));
|
||||||
|
|
||||||
|
resolve([result1, new TextDecoder().decode(result2 as any)]);
|
||||||
});
|
});
|
||||||
openRequest.addEventListener('error', () => reject(openRequest.error));
|
openRequest.addEventListener('error', () => reject(openRequest.error));
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -94,6 +94,27 @@ it('should transfer arrays as arrays, not objects', async ({ page }) => {
|
|||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should transfer typed arrays', async ({ page }) => {
|
||||||
|
const testCases = [
|
||||||
|
new Int8Array([1, 2, 3]),
|
||||||
|
new Uint8Array([1, 2, 3]),
|
||||||
|
new Uint8ClampedArray([1, 2, 3]),
|
||||||
|
new Int16Array([1, 2, 3]),
|
||||||
|
new Uint16Array([1, 2, 3]),
|
||||||
|
new Int32Array([1, 2, 3]),
|
||||||
|
new Uint32Array([1, 2, 3]),
|
||||||
|
new Float32Array([1.1, 2.2, 3.3]),
|
||||||
|
new Float64Array([1.1, 2.2, 3.3]),
|
||||||
|
new BigInt64Array([1n, 2n, 3n]),
|
||||||
|
new BigUint64Array([1n, 2n, 3n])
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const typedArray of testCases) {
|
||||||
|
const result = await page.evaluate(a => a, typedArray);
|
||||||
|
expect(result).toEqual(typedArray);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('should transfer bigint', async ({ page }) => {
|
it('should transfer bigint', async ({ page }) => {
|
||||||
expect(await page.evaluate(() => 42n)).toBe(42n);
|
expect(await page.evaluate(() => 42n)).toBe(42n);
|
||||||
expect(await page.evaluate(a => a, 17n)).toBe(17n);
|
expect(await page.evaluate(a => a, 17n)).toBe(17n);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user