Skip to content

Commit 3a15790

Browse files
authored
Merge pull request #671 from lightpanda-io/webapi_destructor
Allow webapis to register a destructor to do cleanup on scope (page) end
2 parents 3f31573 + ce832a8 commit 3a15790

File tree

3 files changed

+58
-19
lines changed

3 files changed

+58
-19
lines changed

src/browser/dom/mutation_observer.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ pub const MutationObserver = struct {
103103
}
104104
}
105105

106-
pub fn jsCallScopeEnd(self: *MutationObserver, _: anytype) void {
106+
pub fn jsCallScopeEnd(self: *MutationObserver) void {
107107
const record = self.observed.items;
108108
if (record.len == 0) {
109109
return;

src/browser/xhr/xhr.zig

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -278,13 +278,6 @@ pub const XMLHttpRequest = struct {
278278
self.priv_state = .new;
279279
}
280280

281-
pub fn deinit(self: *XMLHttpRequest, alloc: Allocator) void {
282-
if (self.response_obj) |v| {
283-
v.deinit();
284-
}
285-
self.proto.deinit(alloc);
286-
}
287-
288281
pub fn get_readyState(self: *XMLHttpRequest) u16 {
289282
return @intFromEnum(self.state);
290283
}

src/runtime/js.zig

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -502,31 +502,34 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
502502

503503
// Callbacks are PesistendObjects. When the scope ends, we need
504504
// to free every callback we created.
505-
callbacks: std.ArrayListUnmanaged(v8.Persistent(v8.Function)) = .{},
505+
callbacks: std.ArrayListUnmanaged(v8.Persistent(v8.Function)) = .empty,
506506

507507
// Serves two purposes. Like `callbacks` above, this is used to free
508508
// every PeristentObjet we've created during the lifetime of the scope.
509509
// More importantly, it serves as an identity map - for a given Zig
510510
// instance, we map it to the same PersistentObject.
511511
// The key is the @intFromPtr of the Zig value
512-
identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .{},
512+
identity_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty,
513513

514514
// Similar to the identity map, but used much less frequently. Some
515515
// web APIs have to manage opaque values. Ideally, they use an
516516
// JsObject, but the JsObject has no lifetime guarantee beyond the
517517
// current call. They can call .persist() on their JsObject to get
518518
// a `*PersistentObject()`. We need to track these to free them.
519519
// The key is the @intFromPtr of the v8.Object.handle.
520-
js_object_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .{},
520+
js_object_map: std.AutoHashMapUnmanaged(usize, PersistentObject) = .empty,
521521

522522
// When we need to load a resource (i.e. an external script), we call
523523
// this function to get the source. This is always a reference to the
524524
// Page's fetchModuleSource, but we use a function pointer
525525
// since this js module is decoupled from the browser implementation.
526526
module_loader: ModuleLoader,
527527

528+
// Some Zig types have code to execute to cleanup
529+
destructor_callbacks: std.ArrayListUnmanaged(DestructorCallback) = .empty,
530+
528531
// Some Zig types have code to execute when the call scope ends
529-
call_scope_end_callbacks: std.ArrayListUnmanaged(CallScopeEndCallback) = .{},
532+
call_scope_end_callbacks: std.ArrayListUnmanaged(CallScopeEndCallback) = .empty,
530533

531534
const ModuleLoader = struct {
532535
ptr: *anyopaque,
@@ -536,6 +539,17 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
536539
// no init, started with executor.startScope()
537540

538541
fn deinit(self: *Scope) void {
542+
{
543+
// reverse order, as this has more chance of respecting any
544+
// dependencies objects might have with each other.
545+
const items = self.destructor_callbacks.items;
546+
var i = items.len;
547+
while (i > 0) {
548+
i -= 1;
549+
items[i].destructor();
550+
}
551+
}
552+
539553
{
540554
var it = self.identity_map.valueIterator();
541555
while (it.next()) |p| {
@@ -694,6 +708,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
694708
return gop.value_ptr.*;
695709
}
696710

711+
if (comptime @hasDecl(ptr.child, "destructor")) {
712+
try self.destructor_callbacks.append(scope_arena, DestructorCallback.init(value));
713+
}
714+
697715
if (comptime @hasDecl(ptr.child, "jsCallScopeEnd")) {
698716
try self.call_scope_end_callbacks.append(scope_arena, CallScopeEndCallback.init(value));
699717
}
@@ -1700,20 +1718,48 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
17001718
}
17011719
}
17021720

1703-
// An interface for types that want to their jsScopeEnd function to be
1721+
// An interface for types that want to have their jsDeinit function to be
1722+
// called when the call scope ends
1723+
const DestructorCallback = struct {
1724+
ptr: *anyopaque,
1725+
destructorFn: *const fn (ptr: *anyopaque) void,
1726+
1727+
fn init(ptr: anytype) DestructorCallback {
1728+
const T = @TypeOf(ptr);
1729+
const ptr_info = @typeInfo(T);
1730+
1731+
const gen = struct {
1732+
pub fn destructor(pointer: *anyopaque) void {
1733+
const self: T = @ptrCast(@alignCast(pointer));
1734+
return ptr_info.pointer.child.destructor(self);
1735+
}
1736+
};
1737+
1738+
return .{
1739+
.ptr = ptr,
1740+
.destructorFn = gen.destructor,
1741+
};
1742+
}
1743+
1744+
pub fn destructor(self: DestructorCallback) void {
1745+
self.destructorFn(self.ptr);
1746+
}
1747+
};
1748+
1749+
// An interface for types that want to have their jsScopeEnd function be
17041750
// called when the call scope ends
17051751
const CallScopeEndCallback = struct {
17061752
ptr: *anyopaque,
1707-
callScopeEndFn: *const fn (ptr: *anyopaque, scope: *Scope) void,
1753+
callScopeEndFn: *const fn (ptr: *anyopaque) void,
17081754

17091755
fn init(ptr: anytype) CallScopeEndCallback {
17101756
const T = @TypeOf(ptr);
17111757
const ptr_info = @typeInfo(T);
17121758

17131759
const gen = struct {
1714-
pub fn callScopeEnd(pointer: *anyopaque, scope: *Scope) void {
1760+
pub fn callScopeEnd(pointer: *anyopaque) void {
17151761
const self: T = @ptrCast(@alignCast(pointer));
1716-
return ptr_info.pointer.child.jsCallScopeEnd(self, scope);
1762+
return ptr_info.pointer.child.jsCallScopeEnd(self);
17171763
}
17181764
};
17191765

@@ -1723,8 +1769,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
17231769
};
17241770
}
17251771

1726-
pub fn callScopeEnd(self: CallScopeEndCallback, scope: *Scope) void {
1727-
self.callScopeEndFn(self.ptr, scope);
1772+
pub fn callScopeEnd(self: CallScopeEndCallback) void {
1773+
self.callScopeEndFn(self.ptr);
17281774
}
17291775
};
17301776
};
@@ -1804,7 +1850,7 @@ fn Caller(comptime E: type, comptime State: type) type {
18041850
// when a top-level (call_depth == 0) function ends.
18051851
if (call_depth == 0) {
18061852
for (scope.call_scope_end_callbacks.items) |cb| {
1807-
cb.callScopeEnd(scope);
1853+
cb.callScopeEnd();
18081854
}
18091855

18101856
const arena: *ArenaAllocator = @alignCast(@ptrCast(scope.call_arena.ptr));

0 commit comments

Comments
 (0)