mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-30 17:18:57 +00:00
666 lines
27 KiB
Zig
666 lines
27 KiB
Zig
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
|
//
|
|
// Francis Bouvier <francis@lightpanda.io>
|
|
// Pierre Tachoire <pierre@lightpanda.io>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
const std = @import("std");
|
|
const js = @import("js.zig");
|
|
const bridge = @import("bridge.zig");
|
|
const log = @import("../../log.zig");
|
|
|
|
const IS_DEBUG = @import("builtin").mode == .Debug;
|
|
|
|
const v8 = js.v8;
|
|
const JsApis = bridge.JsApis;
|
|
|
|
const Snapshot = @This();
|
|
|
|
const embedded_snapshot_blob = if (@import("build_config").snapshot_path) |path| @embedFile(path) else "";
|
|
|
|
// When creating our Snapshot, we use local function templates for every Zig type.
|
|
// You cannot, from what I can tell, create persisted FunctionTemplates at
|
|
// snapshot creation time. But you can embedd those templates (or any other v8
|
|
// Data) so that it's available to contexts created from the snapshot. This is
|
|
// the starting index of those function templates, which we can extract. At
|
|
// creation time, in debug, we assert that this is actually a consecutive integer
|
|
// sequence
|
|
data_start: usize,
|
|
|
|
// The snapshot data (v8.StartupData is a ptr to the data and len).
|
|
startup_data: v8.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).
|
|
// When we create the snapshot, we give it an array with the address of every
|
|
// external reference. When we load the snapshot, we need to give it the same
|
|
// array with the exact same number of entries in the same order (but, of course
|
|
// cross-process, the value (address) might be different).
|
|
external_references: [countExternalReferences()]isize,
|
|
|
|
// Track whether this snapshot owns its data (was created in-process)
|
|
// If false, the data points into embedded_snapshot_blob and will not be freed
|
|
owns_data: bool = false,
|
|
|
|
pub fn load() !Snapshot {
|
|
if (loadEmbedded()) |snapshot| {
|
|
return snapshot;
|
|
}
|
|
return create();
|
|
}
|
|
|
|
fn loadEmbedded() ?Snapshot {
|
|
// Binary format: [data_start: usize][blob data]
|
|
const min_size = @sizeOf(usize) + 1000;
|
|
if (embedded_snapshot_blob.len < min_size) {
|
|
// our blob should be in the MB, this is just a quick sanity check
|
|
return null;
|
|
}
|
|
|
|
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.v8__StartupData__IsValid(startup_data)) {
|
|
return null;
|
|
}
|
|
|
|
return .{
|
|
.owns_data = false,
|
|
.data_start = data_start,
|
|
.startup_data = startup_data,
|
|
.external_references = collectExternalReferences(),
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: Snapshot) void {
|
|
// Only free if we own the data (was created in-process)
|
|
if (self.owns_data) {
|
|
// V8 allocated this with `new char[]`, so we need to use the C++ delete[] operator
|
|
v8.v8__StartupData__DELETE(self.startup_data.data);
|
|
}
|
|
}
|
|
|
|
pub fn write(self: Snapshot, writer: *std.Io.Writer) !void {
|
|
if (!self.isValid()) {
|
|
return error.InvalidSnapshot;
|
|
}
|
|
|
|
try writer.writeInt(usize, self.data_start, .little);
|
|
try writer.writeAll(self.startup_data.data[0..@intCast(self.startup_data.raw_size)]);
|
|
}
|
|
|
|
pub fn fromEmbedded(self: Snapshot) bool {
|
|
// if the snapshot comes from the embedFile, then it'll be flagged as not
|
|
// owning (aka, not needing to free) the data.
|
|
return self.owns_data == false;
|
|
}
|
|
|
|
fn isValid(self: Snapshot) bool {
|
|
return v8.v8__StartupData__IsValid(self.startup_data);
|
|
}
|
|
|
|
pub fn create() !Snapshot {
|
|
var external_references = collectExternalReferences();
|
|
|
|
var params: v8.CreateParams = undefined;
|
|
v8.v8__Isolate__CreateParams__CONSTRUCT(¶ms);
|
|
params.array_buffer_allocator = v8.v8__ArrayBuffer__Allocator__NewDefaultAllocator();
|
|
defer v8.v8__ArrayBuffer__Allocator__DELETE(params.array_buffer_allocator.?);
|
|
params.external_references = @ptrCast(&external_references);
|
|
|
|
const snapshot_creator = v8.v8__SnapshotCreator__CREATE(¶ms);
|
|
defer v8.v8__SnapshotCreator__DESTRUCT(snapshot_creator);
|
|
|
|
var data_start: usize = 0;
|
|
const isolate = v8.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.v8__HandleScope__CONSTRUCT(&handle_scope, isolate);
|
|
defer v8.v8__HandleScope__DESTRUCT(&handle_scope);
|
|
|
|
// Create templates (constructors only) FIRST
|
|
var templates: [JsApis.len]*const v8.FunctionTemplate = undefined;
|
|
inline for (JsApis, 0..) |JsApi, i| {
|
|
@setEvalBranchQuota(10_000);
|
|
templates[i] = generateConstructor(JsApi, isolate);
|
|
attachClass(JsApi, isolate, templates[i]);
|
|
}
|
|
|
|
// Set up prototype chains BEFORE attaching properties
|
|
// This must come before attachClass so inheritance is set up first
|
|
inline for (JsApis, 0..) |JsApi, i| {
|
|
if (comptime protoIndexLookup(JsApi)) |proto_index| {
|
|
v8.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 context = v8.v8__Context__New(isolate, null, null);
|
|
v8.v8__Context__Enter(context);
|
|
defer v8.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 = v8.v8__SnapshotCreator__AddData(snapshot_creator, @ptrCast(templates[i]));
|
|
if (i == 0) {
|
|
data_start = data_index;
|
|
last_data_index = data_index;
|
|
} else {
|
|
// This isn't strictly required, but it means we only need to keep
|
|
// the first data_index. This is based on the assumption that
|
|
// addDataWithContext always increases by 1. If we ever hit this
|
|
// error, then that assumption is wrong and we should capture
|
|
// all the indexes explicitly in an array.
|
|
if (data_index != last_data_index + 1) {
|
|
return error.InvalidDataIndex;
|
|
}
|
|
last_data_index = data_index;
|
|
}
|
|
}
|
|
|
|
// Realize all templates by getting their functions and attaching to global
|
|
const global_obj = v8.v8__Context__Global(context);
|
|
|
|
inline for (JsApis, 0..) |JsApi, i| {
|
|
const func = v8.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 alias = JsApi.Meta.constructor_alias;
|
|
const v8_class_name = v8.v8__String__NewFromUtf8(isolate, alias.ptr, v8.kNormal, @intCast(alias.len));
|
|
var maybe_result: v8.MaybeBool = undefined;
|
|
v8.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
|
|
// new HTMLImageElement() isn't.
|
|
// 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 name = JsApi.Meta.name;
|
|
const illegal_class_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
|
var maybe_result2: v8.MaybeBool = undefined;
|
|
v8.v8__Object__DefineOwnProperty(global_obj, context, illegal_class_name, func, 0, &maybe_result2);
|
|
} else {
|
|
const name = JsApi.Meta.name;
|
|
const v8_class_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
|
var maybe_result: v8.MaybeBool = undefined;
|
|
var properties: v8.PropertyAttribute = v8.None;
|
|
if (@hasDecl(JsApi.Meta, "enumerable") and JsApi.Meta.enumerable == false) {
|
|
properties |= v8.DontEnum;
|
|
}
|
|
v8.v8__Object__DefineOwnProperty(global_obj, context, v8_class_name, func, properties, &maybe_result);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
// If we want to overwrite the built-in console, we have to
|
|
// delete the built-in one.
|
|
const console_key = v8.v8__String__NewFromUtf8(isolate, "console", v8.kNormal, 7);
|
|
var maybe_deleted: v8.MaybeBool = undefined;
|
|
v8.v8__Object__Delete(global_obj, context, console_key, &maybe_deleted);
|
|
if (maybe_deleted.value == false) {
|
|
return error.ConsoleDeleteError;
|
|
}
|
|
}
|
|
|
|
// This shouldn't be necessary, but it is:
|
|
// https://groups.google.com/g/v8-users/c/qAQQBmbi--8
|
|
// 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_func = v8.v8__FunctionTemplate__GetFunction(templates[proto_index], context);
|
|
const proto_obj: *const v8.Object = @ptrCast(proto_func);
|
|
|
|
const self_func = v8.v8__FunctionTemplate__GetFunction(templates[i], context);
|
|
const self_obj: *const v8.Object = @ptrCast(self_func);
|
|
|
|
var maybe_result: v8.MaybeBool = undefined;
|
|
v8.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_str = "DOMException.prototype.__proto__ = Error.prototype";
|
|
const code = v8.v8__String__NewFromUtf8(isolate, code_str.ptr, v8.kNormal, @intCast(code_str.len));
|
|
const script = v8.v8__Script__Compile(context, code, null) orelse return error.ScriptCompileFailed;
|
|
_ = v8.v8__Script__Run(script, context) orelse return error.ScriptRunFailed;
|
|
}
|
|
|
|
v8.v8__SnapshotCreator__setDefaultContext(snapshot_creator, context);
|
|
}
|
|
|
|
const blob = v8.v8__SnapshotCreator__createBlob(snapshot_creator, v8.kKeep);
|
|
|
|
return .{
|
|
.owns_data = true,
|
|
.data_start = data_start,
|
|
.external_references = external_references,
|
|
.startup_data = blob,
|
|
};
|
|
}
|
|
|
|
// Helper to check if a JsApi has a NamedIndexed handler
|
|
fn hasNamedIndexedGetter(comptime JsApi: type) bool {
|
|
const declarations = @typeInfo(JsApi).@"struct".decls;
|
|
inline for (declarations) |d| {
|
|
const value = @field(JsApi, d.name);
|
|
const T = @TypeOf(value);
|
|
if (T == bridge.NamedIndexed) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Count total callbacks needed for external_references array
|
|
fn countExternalReferences() comptime_int {
|
|
@setEvalBranchQuota(100_000);
|
|
|
|
var count: comptime_int = 0;
|
|
|
|
// +1 for the illegal constructor callback shared by various types
|
|
count += 1;
|
|
|
|
// +1 for the noop function shared by various types
|
|
count += 1;
|
|
|
|
inline for (JsApis) |JsApi| {
|
|
// Constructor (only if explicit)
|
|
if (@hasDecl(JsApi, "constructor")) {
|
|
count += 1;
|
|
}
|
|
|
|
// Callable (htmldda)
|
|
if (@hasDecl(JsApi, "callable")) {
|
|
count += 1;
|
|
}
|
|
|
|
// All other callbacks
|
|
const declarations = @typeInfo(JsApi).@"struct".decls;
|
|
inline for (declarations) |d| {
|
|
const value = @field(JsApi, d.name);
|
|
const T = @TypeOf(value);
|
|
if (T == bridge.Accessor) {
|
|
count += 1; // getter
|
|
if (value.setter != null) {
|
|
count += 1;
|
|
}
|
|
} else if (T == bridge.Function) {
|
|
count += 1;
|
|
} else if (T == bridge.Iterator) {
|
|
count += 1;
|
|
} else if (T == bridge.Indexed) {
|
|
count += 1;
|
|
if (value.enumerator != null) {
|
|
count += 1;
|
|
}
|
|
} else if (T == bridge.NamedIndexed) {
|
|
count += 1; // getter
|
|
if (value.setter != null) count += 1;
|
|
if (value.deleter != null) count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// In debug mode, add unknown property callbacks for types without NamedIndexed
|
|
if (comptime IS_DEBUG) {
|
|
inline for (JsApis) |JsApi| {
|
|
if (!hasNamedIndexedGetter(JsApi)) {
|
|
count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return count + 1; // +1 for null terminator
|
|
}
|
|
|
|
fn collectExternalReferences() [countExternalReferences()]isize {
|
|
var idx: usize = 0;
|
|
var references = std.mem.zeroes([countExternalReferences()]isize);
|
|
|
|
references[idx] = @bitCast(@intFromPtr(&illegalConstructorCallback));
|
|
idx += 1;
|
|
|
|
references[idx] = @bitCast(@intFromPtr(&bridge.Function.noopFunction));
|
|
idx += 1;
|
|
|
|
inline for (JsApis) |JsApi| {
|
|
if (@hasDecl(JsApi, "constructor")) {
|
|
references[idx] = @bitCast(@intFromPtr(JsApi.constructor.func));
|
|
idx += 1;
|
|
}
|
|
|
|
if (@hasDecl(JsApi, "callable")) {
|
|
references[idx] = @bitCast(@intFromPtr(JsApi.callable.func));
|
|
idx += 1;
|
|
}
|
|
|
|
const declarations = @typeInfo(JsApi).@"struct".decls;
|
|
inline for (declarations) |d| {
|
|
const value = @field(JsApi, d.name);
|
|
const T = @TypeOf(value);
|
|
if (T == bridge.Accessor) {
|
|
references[idx] = @bitCast(@intFromPtr(value.getter));
|
|
idx += 1;
|
|
if (value.setter) |setter| {
|
|
references[idx] = @bitCast(@intFromPtr(setter));
|
|
idx += 1;
|
|
}
|
|
} else if (T == bridge.Function) {
|
|
references[idx] = @bitCast(@intFromPtr(value.func));
|
|
idx += 1;
|
|
} else if (T == bridge.Iterator) {
|
|
references[idx] = @bitCast(@intFromPtr(value.func));
|
|
idx += 1;
|
|
} else if (T == bridge.Indexed) {
|
|
references[idx] = @bitCast(@intFromPtr(value.getter));
|
|
idx += 1;
|
|
if (value.enumerator) |enumerator| {
|
|
references[idx] = @bitCast(@intFromPtr(enumerator));
|
|
idx += 1;
|
|
}
|
|
} else if (T == bridge.NamedIndexed) {
|
|
references[idx] = @bitCast(@intFromPtr(value.getter));
|
|
idx += 1;
|
|
if (value.setter) |setter| {
|
|
references[idx] = @bitCast(@intFromPtr(setter));
|
|
idx += 1;
|
|
}
|
|
if (value.deleter) |deleter| {
|
|
references[idx] = @bitCast(@intFromPtr(deleter));
|
|
idx += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// In debug mode, collect unknown property callbacks for types without NamedIndexed
|
|
if (comptime IS_DEBUG) {
|
|
inline for (JsApis) |JsApi| {
|
|
if (!hasNamedIndexedGetter(JsApi)) {
|
|
references[idx] = @bitCast(@intFromPtr(bridge.unknownObjectPropertyCallback(JsApi)));
|
|
idx += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return references;
|
|
}
|
|
|
|
// Even if a struct doesn't have a `constructor` function, we still
|
|
// `generateConstructor`, because this is how we create our
|
|
// FunctionTemplate. Such classes exist, but they can't be instantiated
|
|
// 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) *const v8.FunctionTemplate {
|
|
const callback = blk: {
|
|
if (@hasDecl(JsApi, "constructor")) {
|
|
break :blk JsApi.constructor.func;
|
|
}
|
|
|
|
// Use shared illegal constructor callback
|
|
break :blk illegalConstructorCallback;
|
|
};
|
|
|
|
const template = v8.v8__FunctionTemplate__New__DEFAULT2(isolate, callback).?;
|
|
{
|
|
const internal_field_count = comptime countInternalFields(JsApi);
|
|
if (internal_field_count > 0) {
|
|
const instance_template = v8.v8__FunctionTemplate__InstanceTemplate(template);
|
|
v8.v8__ObjectTemplate__SetInternalFieldCount(instance_template, internal_field_count);
|
|
}
|
|
}
|
|
const name_str = if (@hasDecl(JsApi.Meta, "name")) JsApi.Meta.name else @typeName(JsApi);
|
|
const class_name = v8.v8__String__NewFromUtf8(isolate, name_str.ptr, v8.kNormal, @intCast(name_str.len));
|
|
v8.v8__FunctionTemplate__SetClassName(template, class_name);
|
|
return template;
|
|
}
|
|
|
|
pub fn countInternalFields(comptime JsApi: type) u8 {
|
|
var last_used_id = 0;
|
|
var cache_count: u8 = 0;
|
|
|
|
inline for (@typeInfo(JsApi).@"struct".decls) |d| {
|
|
const name: [:0]const u8 = d.name;
|
|
const value = @field(JsApi, name);
|
|
const definition = @TypeOf(value);
|
|
|
|
switch (definition) {
|
|
inline bridge.Accessor, bridge.Function => {
|
|
const cache = value.cache orelse continue;
|
|
if (cache != .internal) {
|
|
continue;
|
|
}
|
|
// We assert that they are declared in-order. This isn't necessary
|
|
// but I don't want to do anything fancy to look for gaps or
|
|
// duplicates.
|
|
const internal_id = cache.internal;
|
|
if (internal_id != last_used_id + 1) {
|
|
@compileError(@typeName(JsApi) ++ "." ++ name ++ " has a non-monotonic cache index");
|
|
}
|
|
last_used_id = internal_id;
|
|
cache_count += 1; // this is just last_used, but it's more explicit this way
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
if (@hasDecl(JsApi.Meta, "empty_with_no_proto")) {
|
|
return cache_count;
|
|
}
|
|
|
|
// we need cache_count internal fields, + 1 for the TAO pointer (the v8 -> Zig)
|
|
// mapping) itself.
|
|
return cache_count + 1;
|
|
}
|
|
|
|
// Attaches JsApi members to the prototype template (normal case)
|
|
fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *const v8.FunctionTemplate) void {
|
|
const instance = v8.v8__FunctionTemplate__InstanceTemplate(template);
|
|
const prototype = v8.v8__FunctionTemplate__PrototypeTemplate(template);
|
|
|
|
// Create a signature that validates the receiver is an instance of this template.
|
|
// This prevents crashes when JavaScript extracts a getter/method and calls it
|
|
// with the wrong `this` (e.g., documentGetter.call(null)).
|
|
const signature = v8.v8__Signature__New(isolate, template);
|
|
|
|
const declarations = @typeInfo(JsApi).@"struct".decls;
|
|
var has_named_index_getter = false;
|
|
|
|
inline for (declarations) |d| {
|
|
const name: [:0]const u8 = d.name;
|
|
const value = @field(JsApi, name);
|
|
const definition = @TypeOf(value);
|
|
|
|
switch (definition) {
|
|
bridge.Accessor => {
|
|
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
|
const getter_signature = if (value.static) null else signature;
|
|
const getter_callback = v8.v8__FunctionTemplate__New__Config(isolate, &.{
|
|
.callback = value.getter,
|
|
.signature = getter_signature,
|
|
}).?;
|
|
const setter_callback = if (value.setter) |setter|
|
|
v8.v8__FunctionTemplate__New__Config(isolate, &.{
|
|
.callback = setter,
|
|
.signature = getter_signature,
|
|
}).?
|
|
else
|
|
null;
|
|
|
|
var attribute: v8.PropertyAttribute = 0;
|
|
if (value.setter == null) {
|
|
attribute |= v8.ReadOnly;
|
|
}
|
|
if (value.deletable == false) {
|
|
attribute |= v8.DontDelete;
|
|
}
|
|
|
|
if (value.static) {
|
|
// Static accessors: use Template's SetAccessorProperty
|
|
v8.v8__Template__SetAccessorProperty(@ptrCast(template), js_name, getter_callback, setter_callback, attribute);
|
|
} else {
|
|
v8.v8__ObjectTemplate__SetAccessorProperty__Config(prototype, &.{
|
|
.key = js_name,
|
|
.getter = getter_callback,
|
|
.setter = setter_callback,
|
|
.attribute = attribute,
|
|
});
|
|
}
|
|
},
|
|
bridge.Function => {
|
|
// For non-static functions, use the signature to validate the receiver
|
|
const func_signature = if (value.static) null else signature;
|
|
const function_template = v8.v8__FunctionTemplate__New__Config(isolate, &.{
|
|
.callback = value.func,
|
|
.length = value.arity,
|
|
.signature = func_signature,
|
|
}).?;
|
|
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
|
if (value.static) {
|
|
v8.v8__Template__Set(@ptrCast(template), js_name, @ptrCast(function_template), v8.None);
|
|
} else {
|
|
v8.v8__Template__Set(@ptrCast(prototype), js_name, @ptrCast(function_template), v8.None);
|
|
}
|
|
},
|
|
bridge.Indexed => {
|
|
var configuration: v8.IndexedPropertyHandlerConfiguration = .{
|
|
.getter = value.getter,
|
|
.enumerator = value.enumerator,
|
|
.setter = null,
|
|
.query = null,
|
|
.deleter = null,
|
|
.definer = null,
|
|
.descriptor = null,
|
|
.data = null,
|
|
.flags = 0,
|
|
};
|
|
v8.v8__ObjectTemplate__SetIndexedHandler(instance, &configuration);
|
|
},
|
|
bridge.NamedIndexed => {
|
|
var configuration: v8.NamedPropertyHandlerConfiguration = .{
|
|
.getter = value.getter,
|
|
.setter = value.setter,
|
|
.query = null,
|
|
.deleter = value.deleter,
|
|
.enumerator = null,
|
|
.definer = null,
|
|
.descriptor = null,
|
|
.data = null,
|
|
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
|
|
};
|
|
v8.v8__ObjectTemplate__SetNamedHandler(instance, &configuration);
|
|
has_named_index_getter = true;
|
|
},
|
|
bridge.Iterator => {
|
|
const function_template = v8.v8__FunctionTemplate__New__Config(isolate, &.{ .callback = value.func }).?;
|
|
const js_name = if (value.async)
|
|
v8.v8__Symbol__GetAsyncIterator(isolate)
|
|
else
|
|
v8.v8__Symbol__GetIterator(isolate);
|
|
v8.v8__Template__Set(@ptrCast(prototype), js_name, @ptrCast(function_template), v8.None);
|
|
},
|
|
bridge.Property => {
|
|
const js_value = switch (value.value) {
|
|
.null => js.simpleZigValueToJs(.{ .handle = isolate }, null, true, false),
|
|
inline .bool, .int, .float, .string => |v| js.simpleZigValueToJs(.{ .handle = isolate }, v, true, false),
|
|
};
|
|
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
|
|
|
|
{
|
|
const flags = if (value.readonly) v8.ReadOnly + v8.DontDelete else 0;
|
|
v8.v8__Template__Set(@ptrCast(prototype), js_name, js_value, flags);
|
|
}
|
|
|
|
if (value.template) {
|
|
// apply it both to the type itself (e.g. Node.Elem)
|
|
v8.v8__Template__Set(@ptrCast(template), js_name, js_value, v8.ReadOnly + v8.DontDelete);
|
|
}
|
|
},
|
|
bridge.Constructor => {}, // already handled in generateConstructor
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
if (@hasDecl(JsApi.Meta, "htmldda")) {
|
|
v8.v8__ObjectTemplate__MarkAsUndetectable(instance);
|
|
v8.v8__ObjectTemplate__SetCallAsFunctionHandler(instance, JsApi.Meta.callable.func);
|
|
}
|
|
|
|
if (@hasDecl(JsApi.Meta, "name")) {
|
|
const js_name = v8.v8__Symbol__GetToStringTag(isolate);
|
|
const js_value = v8.v8__String__NewFromUtf8(isolate, JsApi.Meta.name.ptr, v8.kNormal, @intCast(JsApi.Meta.name.len));
|
|
v8.v8__Template__Set(@ptrCast(instance), js_name, js_value, v8.ReadOnly + v8.DontDelete);
|
|
}
|
|
|
|
if (comptime IS_DEBUG) {
|
|
if (!has_named_index_getter) {
|
|
var configuration: v8.NamedPropertyHandlerConfiguration = .{
|
|
.getter = bridge.unknownObjectPropertyCallback(JsApi),
|
|
.setter = null,
|
|
.query = null,
|
|
.deleter = null,
|
|
.enumerator = null,
|
|
.definer = null,
|
|
.descriptor = null,
|
|
.data = null,
|
|
.flags = v8.kOnlyInterceptStrings | v8.kNonMasking,
|
|
};
|
|
v8.v8__ObjectTemplate__SetNamedHandler(instance, &configuration);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn protoIndexLookup(comptime JsApi: type) ?bridge.JsApiLookup.BackingInt {
|
|
@setEvalBranchQuota(2000);
|
|
comptime {
|
|
const T = JsApi.bridge.type;
|
|
if (!@hasField(T, "_proto")) {
|
|
return null;
|
|
}
|
|
const Ptr = std.meta.fieldInfo(T, ._proto).type;
|
|
const F = @typeInfo(Ptr).pointer.child;
|
|
return bridge.JsApiLookup.getId(F.JsApi);
|
|
}
|
|
}
|
|
|
|
// Shared illegal constructor callback for types without explicit constructors
|
|
fn illegalConstructorCallback(raw_info: ?*const v8.FunctionCallbackInfo) callconv(.c) void {
|
|
const isolate = v8.v8__FunctionCallbackInfo__GetIsolate(raw_info);
|
|
log.warn(.js, "Illegal constructor call", .{});
|
|
|
|
const message = v8.v8__String__NewFromUtf8(isolate, "Illegal Constructor", v8.kNormal, 19);
|
|
const js_exception = v8.v8__Exception__TypeError(message);
|
|
|
|
_ = v8.v8__Isolate__ThrowException(isolate, js_exception);
|
|
var return_value: v8.ReturnValue = undefined;
|
|
v8.v8__FunctionCallbackInfo__GetReturnValue(raw_info, &return_value);
|
|
v8.v8__ReturnValue__Set(return_value, js_exception);
|
|
}
|