mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Add window.structuredClone
Depends on https://github.com/lightpanda-io/zig-v8-fork/pull/156 Uses V8::Serializer and V8::Deserializer which handles built-in types, e.g. regex. But it doesn't handle Zig types by default. This is something we need to hook in, using the delegate callbacks. Which we can do after. Meant to replace https://github.com/lightpanda-io/browser/pull/1785
This commit is contained in:
@@ -245,6 +245,46 @@ pub fn toJson(self: Value, allocator: Allocator) ![]u8 {
|
|||||||
return js.String.toSliceWithAlloc(.{ .local = local, .handle = str_handle }, allocator);
|
return js.String.toSliceWithAlloc(.{ .local = local, .handle = str_handle }, allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Currently does not support host objects (Blob, File, etc.) or transferables
|
||||||
|
// which require delegate callbacks to be implemented.
|
||||||
|
pub fn structuredClone(self: Value) !Value {
|
||||||
|
const local = self.local;
|
||||||
|
const v8_context = local.handle;
|
||||||
|
const v8_isolate = local.isolate.handle;
|
||||||
|
|
||||||
|
const size, const data = blk: {
|
||||||
|
const serializer = v8.v8__ValueSerializer__New(v8_isolate, null) orelse return error.JsException;
|
||||||
|
defer v8.v8__ValueSerializer__DELETE(serializer);
|
||||||
|
|
||||||
|
var write_result: v8.MaybeBool = undefined;
|
||||||
|
v8.v8__ValueSerializer__WriteHeader(serializer);
|
||||||
|
v8.v8__ValueSerializer__WriteValue(serializer, v8_context, self.handle, &write_result);
|
||||||
|
if (!write_result.has_value or !write_result.value) {
|
||||||
|
return error.JsException;
|
||||||
|
}
|
||||||
|
|
||||||
|
var size: usize = undefined;
|
||||||
|
const data = v8.v8__ValueSerializer__Release(serializer, &size) orelse return error.JsException;
|
||||||
|
break :blk .{ size, data };
|
||||||
|
};
|
||||||
|
|
||||||
|
defer v8.v8__ValueSerializer__FreeBuffer(data);
|
||||||
|
|
||||||
|
const cloned_handle = blk: {
|
||||||
|
const deserializer = v8.v8__ValueDeserializer__New(v8_isolate, data, size, null) orelse return error.JsException;
|
||||||
|
defer v8.v8__ValueDeserializer__DELETE(deserializer);
|
||||||
|
|
||||||
|
var read_header_result: v8.MaybeBool = undefined;
|
||||||
|
v8.v8__ValueDeserializer__ReadHeader(deserializer, v8_context, &read_header_result);
|
||||||
|
if (!read_header_result.has_value or !read_header_result.value) {
|
||||||
|
return error.JsException;
|
||||||
|
}
|
||||||
|
break :blk v8.v8__ValueDeserializer__ReadValue(deserializer, v8_context) orelse return error.JsException;
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{ .local = local, .handle = cloned_handle };
|
||||||
|
}
|
||||||
|
|
||||||
pub fn persist(self: Value) !Global {
|
pub fn persist(self: Value) !Global {
|
||||||
return self._persist(true);
|
return self._persist(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,143 @@
|
|||||||
testing.expectEqual(screen, window.screen);
|
testing.expectEqual(screen, window.screen);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id=structuredClone>
|
||||||
|
// Basic types
|
||||||
|
testing.expectEqual(42, structuredClone(42));
|
||||||
|
testing.expectEqual('hello', structuredClone('hello'));
|
||||||
|
testing.expectEqual(true, structuredClone(true));
|
||||||
|
testing.expectEqual(null, structuredClone(null));
|
||||||
|
testing.expectEqual(undefined, structuredClone(undefined));
|
||||||
|
|
||||||
|
// Objects and arrays (these work with JSON too, but verify they're cloned)
|
||||||
|
const obj = { a: 1, b: { c: 2 } };
|
||||||
|
const clonedObj = structuredClone(obj);
|
||||||
|
testing.expectEqual(1, clonedObj.a);
|
||||||
|
testing.expectEqual(2, clonedObj.b.c);
|
||||||
|
clonedObj.b.c = 999;
|
||||||
|
testing.expectEqual(2, obj.b.c); // original unchanged
|
||||||
|
|
||||||
|
const arr = [1, [2, 3]];
|
||||||
|
const clonedArr = structuredClone(arr);
|
||||||
|
testing.expectEqual(1, clonedArr[0]);
|
||||||
|
testing.expectEqual(2, clonedArr[1][0]);
|
||||||
|
clonedArr[1][0] = 999;
|
||||||
|
testing.expectEqual(2, arr[1][0]); // original unchanged
|
||||||
|
|
||||||
|
// Date - JSON would stringify to ISO string
|
||||||
|
const date = new Date('2024-01-15T12:30:00Z');
|
||||||
|
const clonedDate = structuredClone(date);
|
||||||
|
testing.expectEqual(true, clonedDate instanceof Date);
|
||||||
|
testing.expectEqual(date.getTime(), clonedDate.getTime());
|
||||||
|
testing.expectEqual(date.toISOString(), clonedDate.toISOString());
|
||||||
|
|
||||||
|
// RegExp - JSON would stringify to {}
|
||||||
|
const regex = /test\d+/gi;
|
||||||
|
const clonedRegex = structuredClone(regex);
|
||||||
|
testing.expectEqual(true, clonedRegex instanceof RegExp);
|
||||||
|
testing.expectEqual(regex.source, clonedRegex.source);
|
||||||
|
testing.expectEqual(regex.flags, clonedRegex.flags);
|
||||||
|
testing.expectEqual(true, clonedRegex.test('test123'));
|
||||||
|
|
||||||
|
// Map - JSON can't handle
|
||||||
|
const map = new Map([['a', 1], ['b', 2]]);
|
||||||
|
const clonedMap = structuredClone(map);
|
||||||
|
testing.expectEqual(true, clonedMap instanceof Map);
|
||||||
|
testing.expectEqual(2, clonedMap.size);
|
||||||
|
testing.expectEqual(1, clonedMap.get('a'));
|
||||||
|
testing.expectEqual(2, clonedMap.get('b'));
|
||||||
|
|
||||||
|
// Set - JSON can't handle
|
||||||
|
const set = new Set([1, 2, 3]);
|
||||||
|
const clonedSet = structuredClone(set);
|
||||||
|
testing.expectEqual(true, clonedSet instanceof Set);
|
||||||
|
testing.expectEqual(3, clonedSet.size);
|
||||||
|
testing.expectEqual(true, clonedSet.has(1));
|
||||||
|
testing.expectEqual(true, clonedSet.has(2));
|
||||||
|
testing.expectEqual(true, clonedSet.has(3));
|
||||||
|
|
||||||
|
// ArrayBuffer
|
||||||
|
const buffer = new ArrayBuffer(8);
|
||||||
|
const view = new Uint8Array(buffer);
|
||||||
|
view[0] = 42;
|
||||||
|
view[7] = 99;
|
||||||
|
const clonedBuffer = structuredClone(buffer);
|
||||||
|
testing.expectEqual(true, clonedBuffer instanceof ArrayBuffer);
|
||||||
|
testing.expectEqual(8, clonedBuffer.byteLength);
|
||||||
|
const clonedView = new Uint8Array(clonedBuffer);
|
||||||
|
testing.expectEqual(42, clonedView[0]);
|
||||||
|
testing.expectEqual(99, clonedView[7]);
|
||||||
|
|
||||||
|
// TypedArray
|
||||||
|
const typedArr = new Uint32Array([100, 200, 300]);
|
||||||
|
const clonedTypedArr = structuredClone(typedArr);
|
||||||
|
testing.expectEqual(true, clonedTypedArr instanceof Uint32Array);
|
||||||
|
testing.expectEqual(3, clonedTypedArr.length);
|
||||||
|
testing.expectEqual(100, clonedTypedArr[0]);
|
||||||
|
testing.expectEqual(200, clonedTypedArr[1]);
|
||||||
|
testing.expectEqual(300, clonedTypedArr[2]);
|
||||||
|
|
||||||
|
// Special number values - JSON can't preserve these
|
||||||
|
testing.expectEqual(true, Number.isNaN(structuredClone(NaN)));
|
||||||
|
testing.expectEqual(Infinity, structuredClone(Infinity));
|
||||||
|
testing.expectEqual(-Infinity, structuredClone(-Infinity));
|
||||||
|
|
||||||
|
// Object with undefined value - JSON would omit it
|
||||||
|
const objWithUndef = { a: 1, b: undefined, c: 3 };
|
||||||
|
const clonedObjWithUndef = structuredClone(objWithUndef);
|
||||||
|
testing.expectEqual(1, clonedObjWithUndef.a);
|
||||||
|
testing.expectEqual(undefined, clonedObjWithUndef.b);
|
||||||
|
testing.expectEqual(true, 'b' in clonedObjWithUndef);
|
||||||
|
testing.expectEqual(3, clonedObjWithUndef.c);
|
||||||
|
|
||||||
|
// Error objects
|
||||||
|
const error = new Error('test error');
|
||||||
|
const clonedError = structuredClone(error);
|
||||||
|
testing.expectEqual(true, clonedError instanceof Error);
|
||||||
|
testing.expectEqual('test error', clonedError.message);
|
||||||
|
|
||||||
|
// TypeError
|
||||||
|
const typeError = new TypeError('type error');
|
||||||
|
const clonedTypeError = structuredClone(typeError);
|
||||||
|
testing.expectEqual(true, clonedTypeError instanceof TypeError);
|
||||||
|
testing.expectEqual('type error', clonedTypeError.message);
|
||||||
|
|
||||||
|
// BigInt
|
||||||
|
const bigInt = BigInt('9007199254740993');
|
||||||
|
const clonedBigInt = structuredClone(bigInt);
|
||||||
|
testing.expectEqual(bigInt, clonedBigInt);
|
||||||
|
|
||||||
|
// Circular references ARE supported by structuredClone (unlike JSON)
|
||||||
|
const circular = { a: 1 };
|
||||||
|
circular.self = circular;
|
||||||
|
const clonedCircular = structuredClone(circular);
|
||||||
|
testing.expectEqual(1, clonedCircular.a);
|
||||||
|
testing.expectEqual(clonedCircular, clonedCircular.self); // circular ref preserved
|
||||||
|
|
||||||
|
// Functions cannot be cloned - should throw
|
||||||
|
{
|
||||||
|
let threw = false;
|
||||||
|
try {
|
||||||
|
structuredClone(() => {});
|
||||||
|
} catch (err) {
|
||||||
|
threw = true;
|
||||||
|
// Just verify an error was thrown - V8's message format may vary
|
||||||
|
}
|
||||||
|
testing.expectEqual(true, threw);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbols cannot be cloned - should throw
|
||||||
|
{
|
||||||
|
let threw = false;
|
||||||
|
try {
|
||||||
|
structuredClone(Symbol('test'));
|
||||||
|
} catch (err) {
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
testing.expectEqual(true, threw);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script id=unhandled_rejection>
|
<script id=unhandled_rejection>
|
||||||
{
|
{
|
||||||
let unhandledCalled = 0;
|
let unhandledCalled = 0;
|
||||||
|
|||||||
@@ -412,6 +412,10 @@ pub fn atob(_: *const Window, input: []const u8, page: *Page) ![]const u8 {
|
|||||||
return decoded;
|
return decoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn structuredClone(_: *const Window, value: js.Value) !js.Value {
|
||||||
|
return value.structuredClone();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn getFrame(self: *Window, idx: usize) !?*Window {
|
pub fn getFrame(self: *Window, idx: usize) !?*Window {
|
||||||
const page = self._page;
|
const page = self._page;
|
||||||
const frames = page.frames.items;
|
const frames = page.frames.items;
|
||||||
@@ -797,6 +801,7 @@ pub const JsApi = struct {
|
|||||||
pub const btoa = bridge.function(Window.btoa, .{});
|
pub const btoa = bridge.function(Window.btoa, .{});
|
||||||
pub const atob = bridge.function(Window.atob, .{ .dom_exception = true });
|
pub const atob = bridge.function(Window.atob, .{ .dom_exception = true });
|
||||||
pub const reportError = bridge.function(Window.reportError, .{});
|
pub const reportError = bridge.function(Window.reportError, .{});
|
||||||
|
pub const structuredClone = bridge.function(Window.structuredClone, .{});
|
||||||
pub const getComputedStyle = bridge.function(Window.getComputedStyle, .{});
|
pub const getComputedStyle = bridge.function(Window.getComputedStyle, .{});
|
||||||
pub const getSelection = bridge.function(Window.getSelection, .{});
|
pub const getSelection = bridge.function(Window.getSelection, .{});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user