From a5038893fe520df09f3502b913b06d69400f3717 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 31 Dec 2025 13:47:56 +0800 Subject: [PATCH] port Snapshot --- src/browser/js/Snapshot.zig | 212 ++++++++++++++++++++++-------------- 1 file changed, 128 insertions(+), 84 deletions(-) diff --git a/src/browser/js/Snapshot.zig b/src/browser/js/Snapshot.zig index d227bee3..4c49079e 100644 --- a/src/browser/js/Snapshot.zig +++ b/src/browser/js/Snapshot.zig @@ -41,8 +41,8 @@ const embedded_snapshot_blob = if (@import("build_config").snapshot_path) |path| // sequence data_start: usize, -// The snapshot data (v8.StartupData is a ptr to the data and len). -startup_data: v8.StartupData, +// The snapshot data (v8.c.StartupData is a ptr to the data and len). +startup_data: v8.c.StartupData, // V8 doesn't know how to serialize external references, and pretty much any hook // into Zig is an external reference (e.g. every accessor and function callback). @@ -74,8 +74,8 @@ fn loadEmbedded() ?Snapshot { const data_start = std.mem.readInt(usize, embedded_snapshot_blob[0..@sizeOf(usize)], .little); const blob = embedded_snapshot_blob[@sizeOf(usize)..]; - const startup_data = v8.StartupData{ .data = blob.ptr, .raw_size = @intCast(blob.len) }; - if (!v8.SnapshotCreator.startupDataIsValid(startup_data)) { + const startup_data = v8.c.StartupData{ .data = blob.ptr, .raw_size = @intCast(blob.len) }; + if (!v8.c.v8__StartupData__IsValid(startup_data)) { return null; } @@ -110,45 +110,48 @@ pub fn fromEmbedded(self: Snapshot) bool { } fn isValid(self: Snapshot) bool { - return v8.SnapshotCreator.startupDataIsValid(self.startup_data); + return v8.c.v8__StartupData__IsValid(self.startup_data); } pub fn createGlobalTemplate(isolate: v8.Isolate, templates: []const v8.FunctionTemplate) v8.ObjectTemplate { // Set up the global template to inherit from Window's template // This way the global object gets all Window properties through inheritance - const js_global = v8.FunctionTemplate.initDefault(isolate); - js_global.setClassName(v8.String.initUtf8(isolate, "Window")); + const js_global = v8.c.v8__FunctionTemplate__New__DEFAULT(isolate); + const window_name = v8.c.v8__String__NewFromUtf8(isolate, "Window", v8.c.kNormal, 6); + v8.c.v8__FunctionTemplate__SetClassName(js_global, window_name); + // Find Window in JsApis by name (avoids circular import) const window_index = comptime bridge.JsApiLookup.getId(Window.JsApi); - js_global.inherit(templates[window_index]); - return js_global.getInstanceTemplate(); + v8.c.v8__FunctionTemplate__Inherit(js_global, templates[window_index]); + + return v8.c.v8__FunctionTemplate__InstanceTemplate(js_global); } pub fn create(allocator: Allocator) !Snapshot { var external_references = collectExternalReferences(); - var params = v8.initCreateParams(); - params.array_buffer_allocator = v8.createDefaultArrayBufferAllocator(); - defer v8.destroyArrayBufferAllocator(params.array_buffer_allocator.?); + var params: v8.c.CreateParams = undefined; + v8.c.v8__Isolate__CreateParams__CONSTRUCT(¶ms); + params.array_buffer_allocator = v8.c.v8__ArrayBuffer__Allocator__NewDefaultAllocator(); + defer v8.c.v8__ArrayBuffer__Allocator__DELETE(params.array_buffer_allocator.?); params.external_references = @ptrCast(&external_references); - var snapshot_creator: v8.SnapshotCreator = undefined; - v8.SnapshotCreator.init(&snapshot_creator, ¶ms); - defer snapshot_creator.deinit(); + const snapshot_creator = v8.c.v8__SnapshotCreator__CREATE(¶ms); + defer v8.c.v8__SnapshotCreator__DESTRUCT(snapshot_creator); var data_start: usize = 0; - const isolate = snapshot_creator.getIsolate(); + const isolate = v8.c.v8__SnapshotCreator__getIsolate(snapshot_creator).?; { // CreateBlob, which we'll call once everything is setup, MUST NOT // be called from an active HandleScope. Hence we have this scope to // clean it up before we call CreateBlob - var handle_scope: v8.HandleScope = undefined; - v8.HandleScope.init(&handle_scope, isolate); - defer handle_scope.deinit(); + var handle_scope: v8.c.HandleScope = undefined; + v8.c.v8__HandleScope__CONSTRUCT(&handle_scope, isolate); + defer v8.c.v8__HandleScope__DESTRUCT(&handle_scope); // Create templates (constructors only) FIRST - var templates: [JsApis.len]v8.FunctionTemplate = undefined; + var templates: [JsApis.len]*v8.c.FunctionTemplate = undefined; inline for (JsApis, 0..) |JsApi, i| { @setEvalBranchQuota(10_000); templates[i] = generateConstructor(JsApi, isolate); @@ -159,23 +162,23 @@ pub fn create(allocator: Allocator) !Snapshot { // This must come before attachClass so inheritance is set up first inline for (JsApis, 0..) |JsApi, i| { if (comptime protoIndexLookup(JsApi)) |proto_index| { - templates[i].inherit(templates[proto_index]); + v8.c.v8__FunctionTemplate__Inherit(templates[i], templates[proto_index]); } } // Set up the global template to inherit from Window's template // This way the global object gets all Window properties through inheritance - const global_template = createGlobalTemplate(isolate, templates[0..]); - const context = v8.Context.init(isolate, global_template, null); - context.enter(); - defer context.exit(); + const global_template = createGlobalTemplate(isolate, templates[0..]); + const context = v8.c.v8__Context__New(isolate, global_template, null); + v8.c.v8__Context__Enter(context); + defer v8.c.v8__Context__Exit(context); // Add templates to context snapshot var last_data_index: usize = 0; inline for (JsApis, 0..) |_, i| { @setEvalBranchQuota(10_000); - const data_index = snapshot_creator.addDataWithContext(context, @ptrCast(templates[i].handle)); + const data_index = v8.c.v8__SnapshotCreator__AddData2(snapshot_creator, context, @ptrCast(templates[i])); if (i == 0) { data_start = data_index; last_data_index = data_index; @@ -193,16 +196,18 @@ pub fn create(allocator: Allocator) !Snapshot { } // Realize all templates by getting their functions and attaching to global - const global_obj = context.getGlobal(); + const global_obj = v8.c.v8__Context__Global(context); inline for (JsApis, 0..) |JsApi, i| { - const func = templates[i].getFunction(context); + const func = v8.c.v8__FunctionTemplate__GetFunction(templates[i], context); // Attach to global if it has a name if (@hasDecl(JsApi.Meta, "name")) { if (@hasDecl(JsApi.Meta, "constructor_alias")) { - const v8_class_name = v8.String.initUtf8(isolate, JsApi.Meta.constructor_alias); - _ = global_obj.setValue(context, v8_class_name, func); + const alias = JsApi.Meta.constructor_alias; + const v8_class_name = v8.c.v8__String__NewFromUtf8(isolate, alias.ptr, v8.c.kNormal, @intCast(alias.len)); + var maybe_result: v8.c.MaybeBool = undefined; + v8.c.v8__Object__Set(global_obj, context, v8_class_name, func, &maybe_result); // @TODO: This is wrong. This name should be registered with the // illegalConstructorCallback. I.e. new Image() is OK, but @@ -210,11 +215,15 @@ pub fn create(allocator: Allocator) !Snapshot { // But we _have_ to register the name, i.e. HTMLImageElement // has to be registered so, for now, instead of creating another // template, we just hook it into the constructor. - const illegal_class_name = v8.String.initUtf8(isolate, JsApi.Meta.name); - _ = global_obj.setValue(context, illegal_class_name, func); + const name = JsApi.Meta.name; + const illegal_class_name = v8.c.v8__String__NewFromUtf8(isolate, name.ptr, v8.c.kNormal, @intCast(name.len)); + var maybe_result2: v8.c.MaybeBool = undefined; + v8.c.v8__Object__Set(global_obj, context, illegal_class_name, func, &maybe_result2); } else { - const v8_class_name = v8.String.initUtf8(isolate, JsApi.Meta.name); - _ = global_obj.setValue(context, v8_class_name, func); + const name = JsApi.Meta.name; + const v8_class_name = v8.c.v8__String__NewFromUtf8(isolate, name.ptr, v8.c.kNormal, @intCast(name.len)); + var maybe_result: v8.c.MaybeBool = undefined; + v8.c.v8__Object__Set(global_obj, context, v8_class_name, func, &maybe_result); } } } @@ -222,8 +231,10 @@ pub fn create(allocator: Allocator) !Snapshot { { // If we want to overwrite the built-in console, we have to // delete the built-in one. - const console_key = v8.String.initUtf8(isolate, "console"); - if (global_obj.deleteValue(context, console_key) == false) { + const console_key = v8.c.v8__String__NewFromUtf8(isolate, "console", v8.c.kNormal, 7); + var maybe_deleted: v8.c.MaybeBool = undefined; + v8.c.v8__Object__Delete(global_obj, context, console_key, &maybe_deleted); + if (maybe_deleted.value == false) { return error.ConsoleDeleteError; } } @@ -233,23 +244,30 @@ pub fn create(allocator: Allocator) !Snapshot { // TODO: see if newer V8 engines have a way around this. inline for (JsApis, 0..) |JsApi, i| { if (comptime protoIndexLookup(JsApi)) |proto_index| { - const proto_obj = templates[proto_index].getFunction(context).toObject(); - const self_obj = templates[i].getFunction(context).toObject(); - _ = self_obj.setPrototype(context, proto_obj); + const proto_func = v8.c.v8__FunctionTemplate__GetFunction(templates[proto_index], context); + const proto_obj: *const v8.c.Object = @ptrCast(proto_func); + + const self_func = v8.c.v8__FunctionTemplate__GetFunction(templates[i], context); + const self_obj: *const v8.c.Object = @ptrCast(self_func); + + var maybe_result: v8.c.MaybeBool = undefined; + v8.c.v8__Object__SetPrototype(self_obj, context, proto_obj, &maybe_result); } } { // Custom exception // TODO: this is an horrible hack, I can't figure out how to do this cleanly. - const code = v8.String.initUtf8(isolate, "DOMException.prototype.__proto__ = Error.prototype"); - _ = try (try v8.Script.compile(context, code, null)).run(context); + const code_str = "DOMException.prototype.__proto__ = Error.prototype"; + const code = v8.c.v8__String__NewFromUtf8(isolate, code_str.ptr, v8.c.kNormal, @intCast(code_str.len)); + const script = v8.c.v8__Script__Compile(context, code, null) orelse return error.ScriptCompileFailed; + _ = v8.c.v8__Script__Run(script, context) orelse return error.ScriptRunFailed; } - snapshot_creator.setDefaultContext(context); + v8.c.v8__SnapshotCreator__setDefaultContext(snapshot_creator, context); } - const blob = snapshot_creator.createBlob(v8.FunctionCodeHandling.kKeep); + const blob = v8.c.v8__SnapshotCreator__createBlob(snapshot_creator, v8.c.kKeep); const owned = try allocator.dupe(u8, blob.data[0..@intCast(blob.raw_size)]); return .{ @@ -365,7 +383,7 @@ fn collectExternalReferences() [countExternalReferences()]isize { // via `new ClassName()` - but they could, for example, be created in // Zig and returned from a function call, which is why we need the // FunctionTemplate. -fn generateConstructor(comptime JsApi: type, isolate: v8.Isolate) v8.FunctionTemplate { +fn generateConstructor(comptime JsApi: type, isolate: *v8.c.Isolate) *v8.c.FunctionTemplate { const callback = blk: { if (@hasDecl(JsApi, "constructor")) { break :blk JsApi.constructor.func; @@ -375,19 +393,22 @@ fn generateConstructor(comptime JsApi: type, isolate: v8.Isolate) v8.FunctionTem break :blk illegalConstructorCallback; }; - const template = v8.FunctionTemplate.initCallback(isolate, callback); + const template = @constCast(v8.c.v8__FunctionTemplate__New__DEFAULT2(isolate, callback).?); if (!@hasDecl(JsApi.Meta, "empty_with_no_proto")) { - template.getInstanceTemplate().setInternalFieldCount(1); + const instance_template = v8.c.v8__FunctionTemplate__InstanceTemplate(template); + v8.c.v8__ObjectTemplate__SetInternalFieldCount(instance_template, 1); } - const class_name = v8.String.initUtf8(isolate, if (@hasDecl(JsApi.Meta, "name")) JsApi.Meta.name else @typeName(JsApi)); - template.setClassName(class_name); + const name_str = if (@hasDecl(JsApi.Meta, "name")) JsApi.Meta.name else @typeName(JsApi); + const class_name = v8.c.v8__String__NewFromUtf8(isolate, name_str.ptr, v8.c.kNormal, @intCast(name_str.len)); + v8.c.v8__FunctionTemplate__SetClassName(template, class_name); return template; } // Attaches JsApi members to the prototype template (normal case) -fn attachClass(comptime JsApi: type, isolate: v8.Isolate, template: v8.FunctionTemplate) void { - const target = template.getPrototypeTemplate(); - const instance = template.getInstanceTemplate(); + +fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.FunctionTemplate) void { + const target = v8.c.v8__FunctionTemplate__PrototypeTemplate(template); + const instance = v8.c.v8__FunctionTemplate__InstanceTemplate(template); const declarations = @typeInfo(JsApi).@"struct".decls; inline for (declarations) |d| { @@ -397,60 +418,80 @@ fn attachClass(comptime JsApi: type, isolate: v8.Isolate, template: v8.FunctionT switch (definition) { bridge.Accessor => { - const js_name = v8.String.initUtf8(isolate, name).toName(); - const getter_callback = v8.FunctionTemplate.initCallback(isolate, value.getter); + const js_name = v8.c.v8__String__NewFromUtf8(isolate, name.ptr, v8.c.kNormal, @intCast(name.len)); + const getter_callback = @constCast(v8.c.v8__FunctionTemplate__New__DEFAULT2(isolate, value.getter).?); if (value.setter == null) { if (value.static) { - template.setAccessorGetter(js_name, getter_callback); + v8.c.v8__Template__SetAccessorProperty__DEFAULT(@ptrCast(template), js_name, getter_callback); } else { - target.setAccessorGetter(js_name, getter_callback); + v8.c.v8__ObjectTemplate__SetAccessorProperty__DEFAULT(target, js_name, getter_callback); } } else { std.debug.assert(value.static == false); - const setter_callback = v8.FunctionTemplate.initCallback(isolate, value.setter); - target.setAccessorGetterAndSetter(js_name, getter_callback, setter_callback); + const setter_callback = @constCast(v8.c.v8__FunctionTemplate__New__DEFAULT2(isolate, value.setter.?).?); + v8.c.v8__ObjectTemplate__SetAccessorProperty__DEFAULT2(target, js_name, getter_callback, setter_callback); } }, bridge.Function => { - const function_template = v8.FunctionTemplate.initCallback(isolate, value.func); - const js_name = v8.String.initUtf8(isolate, name).toName(); + const function_template = @constCast(v8.c.v8__FunctionTemplate__New__DEFAULT2(isolate, value.func).?); + const js_name = v8.c.v8__String__NewFromUtf8(isolate, name.ptr, v8.c.kNormal, @intCast(name.len)); if (value.static) { - template.set(js_name, function_template, v8.PropertyAttribute.None); + v8.c.v8__Template__Set(@ptrCast(template), js_name, @ptrCast(function_template), v8.c.None); } else { - target.set(js_name, function_template, v8.PropertyAttribute.None); + v8.c.v8__Template__Set(@ptrCast(target), js_name, @ptrCast(function_template), v8.c.None); } }, bridge.Indexed => { - const configuration = v8.IndexedPropertyHandlerConfiguration{ + var configuration: v8.c.IndexedPropertyHandlerConfiguration = .{ .getter = value.getter, + .setter = null, + .query = null, + .deleter = null, + .enumerator = null, + .definer = null, + .descriptor = null, + .data = null, + .flags = 0, }; instance.setIndexedProperty(configuration, null); }, - bridge.NamedIndexed => instance.setNamedProperty(.{ - .getter = value.getter, - .setter = value.setter, - .deleter = value.deleter, - .flags = v8.PropertyHandlerFlags.OnlyInterceptStrings | v8.PropertyHandlerFlags.NonMasking, - }, null), + bridge.NamedIndexed => { + const instance_template = v8.c.v8__FunctionTemplate__InstanceTemplate(template); + var configuration: v8.c.NamedPropertyHandlerConfiguration = .{ + .getter = value.getter, + .setter = value.setter, + .query = null, + .deleter = value.deleter, + .enumerator = null, + .definer = null, + .descriptor = null, + .data = null, + .flags = v8.c.kOnlyInterceptStrings | v8.c.kNonMasking, + }; + v8.c.v8__ObjectTemplate__SetNamedHandler(instance, &configuration); + }, bridge.Iterator => { - const function_template = v8.FunctionTemplate.initCallback(isolate, value.func); + const function_template = @constCast(v8.c.v8__FunctionTemplate__New__DEFAULT2(isolate, value.func).?); const js_name = if (value.async) - v8.Symbol.getAsyncIterator(isolate).toName() + v8.c.v8__Symbol__GetAsyncIterator(isolate) else - v8.Symbol.getIterator(isolate).toName(); - target.set(js_name, function_template, v8.PropertyAttribute.None); + v8.c.v8__Symbol__GetIterator(isolate); + v8.c.v8__Template__Set(@ptrCast(target), js_name, @ptrCast(function_template), v8.c.None); }, bridge.Property => { - const js_value = switch (value) { - .int => |v| js.simpleZigValueToJs(isolate, v, true, false), + // simpleZigValueToJs still uses old v8.Isolate wrapper, so create a temp wrapper + const iso_wrapper = v8.Isolate{ .handle = isolate }; + const js_value_wrapper = switch (value) { + .int => |v| js.simpleZigValueToJs(iso_wrapper, v, true, false), }; + const js_value = js_value_wrapper.handle; - const js_name = v8.String.initUtf8(isolate, name).toName(); + const js_name = v8.c.v8__String__NewFromUtf8(isolate, name.ptr, v8.c.kNormal, @intCast(name.len)); // apply it both to the type itself - template.set(js_name, js_value, v8.PropertyAttribute.ReadOnly + v8.PropertyAttribute.DontDelete); + v8.c.v8__Template__Set(@ptrCast(template), js_name, js_value, v8.c.ReadOnly + v8.c.DontDelete); // and to instances of the type - target.set(js_name, js_value, v8.PropertyAttribute.ReadOnly + v8.PropertyAttribute.DontDelete); + v8.c.v8__Template__Set(@ptrCast(target), js_name, js_value, v8.c.ReadOnly + v8.c.DontDelete); }, bridge.Constructor => {}, // already handled in generateConstructor else => {}, @@ -458,8 +499,8 @@ fn attachClass(comptime JsApi: type, isolate: v8.Isolate, template: v8.FunctionT } if (@hasDecl(JsApi.Meta, "htmldda")) { - instance.markAsUndetectable(); - instance.setCallAsFunctionHandler(JsApi.Meta.callable.func); + v8.c.v8__ObjectTemplate__MarkAsUndetectable(instance); + v8.c.v8__ObjectTemplate__SetCallAsFunctionHandler(instance, JsApi.Meta.callable.func); } if (@hasDecl(JsApi.Meta, "name")) { @@ -482,10 +523,13 @@ fn protoIndexLookup(comptime JsApi: type) ?bridge.JsApiLookup.BackingInt { } // Shared illegal constructor callback for types without explicit constructors -fn illegalConstructorCallback(raw_info: ?*const v8.C_FunctionCallbackInfo) callconv(.c) void { - const info = v8.FunctionCallbackInfo.initFromV8(raw_info); - const iso = info.getIsolate(); +fn illegalConstructorCallback(raw_info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void { + const isolate = v8.c.v8__FunctionCallbackInfo__GetIsolate(raw_info); log.warn(.js, "Illegal constructor call", .{}); - const js_exception = iso.throwException(js._createException(iso, "Illegal Constructor")); - info.getReturnValue().set(js_exception); + const message = v8.c.v8__String__NewFromUtf8(isolate, "Illegal Constructor", v8.c.kNormal, 19); + const js_exception = v8.c.v8__Exception__TypeError(message); + _ = v8.c.v8__Isolate__ThrowException(isolate, js_exception); + var return_value: v8.c.ReturnValue = undefined; + v8.c.v8__FunctionCallbackInfo__GetReturnValue(raw_info, &return_value); + v8.c.v8__ReturnValue__Set(return_value, js_exception); }