Merge pull request #671 from lightpanda-io/webapi_destructor

Allow webapis to register a destructor to do cleanup on scope (page) end
This commit is contained in:
Karl Seguin
2025-05-21 18:09:42 +08:00
committed by GitHub
3 changed files with 58 additions and 19 deletions

View File

@@ -103,7 +103,7 @@ pub const MutationObserver = struct {
} }
} }
pub fn jsCallScopeEnd(self: *MutationObserver, _: anytype) void { pub fn jsCallScopeEnd(self: *MutationObserver) void {
const record = self.observed.items; const record = self.observed.items;
if (record.len == 0) { if (record.len == 0) {
return; return;

View File

@@ -278,13 +278,6 @@ pub const XMLHttpRequest = struct {
self.priv_state = .new; self.priv_state = .new;
} }
pub fn deinit(self: *XMLHttpRequest, alloc: Allocator) void {
if (self.response_obj) |v| {
v.deinit();
}
self.proto.deinit(alloc);
}
pub fn get_readyState(self: *XMLHttpRequest) u16 { pub fn get_readyState(self: *XMLHttpRequest) u16 {
return @intFromEnum(self.state); return @intFromEnum(self.state);
} }

View File

@@ -502,14 +502,14 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// Callbacks are PesistendObjects. When the scope ends, we need // Callbacks are PesistendObjects. When the scope ends, we need
// to free every callback we created. // to free every callback we created.
callbacks: std.ArrayListUnmanaged(v8.Persistent(v8.Function)) = .{}, callbacks: std.ArrayListUnmanaged(v8.Persistent(v8.Function)) = .empty,
// Serves two purposes. Like `callbacks` above, this is used to free // Serves two purposes. Like `callbacks` above, this is used to free
// every PeristentObjet we've created during the lifetime of the scope. // every PeristentObjet we've created during the lifetime of the scope.
// More importantly, it serves as an identity map - for a given Zig // More importantly, it serves as an identity map - for a given Zig
// instance, we map it to the same PersistentObject. // instance, we map it to the same PersistentObject.
// The key is the @intFromPtr of the Zig value // The key is the @intFromPtr of the Zig value
identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .{}, identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty,
// Similar to the identity map, but used much less frequently. Some // Similar to the identity map, but used much less frequently. Some
// web APIs have to manage opaque values. Ideally, they use an // web APIs have to manage opaque values. Ideally, they use an
@@ -517,7 +517,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// current call. They can call .persist() on their JsObject to get // current call. They can call .persist() on their JsObject to get
// a `*PersistentObject()`. We need to track these to free them. // a `*PersistentObject()`. We need to track these to free them.
// The key is the @intFromPtr of the v8.Object.handle. // The key is the @intFromPtr of the v8.Object.handle.
js_object_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .{}, js_object_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty,
// When we need to load a resource (i.e. an external script), we call // When we need to load a resource (i.e. an external script), we call
// this function to get the source. This is always a reference to the // this function to get the source. This is always a reference to the
@@ -525,8 +525,11 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// since this js module is decoupled from the browser implementation. // since this js module is decoupled from the browser implementation.
module_loader: ModuleLoader, module_loader: ModuleLoader,
// Some Zig types have code to execute to cleanup
destructor_callbacks: std.ArrayListUnmanaged(DestructorCallback) = .empty,
// Some Zig types have code to execute when the call scope ends // Some Zig types have code to execute when the call scope ends
call_scope_end_callbacks: std.ArrayListUnmanaged(CallScopeEndCallback) = .{}, call_scope_end_callbacks: std.ArrayListUnmanaged(CallScopeEndCallback) = .empty,
const ModuleLoader = struct { const ModuleLoader = struct {
ptr: *anyopaque, ptr: *anyopaque,
@@ -536,6 +539,17 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// no init, started with executor.startScope() // no init, started with executor.startScope()
fn deinit(self: *Scope) void { fn deinit(self: *Scope) void {
{
// reverse order, as this has more chance of respecting any
// dependencies objects might have with each other.
const items = self.destructor_callbacks.items;
var i = items.len;
while (i > 0) {
i -= 1;
items[i].destructor();
}
}
{ {
var it = self.identity_map.valueIterator(); var it = self.identity_map.valueIterator();
while (it.next()) |p| { while (it.next()) |p| {
@@ -694,6 +708,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
return gop.value_ptr.*; return gop.value_ptr.*;
} }
if (comptime @hasDecl(ptr.child, "destructor")) {
try self.destructor_callbacks.append(scope_arena, DestructorCallback.init(value));
}
if (comptime @hasDecl(ptr.child, "jsCallScopeEnd")) { if (comptime @hasDecl(ptr.child, "jsCallScopeEnd")) {
try self.call_scope_end_callbacks.append(scope_arena, CallScopeEndCallback.init(value)); try self.call_scope_end_callbacks.append(scope_arena, CallScopeEndCallback.init(value));
} }
@@ -1700,20 +1718,48 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
} }
} }
// An interface for types that want to their jsScopeEnd function to be // An interface for types that want to have their jsDeinit function to be
// called when the call scope ends
const DestructorCallback = struct {
ptr: *anyopaque,
destructorFn: *const fn (ptr: *anyopaque) void,
fn init(ptr: anytype) DestructorCallback {
const T = @TypeOf(ptr);
const ptr_info = @typeInfo(T);
const gen = struct {
pub fn destructor(pointer: *anyopaque) void {
const self: T = @ptrCast(@alignCast(pointer));
return ptr_info.pointer.child.destructor(self);
}
};
return .{
.ptr = ptr,
.destructorFn = gen.destructor,
};
}
pub fn destructor(self: DestructorCallback) void {
self.destructorFn(self.ptr);
}
};
// An interface for types that want to have their jsScopeEnd function be
// called when the call scope ends // called when the call scope ends
const CallScopeEndCallback = struct { const CallScopeEndCallback = struct {
ptr: *anyopaque, ptr: *anyopaque,
callScopeEndFn: *const fn (ptr: *anyopaque, scope: *Scope) void, callScopeEndFn: *const fn (ptr: *anyopaque) void,
fn init(ptr: anytype) CallScopeEndCallback { fn init(ptr: anytype) CallScopeEndCallback {
const T = @TypeOf(ptr); const T = @TypeOf(ptr);
const ptr_info = @typeInfo(T); const ptr_info = @typeInfo(T);
const gen = struct { const gen = struct {
pub fn callScopeEnd(pointer: *anyopaque, scope: *Scope) void { pub fn callScopeEnd(pointer: *anyopaque) void {
const self: T = @ptrCast(@alignCast(pointer)); const self: T = @ptrCast(@alignCast(pointer));
return ptr_info.pointer.child.jsCallScopeEnd(self, scope); return ptr_info.pointer.child.jsCallScopeEnd(self);
} }
}; };
@@ -1723,8 +1769,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
}; };
} }
pub fn callScopeEnd(self: CallScopeEndCallback, scope: *Scope) void { pub fn callScopeEnd(self: CallScopeEndCallback) void {
self.callScopeEndFn(self.ptr, scope); self.callScopeEndFn(self.ptr);
} }
}; };
}; };
@@ -1804,7 +1850,7 @@ fn Caller(comptime E: type, comptime State: type) type {
// when a top-level (call_depth == 0) function ends. // when a top-level (call_depth == 0) function ends.
if (call_depth == 0) { if (call_depth == 0) {
for (scope.call_scope_end_callbacks.items) |cb| { for (scope.call_scope_end_callbacks.items) |cb| {
cb.callScopeEnd(scope); cb.callScopeEnd();
} }
const arena: *ArenaAllocator = @alignCast(@ptrCast(scope.call_arena.ptr)); const arena: *ArenaAllocator = @alignCast(@ptrCast(scope.call_arena.ptr));