From 43805ad69807e3bfc8394131b7b7ec729ca846e5 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 19 Jan 2026 11:28:42 +0800 Subject: [PATCH] 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. --- src/browser/js/Caller.zig | 23 +++++++++++++++++++++-- src/browser/js/bridge.zig | 6 ++++-- src/browser/webapi/HTMLDocument.zig | 2 +- src/browser/webapi/Window.zig | 2 +- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index b658a3b8..97e7baed 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -82,6 +82,7 @@ pub fn deinit(self: *Caller) void { } pub const CallOpts = struct { + cache: ?[]const u8 = null, dom_exception: bool = false, null_as_undefined: 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 { const F = @TypeOf(func); 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); - 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 { diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 4c4abb8f..86b498c5 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2025 Lightpanda (Selecy SAS) +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) // // Francis Bouvier // Pierre Tachoire @@ -164,7 +164,7 @@ pub const Accessor = struct { const Opts = struct { static: bool = false, - cache: ?[]const u8 = null, // @ZIGDOM + cache: ?[]const u8 = null, as_typed_array: bool = false, null_as_undefined: bool = false, }; @@ -184,11 +184,13 @@ pub const Accessor = struct { if (comptime opts.static) { caller.function(T, getter, handle.?, .{ + .cache = opts.cache, .as_typed_array = opts.as_typed_array, .null_as_undefined = opts.null_as_undefined, }); } else { caller.method(T, getter, handle.?, .{ + .cache = opts.cache, .as_typed_array = opts.as_typed_array, .null_as_undefined = opts.null_as_undefined, }); diff --git a/src/browser/webapi/HTMLDocument.zig b/src/browser/webapi/HTMLDocument.zig index 8082ac67..069b0d77 100644 --- a/src/browser/webapi/HTMLDocument.zig +++ b/src/browser/webapi/HTMLDocument.zig @@ -261,7 +261,7 @@ pub const JsApi = struct { pub const applets = bridge.accessor(HTMLDocument.getApplets, null, .{}); pub const plugins = bridge.accessor(HTMLDocument.getEmbeds, 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 cookie = bridge.accessor(HTMLDocument.getCookie, HTMLDocument.setCookie, .{}); pub const doctype = bridge.accessor(HTMLDocument.getDocType, null, .{}); diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index ac1aa79e..d32c884c 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -651,7 +651,7 @@ pub const JsApi = struct { pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{ .cache = "localStorage" }); pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{ .cache = "sessionStorage" }); 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 navigation = bridge.accessor(Window.getNavigation, null, .{}); pub const crypto = bridge.accessor(Window.getCrypto, null, .{ .cache = "crypto" });