Re-enable cached property support

The idea is that frequently accessed properties, e.g. window.document, can be
cached directly as data properties on the underlying v8::Object, removing the
need for the access call into Zig. This is only used on a handful of properties,
almost all of which are on the Window. It is important that the property be
read-only. For example, window.location cannot be cached this way because
window.location is writable (e.g. window.location.hash = '#blah').

This existed briefly before Zigdom, but was removed as part of the migration.
The implementation has changed. This previously relied on a "postAttach" feature
which no longer exists. It is not integrated in the bridge/callback directly and
lazily applied after the first access.
This commit is contained in:
Karl Seguin
2026-01-19 11:28:42 +08:00
parent 6f3cb4b48e
commit 43805ad698
4 changed files with 27 additions and 6 deletions

View File

@@ -82,6 +82,7 @@ pub fn deinit(self: *Caller) void {
} }
pub const CallOpts = struct { pub const CallOpts = struct {
cache: ?[]const u8 = null,
dom_exception: bool = false, dom_exception: bool = false,
null_as_undefined: bool = false, null_as_undefined: bool = false,
as_typed_array: bool = false, as_typed_array: bool = false,
@@ -150,9 +151,27 @@ pub fn method(self: *Caller, comptime T: type, func: anytype, handle: *const v8.
fn _method(self: *Caller, comptime T: type, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) !void { fn _method(self: *Caller, comptime T: type, func: anytype, info: FunctionCallbackInfo, comptime opts: CallOpts) !void {
const F = @TypeOf(func); const F = @TypeOf(func);
var args = try self.getArgs(F, 1, info); var args = try self.getArgs(F, 1, info);
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis());
const js_this = info.getThis();
@field(args, "0") = try TaggedOpaque.fromJS(*T, js_this);
const res = @call(.auto, func, args); const res = @call(.auto, func, args);
info.getReturnValue().set(try self.local.zigValueToJs(res, opts));
const mapped = try self.local.zigValueToJs(res, opts);
const return_value = info.getReturnValue();
return_value.set(mapped);
if (comptime opts.cache != null) {
// store the return value directly in the JS object for faster subsequent
// calls. We only do this for a few frequently used properties (e.g. window.document)
const local = self.local;
const key_handle = local.isolate.initStringHandle(opts.cache.?);
var out: v8.MaybeBool = undefined;
v8.v8__Object__DefineOwnProperty(js_this, local.handle, key_handle, mapped.handle, 0, &out);
if (comptime IS_DEBUG) {
std.debug.assert(out.has_value and out.value);
}
}
} }
pub fn function(self: *Caller, comptime T: type, func: anytype, handle: *const v8.FunctionCallbackInfo, comptime opts: CallOpts) void { pub fn function(self: *Caller, comptime T: type, func: anytype, handle: *const v8.FunctionCallbackInfo, comptime opts: CallOpts) void {

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) // Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
// //
// Francis Bouvier <francis@lightpanda.io> // Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io> // Pierre Tachoire <pierre@lightpanda.io>
@@ -164,7 +164,7 @@ pub const Accessor = struct {
const Opts = struct { const Opts = struct {
static: bool = false, static: bool = false,
cache: ?[]const u8 = null, // @ZIGDOM cache: ?[]const u8 = null,
as_typed_array: bool = false, as_typed_array: bool = false,
null_as_undefined: bool = false, null_as_undefined: bool = false,
}; };
@@ -184,11 +184,13 @@ pub const Accessor = struct {
if (comptime opts.static) { if (comptime opts.static) {
caller.function(T, getter, handle.?, .{ caller.function(T, getter, handle.?, .{
.cache = opts.cache,
.as_typed_array = opts.as_typed_array, .as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined, .null_as_undefined = opts.null_as_undefined,
}); });
} else { } else {
caller.method(T, getter, handle.?, .{ caller.method(T, getter, handle.?, .{
.cache = opts.cache,
.as_typed_array = opts.as_typed_array, .as_typed_array = opts.as_typed_array,
.null_as_undefined = opts.null_as_undefined, .null_as_undefined = opts.null_as_undefined,
}); });

View File

@@ -261,7 +261,7 @@ pub const JsApi = struct {
pub const applets = bridge.accessor(HTMLDocument.getApplets, null, .{}); pub const applets = bridge.accessor(HTMLDocument.getApplets, null, .{});
pub const plugins = bridge.accessor(HTMLDocument.getEmbeds, null, .{}); pub const plugins = bridge.accessor(HTMLDocument.getEmbeds, null, .{});
pub const currentScript = bridge.accessor(HTMLDocument.getCurrentScript, null, .{}); pub const currentScript = bridge.accessor(HTMLDocument.getCurrentScript, null, .{});
pub const location = bridge.accessor(HTMLDocument.getLocation, HTMLDocument.setLocation, .{ .cache = "location" }); pub const location = bridge.accessor(HTMLDocument.getLocation, HTMLDocument.setLocation, .{});
pub const all = bridge.accessor(HTMLDocument.getAll, null, .{}); pub const all = bridge.accessor(HTMLDocument.getAll, null, .{});
pub const cookie = bridge.accessor(HTMLDocument.getCookie, HTMLDocument.setCookie, .{}); pub const cookie = bridge.accessor(HTMLDocument.getCookie, HTMLDocument.setCookie, .{});
pub const doctype = bridge.accessor(HTMLDocument.getDocType, null, .{}); pub const doctype = bridge.accessor(HTMLDocument.getDocType, null, .{});

View File

@@ -651,7 +651,7 @@ pub const JsApi = struct {
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{ .cache = "localStorage" }); pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{ .cache = "localStorage" });
pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{ .cache = "sessionStorage" }); pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{ .cache = "sessionStorage" });
pub const document = bridge.accessor(Window.getDocument, null, .{ .cache = "document" }); pub const document = bridge.accessor(Window.getDocument, null, .{ .cache = "document" });
pub const location = bridge.accessor(Window.getLocation, Window.setLocation, .{ .cache = "location" }); pub const location = bridge.accessor(Window.getLocation, Window.setLocation, .{});
pub const history = bridge.accessor(Window.getHistory, null, .{}); pub const history = bridge.accessor(Window.getHistory, null, .{});
pub const navigation = bridge.accessor(Window.getNavigation, null, .{}); pub const navigation = bridge.accessor(Window.getNavigation, null, .{});
pub const crypto = bridge.accessor(Window.getCrypto, null, .{ .cache = "crypto" }); pub const crypto = bridge.accessor(Window.getCrypto, null, .{ .cache = "crypto" });