diff --git a/src/browser/js/Snapshot.zig b/src/browser/js/Snapshot.zig index 7186fd7c..add5e334 100644 --- a/src/browser/js/Snapshot.zig +++ b/src/browser/js/Snapshot.zig @@ -280,6 +280,7 @@ fn countExternalReferences() comptime_int { // +1 for the illegal constructor callback var count: comptime_int = 1; + var has_non_template_property: bool = false; inline for (JsApis) |JsApi| { // Constructor (only if explicit) @@ -302,6 +303,10 @@ fn countExternalReferences() comptime_int { if (value.setter != null) count += 1; // setter } else if (T == bridge.Function) { count += 1; + } else if (T == bridge.Property) { + if (value.template == false) { + has_non_template_property = true; + } } else if (T == bridge.Iterator) { count += 1; } else if (T == bridge.Indexed) { @@ -314,6 +319,10 @@ fn countExternalReferences() comptime_int { } } + if (has_non_template_property) { + count += 1; + } + // In debug mode, add unknown property callbacks for types without NamedIndexed if (comptime IS_DEBUG) { inline for (JsApis) |JsApi| { @@ -333,6 +342,8 @@ fn collectExternalReferences() [countExternalReferences()]isize { references[idx] = @bitCast(@intFromPtr(&illegalConstructorCallback)); idx += 1; + var has_non_template_property = false; + inline for (JsApis) |JsApi| { if (@hasDecl(JsApi, "constructor")) { references[idx] = @bitCast(@intFromPtr(JsApi.constructor.func)); @@ -358,6 +369,10 @@ fn collectExternalReferences() [countExternalReferences()]isize { } else if (T == bridge.Function) { references[idx] = @bitCast(@intFromPtr(value.func)); idx += 1; + } else if (T == bridge.Property) { + if (value.template == false) { + has_non_template_property = true; + } } else if (T == bridge.Iterator) { references[idx] = @bitCast(@intFromPtr(value.func)); idx += 1; @@ -379,6 +394,11 @@ fn collectExternalReferences() [countExternalReferences()]isize { } } + if (has_non_template_property) { + references[idx] = @bitCast(@intFromPtr(&bridge.Property.getter)); + idx += 1; + } + // In debug mode, collect unknown property callbacks for types without NamedIndexed if (comptime IS_DEBUG) { inline for (JsApis) |JsApi| { @@ -497,17 +517,24 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.Functio v8.v8__Template__Set(@ptrCast(target), js_name, @ptrCast(function_template), v8.None); }, bridge.Property => { - // simpleZigValueToJs now returns raw handle directly - const js_value = switch (value) { - .int => |v| js.simpleZigValueToJs(.{ .handle = isolate }, v, true, false), + const js_value = switch (value.value) { + inline .bool, .int => |v| js.simpleZigValueToJs(.{ .handle = isolate }, v, true, false), }; - const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len)); - // apply it both to the type itself - v8.v8__Template__Set(@ptrCast(template), js_name, js_value, v8.ReadOnly + v8.DontDelete); - // and to instances of the type - v8.v8__Template__Set(@ptrCast(target), js_name, js_value, v8.ReadOnly + v8.DontDelete); + if (value.template == false) { + // not defined on the template, only on the instance. This + // is like an Accessor, but because the value is known at + // compile time, we skip _a lot_ of code and quickly return + // the hard-coded value + const getter_callback = @constCast(v8.v8__FunctionTemplate__New__DEFAULT3(isolate, bridge.Property.getter, js_value)); + v8.v8__ObjectTemplate__SetAccessorProperty__DEFAULT(target, js_name, getter_callback); + } else { + // apply it both to the type itself + v8.v8__Template__Set(@ptrCast(template), js_name, js_value, v8.ReadOnly + v8.DontDelete); + // and to instances of the type + v8.v8__Template__Set(@ptrCast(target), js_name, js_value, v8.ReadOnly + v8.DontDelete); + } }, bridge.Constructor => {}, // already handled in generateConstructor else => {}, diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 2bbc112f..3901d172 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -62,12 +62,13 @@ pub fn Builder(comptime T: type) type { return Callable.init(T, func, opts); } - pub fn property(value: anytype) Property { - switch (@typeInfo(@TypeOf(value))) { - .comptime_int, .int => return .{ .int = value }, - else => {}, - } - @compileError("Property for " ++ @typeName(@TypeOf(value)) ++ " hasn't been defined yet"); + pub fn property(value: anytype, opts: Property.Opts) Property { + // If you add strings to this, they might need to be internalized! + return switch (@typeInfo(@TypeOf(value))) { + .bool => Property.init(.{ .bool = value }, opts), + .comptime_int, .int => Property.init(.{ .int = value }, opts), + else => @compileError("Property for " ++ @typeName(@TypeOf(value)) ++ " hasn't been defined yet"), + }; } const PrototypeChainEntry = @import("TaggedOpaque.zig").PrototypeChainEntry; @@ -398,8 +399,33 @@ pub const Callable = struct { } }; -pub const Property = union(enum) { - int: i64, +pub const Property = struct { + value: Value, + template: bool, + + // If you add strings to this, they might need to be internalized! + const Value = union(enum) { + int: i64, + bool: bool, + }; + + const Opts = struct { + template: bool, + }; + + fn init(value: Value, opts: Opts) Property { + return .{ + .value = value, + .template = opts.template, + }; + } + + pub fn getter(handle: ?*const v8.FunctionCallbackInfo) callconv(.c) void { + const value = v8.v8__FunctionCallbackInfo__Data(handle.?); + var rv: v8.ReturnValue = undefined; + v8.v8__FunctionCallbackInfo__GetReturnValue(handle.?, &rv); + v8.v8__ReturnValue__Set(rv, value); + } }; const Finalizer = struct { diff --git a/src/browser/tests/document/document.html b/src/browser/tests/document/document.html index 995588e6..3934147d 100644 --- a/src/browser/tests/document/document.html +++ b/src/browser/tests/document/document.html @@ -15,6 +15,8 @@ testing.expectEqual(window, document.defaultView); testing.expectEqual(false, document.hidden); testing.expectEqual("visible", document.visibilityState); + testing.expectEqual(false, document.prerendering); + testing.expectEqual(undefined, Document.prerendering);