Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/browser/dom/mutation_observer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
if (record.len == 0) {
return;
Expand Down
7 changes: 0 additions & 7 deletions src/browser/xhr/xhr.zig
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,6 @@ pub const XMLHttpRequest = struct {
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 {
return @intFromEnum(self.state);
}
Expand Down
68 changes: 57 additions & 11 deletions src/runtime/js.zig
Original file line number Diff line number Diff line change
Expand Up @@ -502,31 +502,34 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {

// Callbacks are PesistendObjects. When the scope ends, we need
// 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
// every PeristentObjet we've created during the lifetime of the scope.
// More importantly, it serves as an identity map - for a given Zig
// instance, we map it to the same PersistentObject.
// 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
// web APIs have to manage opaque values. Ideally, they use an
// JsObject, but the JsObject has no lifetime guarantee beyond the
// current call. They can call .persist() on their JsObject to get
// a `*PersistentObject()`. We need to track these to free them.
// 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
// this function to get the source. This is always a reference to the
// Page's fetchModuleSource, but we use a function pointer
// since this js module is decoupled from the browser implementation.
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
call_scope_end_callbacks: std.ArrayListUnmanaged(CallScopeEndCallback) = .{},
call_scope_end_callbacks: std.ArrayListUnmanaged(CallScopeEndCallback) = .empty,

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

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();
while (it.next()) |p| {
Expand Down Expand Up @@ -694,6 +708,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
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")) {
try self.call_scope_end_callbacks.append(scope_arena, CallScopeEndCallback.init(value));
}
Expand Down Expand Up @@ -1672,20 +1690,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
const CallScopeEndCallback = struct {
ptr: *anyopaque,
callScopeEndFn: *const fn (ptr: *anyopaque, scope: *Scope) void,
callScopeEndFn: *const fn (ptr: *anyopaque) void,

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

const gen = struct {
pub fn callScopeEnd(pointer: *anyopaque, scope: *Scope) void {
pub fn callScopeEnd(pointer: *anyopaque) void {
const self: T = @ptrCast(@alignCast(pointer));
return ptr_info.pointer.child.jsCallScopeEnd(self, scope);
return ptr_info.pointer.child.jsCallScopeEnd(self);
}
};

Expand All @@ -1695,8 +1741,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
};
}

pub fn callScopeEnd(self: CallScopeEndCallback, scope: *Scope) void {
self.callScopeEndFn(self.ptr, scope);
pub fn callScopeEnd(self: CallScopeEndCallback) void {
self.callScopeEndFn(self.ptr);
}
};
};
Expand Down Expand Up @@ -1776,7 +1822,7 @@ fn Caller(comptime E: type, comptime State: type) type {
// when a top-level (call_depth == 0) function ends.
if (call_depth == 0) {
for (scope.call_scope_end_callbacks.items) |cb| {
cb.callScopeEnd(scope);
cb.callScopeEnd();
}

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