7 Commits

Author SHA1 Message Date
Karl Seguin
269924090a fix double free 2026-03-29 07:12:18 +08:00
Karl Seguin
ad54437ca3 zig fmt 2026-03-28 21:43:46 +08:00
Karl Seguin
01ecb296e5 Rework finalizers
This commit involves a number of changes to finalizers, all aimed towards
better consistency and reliability.

A big part of this has to do with v8::Inspector's ability to move objects
across IsolatedWorlds. There has been a few previous efforts on this, the most
significant being https://github.com/lightpanda-io/browser/pull/1901. To recap,
a Zig instance can map to 0-N v8::Objects. Where N is the total number of
IsolatedWorlds. Generally, IsolatedWorlds between origins are...isolated...but
the v8::Inspector isn't bound by this. So a Zig instance cannot be tied to a
Context/Identity/IsolatedWorld...it has to live until all references, possibly
from different IsolatedWorlds, are released (or the page is reset).

Finalizers could previously be managed via reference counting or explicitly
toggling the instance as weak/strong. Now, only reference counting is supported.
weak/strong can essentially be seen as an acquireRef (rc += 1) and
releaseRef (rc -= 1). Explicit setting did make some things easier, like not
having to worry so much about double-releasing (e.g. XHR abort being called
multiple times), but it was only used in a few places AND it simply doesn't work
with objects shared between IsolatedWorlds. It is never a boolean now, as 3
different IsolatedWorlds can each hold a reference.

Temps and Globals are tracked on the Session. Previously, they were tracked on
the Identity, but that makes no sense. If a Zig instance can outlive an Identity,
then any of its Temp references can too. This hasn't been a problem because we've
only seen MutationObserver and IntersectionObserver be used cross-origin,
but the right CDP script can make this crash with a use-after-free (e.g.
`MessageEvent.data` is released when the Identity is done, but `MessageEvent` is
still referenced by a different IsolateWorld).

Rather than deinit with a `comptime shutdown: bool`, there is now an explicit
`releaseRef` and `deinit`.

Bridge registration has been streamlined. Previously, types had to register
their finalizer AND acquireRef/releaseRef/deinit had to be declared on the entire
prototype chain, even if these methods just delegated to their proto. Finalizers
are now automatically enabled if a type has a `acquireRef` function. If a type
has an `acquireRef`, then it must have a `releaseRef` and a `deinit`. So if
there's custom cleanup to do in `deinit`, then you also have to define
`acquireRef` and `releaseRef` which will just delegate to the _proto.

Furthermore these finalizer methods can be defined anywhere on the chain.

Previously:

```zig
const KeywboardEvent = struct {
  _proto: *Event,
  ...

  pub fn deinit(self: *KeyboardEvent, session: *Session) void {
    self._proto.deinit(session);
  }

  pub fn releaseRef(self: *KeyboardEvent, session: *Session) void {
    self._proto.releaseRef(session);
  }
}
```

```zig
const KeyboardEvent = struct {
  _proto: *Event,
  ...
  // no deinit, releaseRef, acquireref
}
```

Since the `KeyboardEvent` doesn't participate in finalization directly, it
doesn't have to define anything. The bridge will detect the most specific place
they are defined and call them there.
2026-03-28 21:11:23 +08:00
Karl Seguin
67bd555e75 Merge pull request #2013 from lightpanda-io/cleanup_dead_code_removal
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / wba-demo-scripts (push) Has been cancelled
e2e-test / wba-test (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig fmt (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Remove unused imports
2026-03-27 13:52:49 +08:00
Adrià Arrufat
a10e533701 Remove more unused imports 2026-03-27 14:24:17 +09:00
Karl Seguin
226d9bfc6f zig fmt 2026-03-27 12:47:24 +08:00
Karl Seguin
ea422075c7 Remove unused imports
And some smaller cleanups.
2026-03-27 12:45:26 +08:00
89 changed files with 548 additions and 602 deletions

View File

@@ -85,15 +85,6 @@ pub fn build(b: *Build) !void {
break :blk mod; break :blk mod;
}; };
// Check compilation
const check = b.step("check", "Check if lightpanda compiles");
const check_lib = b.addLibrary(.{
.name = "lightpanda_check",
.root_module = lightpanda_module,
});
check.dependOn(&check_lib.step);
{ {
// browser // browser
const exe = b.addExecutable(.{ const exe = b.addExecutable(.{
@@ -112,12 +103,6 @@ pub fn build(b: *Build) !void {
}); });
b.installArtifact(exe); b.installArtifact(exe);
const exe_check = b.addLibrary(.{
.name = "lightpanda_exe_check",
.root_module = exe.root_module,
});
check.dependOn(&exe_check.step);
const run_cmd = b.addRunArtifact(exe); const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| { if (b.args) |args| {
run_cmd.addArgs(args); run_cmd.addArgs(args);
@@ -147,12 +132,6 @@ pub fn build(b: *Build) !void {
}); });
b.installArtifact(exe); b.installArtifact(exe);
const exe_check = b.addLibrary(.{
.name = "snapshot_creator_check",
.root_module = exe.root_module,
});
check.dependOn(&exe_check.step);
const run_cmd = b.addRunArtifact(exe); const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| { if (b.args) |args| {
run_cmd.addArgs(args); run_cmd.addArgs(args);
@@ -191,12 +170,6 @@ pub fn build(b: *Build) !void {
}); });
b.installArtifact(exe); b.installArtifact(exe);
const exe_check = b.addLibrary(.{
.name = "legacy_test_check",
.root_module = exe.root_module,
});
check.dependOn(&exe_check.step);
const run_cmd = b.addRunArtifact(exe); const run_cmd = b.addRunArtifact(exe);
if (b.args) |args| { if (b.args) |args| {
run_cmd.addArgs(args); run_cmd.addArgs(args);

View File

@@ -18,6 +18,7 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const log = @import("log.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator; const ArenaAllocator = std.heap.ArenaAllocator;
@@ -62,7 +63,7 @@ pub fn deinit(self: *ArenaPool) void {
var it = self._leak_track.iterator(); var it = self._leak_track.iterator();
while (it.next()) |kv| { while (it.next()) |kv| {
if (kv.value_ptr.* != 0) { if (kv.value_ptr.* != 0) {
std.debug.print("ArenaPool leak detected: '{s}' count={d}\n", .{ kv.key_ptr.*, kv.value_ptr.* }); log.err(.bug, "ArenaPool leak", .{ .name = kv.key_ptr.*, .count = kv.value_ptr.* });
has_leaks = true; has_leaks = true;
} }
} }
@@ -129,11 +130,11 @@ pub fn release(self: *ArenaPool, allocator: Allocator) void {
if (self._leak_track.getPtr(entry.debug)) |count| { if (self._leak_track.getPtr(entry.debug)) |count| {
count.* -= 1; count.* -= 1;
if (count.* < 0) { if (count.* < 0) {
std.debug.print("ArenaPool double-free detected: '{s}'\n", .{entry.debug}); log.err(.bug, "ArenaPool double-free", .{ .name = entry.debug });
@panic("ArenaPool: double-free detected"); @panic("ArenaPool: double-free detected");
} }
} else { } else {
std.debug.print("ArenaPool release of untracked arena: '{s}'\n", .{entry.debug}); log.err(.bug, "ArenaPool release unknown", .{ .name = entry.debug });
@panic("ArenaPool: release of untracked arena"); @panic("ArenaPool: release of untracked arena");
} }
} }

View File

@@ -22,7 +22,6 @@ const net = std.net;
const posix = std.posix; const posix = std.posix;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const log = @import("log.zig"); const log = @import("log.zig");
const App = @import("App.zig"); const App = @import("App.zig");

View File

@@ -19,17 +19,13 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const js = @import("js/js.zig"); const js = @import("js/js.zig");
const log = @import("../log.zig");
const App = @import("../App.zig"); const App = @import("../App.zig");
const HttpClient = @import("HttpClient.zig"); const HttpClient = @import("HttpClient.zig");
const ArenaPool = App.ArenaPool; const ArenaPool = App.ArenaPool;
const IS_DEBUG = @import("builtin").mode == .Debug;
const Session = @import("Session.zig"); const Session = @import("Session.zig");
const Notification = @import("../Notification.zig"); const Notification = @import("../Notification.zig");

View File

@@ -205,7 +205,7 @@ pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) Dispat
pub fn dispatchOpts(self: *EventManager, target: *EventTarget, event: *Event, comptime opts: DispatchOpts) DispatchError!void { pub fn dispatchOpts(self: *EventManager, target: *EventTarget, event: *Event, comptime opts: DispatchOpts) DispatchError!void {
event.acquireRef(); event.acquireRef();
defer event.deinit(false, self.page._session); defer _ = event.releaseRef(self.page._session);
if (comptime IS_DEBUG) { if (comptime IS_DEBUG) {
log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles }); log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles });
@@ -240,7 +240,7 @@ pub fn dispatchDirect(self: *EventManager, target: *EventTarget, event: *Event,
defer window._current_event = prev_event; defer window._current_event = prev_event;
event.acquireRef(); event.acquireRef();
defer event.deinit(false, page._session); defer _ = event.releaseRef(page._session);
if (comptime IS_DEBUG) { if (comptime IS_DEBUG) {
log.debug(.event, "dispatchDirect", .{ .type = event._type_string, .context = opts.context }); log.debug(.event, "dispatchDirect", .{ .type = event._type_string, .context = opts.context });
@@ -425,7 +425,7 @@ fn dispatchNode(self: *EventManager, target: *Node, event: *Event, comptime opts
ls.deinit(); ls.deinit();
} }
const activation_state = ActivationState.create(event, target, page); const activation_state = try ActivationState.create(event, target, page);
// Defer runs even on early return - ensures event phase is reset // Defer runs even on early return - ensures event phase is reset
// and default actions execute (unless prevented) // and default actions execute (unless prevented)
@@ -820,7 +820,7 @@ const ActivationState = struct {
const Input = Element.Html.Input; const Input = Element.Html.Input;
fn create(event: *const Event, target: *Node, page: *Page) ?ActivationState { fn create(event: *const Event, target: *Node, page: *Page) !?ActivationState {
if (event._type_string.eql(comptime .wrap("click")) == false) { if (event._type_string.eql(comptime .wrap("click")) == false) {
return null; return null;
} }

View File

@@ -239,7 +239,7 @@ fn eventInit(arena: Allocator, typ: String, value: anytype) !Event {
const time_stamp = (raw_timestamp / 2) * 2; const time_stamp = (raw_timestamp / 2) * 2;
return .{ return .{
._rc = 0, ._rc = .{},
._arena = arena, ._arena = arena,
._type = unionInit(Event.Type, value), ._type = unionInit(Event.Type, value),
._type_string = typ, ._type_string = typ,
@@ -255,6 +255,7 @@ pub fn blob(_: *const Factory, arena: Allocator, child: anytype) !*@TypeOf(child
const blob_ptr = chain.get(0); const blob_ptr = chain.get(0);
blob_ptr.* = .{ blob_ptr.* = .{
._rc = .{},
._arena = arena, ._arena = arena,
._type = unionInit(Blob.Type, chain.get(1)), ._type = unionInit(Blob.Type, chain.get(1)),
._slice = "", ._slice = "",
@@ -271,7 +272,7 @@ pub fn abstractRange(_: *const Factory, arena: Allocator, child: anytype, page:
const doc = page.document.asNode(); const doc = page.document.asNode();
const abstract_range = chain.get(0); const abstract_range = chain.get(0);
abstract_range.* = AbstractRange{ abstract_range.* = AbstractRange{
._rc = 0, ._rc = .{},
._arena = arena, ._arena = arena,
._page_id = page.id, ._page_id = page.id,
._type = unionInit(AbstractRange.Type, chain.get(1)), ._type = unionInit(AbstractRange.Type, chain.get(1)),

View File

@@ -24,13 +24,10 @@ const lp = @import("lightpanda");
const log = @import("../log.zig"); const log = @import("../log.zig");
const Net = @import("../network/http.zig"); const Net = @import("../network/http.zig");
const Network = @import("../network/Runtime.zig"); const Network = @import("../network/Runtime.zig");
const Config = @import("../Config.zig");
const URL = @import("../browser/URL.zig"); const URL = @import("../browser/URL.zig");
const Notification = @import("../Notification.zig"); const Notification = @import("../Notification.zig");
const CookieJar = @import("../browser/webapi/storage/Cookie.zig").Jar; const CookieJar = @import("../browser/webapi/storage/Cookie.zig").Jar;
const Robots = @import("../network/Robots.zig"); const Robots = @import("../network/Robots.zig");
const RobotStore = Robots.RobotStore;
const WebBotAuth = @import("../network/WebBotAuth.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator; const ArenaAllocator = std.heap.ArenaAllocator;

View File

@@ -27,7 +27,6 @@ const IS_DEBUG = builtin.mode == .Debug;
const log = @import("../log.zig"); const log = @import("../log.zig");
const App = @import("../App.zig");
const String = @import("../string.zig").String; const String = @import("../string.zig").String;
const Mime = @import("Mime.zig"); const Mime = @import("Mime.zig");
@@ -43,7 +42,6 @@ const URL = @import("URL.zig");
const Blob = @import("webapi/Blob.zig"); const Blob = @import("webapi/Blob.zig");
const Node = @import("webapi/Node.zig"); const Node = @import("webapi/Node.zig");
const Event = @import("webapi/Event.zig"); const Event = @import("webapi/Event.zig");
const EventTarget = @import("webapi/EventTarget.zig");
const CData = @import("webapi/CData.zig"); const CData = @import("webapi/CData.zig");
const Element = @import("webapi/Element.zig"); const Element = @import("webapi/Element.zig");
const HtmlElement = @import("webapi/element/Html.zig"); const HtmlElement = @import("webapi/element/Html.zig");
@@ -59,7 +57,6 @@ const AbstractRange = @import("webapi/AbstractRange.zig");
const MutationObserver = @import("webapi/MutationObserver.zig"); const MutationObserver = @import("webapi/MutationObserver.zig");
const IntersectionObserver = @import("webapi/IntersectionObserver.zig"); const IntersectionObserver = @import("webapi/IntersectionObserver.zig");
const CustomElementDefinition = @import("webapi/CustomElementDefinition.zig"); const CustomElementDefinition = @import("webapi/CustomElementDefinition.zig");
const storage = @import("webapi/storage/storage.zig");
const PageTransitionEvent = @import("webapi/event/PageTransitionEvent.zig"); const PageTransitionEvent = @import("webapi/event/PageTransitionEvent.zig");
const SubmitEvent = @import("webapi/event/SubmitEvent.zig"); const SubmitEvent = @import("webapi/event/SubmitEvent.zig");
const NavigationKind = @import("webapi/navigation/root.zig").NavigationKind; const NavigationKind = @import("webapi/navigation/root.zig").NavigationKind;
@@ -67,7 +64,6 @@ const KeyboardEvent = @import("webapi/event/KeyboardEvent.zig");
const MouseEvent = @import("webapi/event/MouseEvent.zig"); const MouseEvent = @import("webapi/event/MouseEvent.zig");
const HttpClient = @import("HttpClient.zig"); const HttpClient = @import("HttpClient.zig");
const ArenaPool = App.ArenaPool;
const timestamp = @import("../datetime.zig").timestamp; const timestamp = @import("../datetime.zig").timestamp;
const milliTimestamp = @import("../datetime.zig").milliTimestamp; const milliTimestamp = @import("../datetime.zig").milliTimestamp;
@@ -3399,7 +3395,7 @@ pub fn handleClick(self: *Page, target: *Node) !void {
pub fn triggerKeyboard(self: *Page, keyboard_event: *KeyboardEvent) !void { pub fn triggerKeyboard(self: *Page, keyboard_event: *KeyboardEvent) !void {
const event = keyboard_event.asEvent(); const event = keyboard_event.asEvent();
const element = self.window._document._active_element orelse { const element = self.window._document._active_element orelse {
keyboard_event.deinit(false, self._session); _ = event.releaseRef(self._session);
return; return;
}; };
@@ -3495,7 +3491,7 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form
// so submit_event is still valid when we check _prevent_default // so submit_event is still valid when we check _prevent_default
submit_event.acquireRef(); submit_event.acquireRef();
defer submit_event.deinit(false, self._session); defer _ = submit_event.releaseRef(self._session);
try self._event_manager.dispatch(form_element.asEventTarget(), submit_event); try self._event_manager.dispatch(form_element.asEventTarget(), submit_event);
// If the submit event was prevented, don't submit the form // If the submit event was prevented, don't submit the form

View File

@@ -21,12 +21,9 @@ const lp = @import("lightpanda");
const builtin = @import("builtin"); const builtin = @import("builtin");
const log = @import("../log.zig"); const log = @import("../log.zig");
const App = @import("../App.zig");
const Page = @import("Page.zig"); const Page = @import("Page.zig");
const Session = @import("Session.zig"); const Session = @import("Session.zig");
const Browser = @import("Browser.zig");
const Factory = @import("Factory.zig");
const HttpClient = @import("HttpClient.zig"); const HttpClient = @import("HttpClient.zig");
const IS_DEBUG = builtin.mode == .Debug; const IS_DEBUG = builtin.mode == .Debug;

View File

@@ -28,12 +28,10 @@ const String = @import("../string.zig").String;
const js = @import("js/js.zig"); const js = @import("js/js.zig");
const URL = @import("URL.zig"); const URL = @import("URL.zig");
const Page = @import("Page.zig"); const Page = @import("Page.zig");
const Browser = @import("Browser.zig");
const Element = @import("webapi/Element.zig"); const Element = @import("webapi/Element.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const IS_DEBUG = builtin.mode == .Debug; const IS_DEBUG = builtin.mode == .Debug;

View File

@@ -71,6 +71,18 @@ origins: std.StringHashMapUnmanaged(*js.Origin) = .empty,
// ensuring object identity works across same-origin frames. // ensuring object identity works across same-origin frames.
identity: js.Identity = .{}, identity: js.Identity = .{},
// Shared finalizer callbacks across all Identities. Keyed by Zig instance ptr.
// This ensures objects are only freed when ALL v8 wrappers are gone.
finalizer_callbacks: std.AutoHashMapUnmanaged(usize, *FinalizerCallback) = .empty,
// Tracked global v8 objects that need to be released on cleanup.
// Lives at Session level so objects can outlive individual Identities.
globals: std.ArrayList(v8.Global) = .empty,
// Temporary v8 globals that can be released early. Key is global.data_ptr.
// Lives at Session level so objects holding Temps can outlive individual Identities.
temps: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty,
// Shared resources for all pages in this session. // Shared resources for all pages in this session.
// These live for the duration of the page tree (root + frames). // These live for the duration of the page tree (root + frames).
arena_pool: *ArenaPool, arena_pool: *ArenaPool,
@@ -224,6 +236,30 @@ pub fn releaseOrigin(self: *Session, origin: *js.Origin) void {
/// Reset page_arena and factory for a clean slate. /// Reset page_arena and factory for a clean slate.
/// Called when root page is removed. /// Called when root page is removed.
fn resetPageResources(self: *Session) void { fn resetPageResources(self: *Session) void {
// Force cleanup all remaining finalized objects
{
var it = self.finalizer_callbacks.valueIterator();
while (it.next()) |fc| {
fc.*.deinit(self);
}
self.finalizer_callbacks = .empty;
}
{
for (self.globals.items) |*global| {
v8.v8__Global__Reset(global);
}
self.globals = .empty;
}
{
var it = self.temps.valueIterator();
while (it.next()) |global| {
v8.v8__Global__Reset(global);
}
self.temps = .empty;
}
self.identity.deinit(); self.identity.deinit();
self.identity = .{}; self.identity = .{};
@@ -457,35 +493,25 @@ pub fn nextPageId(self: *Session) u32 {
return id; return id;
} }
// A type that has a finalizer can have its finalizer called one of two ways. // Every finalizable instance of Zig gets 1 FinalizerCallback registered in the
// The first is from V8 via the WeakCallback we give to weakRef. But that isn't // session. This is to ensure that, if v8 doesn't finalize the value, we can
// guaranteed to fire, so we track this in finalizer_callbacks and call them on // release on page reset.
// page reset.
pub const FinalizerCallback = struct { pub const FinalizerCallback = struct {
arena: Allocator, arena: Allocator,
session: *Session, session: *Session,
ptr: *anyopaque, resolved_ptr_id: usize,
global: v8.Global, finalizer_ptr_id: usize,
identity: *js.Identity, _deinit: *const fn (ptr_id: usize, session: *Session) void,
zig_finalizer: *const fn (ptr: *anyopaque, session: *Session) void,
pub fn deinit(self: *FinalizerCallback) void { // For every FinalizerCallback we'll have 1+ FinalizerCallback.Identity: one
self.zig_finalizer(self.ptr, self.session); // for every identity that gets the instance. In most cases, that'l be 1.
self.session.releaseArena(self.arena); pub const Identity = struct {
} identity: *js.Identity,
fc: *Session.FinalizerCallback,
/// Release this item from the identity tracking maps (called after finalizer runs from V8) };
pub fn releaseIdentity(self: *FinalizerCallback) void {
const session = self.session;
const id = @intFromPtr(self.ptr);
if (self.identity.identity_map.fetchRemove(id)) |kv| {
var global = kv.value;
v8.v8__Global__Reset(&global);
}
_ = self.identity.finalizer_callbacks.remove(id);
fn deinit(self: *FinalizerCallback, session: *Session) void {
self._deinit(self.finalizer_ptr_id, session);
session.releaseArena(self.arena); session.releaseArena(self.arena);
} }
}; };

View File

@@ -128,7 +128,7 @@ fn _constructor(self: *Caller, func: anytype, info: FunctionCallbackInfo) !void
const new_this_handle = info.getThis(); const new_this_handle = info.getThis();
var this = js.Object{ .local = local, .handle = new_this_handle }; var this = js.Object{ .local = local, .handle = new_this_handle };
if (@typeInfo(ReturnType) == .error_union) { if (@typeInfo(ReturnType) == .error_union) {
const non_error_res = res catch |err| return err; const non_error_res = try res;
this = try local.mapZigInstanceToJs(new_this_handle, non_error_res); this = try local.mapZigInstanceToJs(new_this_handle, non_error_res);
} else { } else {
this = try local.mapZigInstanceToJs(new_this_handle, res); this = try local.mapZigInstanceToJs(new_this_handle, res);

View File

@@ -21,8 +21,8 @@ const lp = @import("lightpanda");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const js = @import("js.zig"); const js = @import("js.zig");
const Env = @import("Env.zig");
const bridge = @import("bridge.zig"); const bridge = @import("bridge.zig");
const Env = @import("Env.zig");
const Origin = @import("Origin.zig"); const Origin = @import("Origin.zig");
const Scheduler = @import("Scheduler.zig"); const Scheduler = @import("Scheduler.zig");
@@ -214,48 +214,11 @@ pub fn setOrigin(self: *Context, key: ?[]const u8) !void {
} }
pub fn trackGlobal(self: *Context, global: v8.Global) !void { pub fn trackGlobal(self: *Context, global: v8.Global) !void {
return self.identity.globals.append(self.identity_arena, global); return self.session.globals.append(self.session.page_arena, global);
} }
pub fn trackTemp(self: *Context, global: v8.Global) !void { pub fn trackTemp(self: *Context, global: v8.Global) !void {
return self.identity.temps.put(self.identity_arena, global.data_ptr, global); return self.session.temps.put(self.session.page_arena, global.data_ptr, global);
}
pub fn weakRef(self: *Context, obj: anytype) void {
const resolved = js.Local.resolveValue(obj);
const fc = self.identity.finalizer_callbacks.get(@intFromPtr(resolved.ptr)) orelse {
if (comptime IS_DEBUG) {
// should not be possible
std.debug.assert(false);
}
return;
};
v8.v8__Global__SetWeakFinalizer(&fc.global, fc, resolved.finalizer_from_v8, v8.kParameter);
}
pub fn safeWeakRef(self: *Context, obj: anytype) void {
const resolved = js.Local.resolveValue(obj);
const fc = self.identity.finalizer_callbacks.get(@intFromPtr(resolved.ptr)) orelse {
if (comptime IS_DEBUG) {
// should not be possible
std.debug.assert(false);
}
return;
};
v8.v8__Global__ClearWeak(&fc.global);
v8.v8__Global__SetWeakFinalizer(&fc.global, fc, resolved.finalizer_from_v8, v8.kParameter);
}
pub fn strongRef(self: *Context, obj: anytype) void {
const resolved = js.Local.resolveValue(obj);
const fc = self.identity.finalizer_callbacks.get(@intFromPtr(resolved.ptr)) orelse {
if (comptime IS_DEBUG) {
// should not be possible
std.debug.assert(false);
}
return;
};
v8.v8__Global__ClearWeak(&fc.global);
} }
pub const IdentityResult = struct { pub const IdentityResult = struct {
@@ -271,35 +234,6 @@ pub fn addIdentity(self: *Context, ptr: usize) !IdentityResult {
}; };
} }
pub fn releaseTemp(self: *Context, global: v8.Global) void {
if (self.identity.temps.fetchRemove(global.data_ptr)) |kv| {
var g = kv.value;
v8.v8__Global__Reset(&g);
}
}
pub fn createFinalizerCallback(
self: *Context,
global: v8.Global,
ptr: *anyopaque,
zig_finalizer: *const fn (ptr: *anyopaque, session: *Session) void,
) !*Session.FinalizerCallback {
const session = self.session;
const arena = try session.getArena(.{ .debug = "FinalizerCallback" });
errdefer session.releaseArena(arena);
const fc = try arena.create(Session.FinalizerCallback);
fc.* = .{
.arena = arena,
.session = session,
.ptr = ptr,
.global = global,
.zig_finalizer = zig_finalizer,
// Store identity pointer for cleanup when V8 GCs the object
.identity = self.identity,
};
return fc;
}
// Any operation on the context have to be made from a local. // Any operation on the context have to be made from a local.
pub fn localScope(self: *Context, ls: *js.Local.Scope) void { pub fn localScope(self: *Context, ls: *js.Local.Scope) void {
const isolate = self.isolate; const isolate = self.isolate;

View File

@@ -26,7 +26,6 @@ const App = @import("../../App.zig");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const bridge = @import("bridge.zig"); const bridge = @import("bridge.zig");
const Origin = @import("Origin.zig");
const Context = @import("Context.zig"); const Context = @import("Context.zig");
const Isolate = @import("Isolate.zig"); const Isolate = @import("Isolate.zig");
const Platform = @import("Platform.zig"); const Platform = @import("Platform.zig");
@@ -34,7 +33,6 @@ const Snapshot = @import("Snapshot.zig");
const Inspector = @import("Inspector.zig"); const Inspector = @import("Inspector.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const Window = @import("../webapi/Window.zig"); const Window = @import("../webapi/Window.zig");
const JsApis = bridge.JsApis; const JsApis = bridge.JsApis;

View File

@@ -21,7 +21,6 @@ const js = @import("js.zig");
const v8 = js.v8; const v8 = js.v8;
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const Session = @import("../Session.zig");
const Function = @This(); const Function = @This();
@@ -214,7 +213,7 @@ fn _persist(self: *const Function, comptime is_global: bool) !(if (is_global) Gl
return .{ .handle = global, .temps = {} }; return .{ .handle = global, .temps = {} };
} }
try ctx.trackTemp(global); try ctx.trackTemp(global);
return .{ .handle = global, .temps = &ctx.identity.temps }; return .{ .handle = global, .temps = &ctx.session.temps };
} }
pub fn tempWithThis(self: *const Function, value: anytype) !Temp { pub fn tempWithThis(self: *const Function, value: anytype) !Temp {

View File

@@ -32,45 +32,15 @@ const js = @import("js.zig");
const Session = @import("../Session.zig"); const Session = @import("../Session.zig");
const v8 = js.v8; const v8 = js.v8;
const Allocator = std.mem.Allocator;
const Identity = @This(); const Identity = @This();
// Maps Zig instance pointers to their v8::Global(Object) wrappers. // Maps Zig instance pointers to their v8::Global(Object) wrappers.
identity_map: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty, identity_map: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty,
// Tracked global v8 objects that need to be released on cleanup.
globals: std.ArrayList(v8.Global) = .empty,
// Temporary v8 globals that can be released early. Key is global.data_ptr.
temps: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty,
// Finalizer callbacks for weak references. Key is @intFromPtr of the Zig instance.
finalizer_callbacks: std.AutoHashMapUnmanaged(usize, *Session.FinalizerCallback) = .empty,
pub fn deinit(self: *Identity) void { pub fn deinit(self: *Identity) void {
{ var it = self.identity_map.valueIterator();
var it = self.finalizer_callbacks.valueIterator(); while (it.next()) |global| {
while (it.next()) |finalizer| {
finalizer.*.deinit();
}
}
{
var it = self.identity_map.valueIterator();
while (it.next()) |global| {
v8.v8__Global__Reset(global);
}
}
for (self.globals.items) |*global| {
v8.v8__Global__Reset(global); v8.v8__Global__Reset(global);
} }
{
var it = self.temps.valueIterator();
while (it.next()) |global| {
v8.v8__Global__Reset(global);
}
}
} }

View File

@@ -17,11 +17,11 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const Page = @import("../Page.zig");
const Session = @import("../Session.zig");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const string = @import("../../string.zig"); const string = @import("../../string.zig");
const Session = @import("../Session.zig");
const js = @import("js.zig"); const js = @import("js.zig");
const bridge = @import("bridge.zig"); const bridge = @import("bridge.zig");
const Caller = @import("Caller.zig"); const Caller = @import("Caller.zig");
@@ -33,7 +33,6 @@ const IS_DEBUG = @import("builtin").mode == .Debug;
const v8 = js.v8; const v8 = js.v8;
const CallOpts = Caller.CallOpts; const CallOpts = Caller.CallOpts;
const Allocator = std.mem.Allocator;
// Where js.Context has a lifetime tied to the page, and holds the // Where js.Context has a lifetime tied to the page, and holds the
// v8::Global<v8::Context>, this has a much shorter lifetime and holds a // v8::Global<v8::Context>, this has a much shorter lifetime and holds a
@@ -215,7 +214,8 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object,
.pointer => |ptr| { .pointer => |ptr| {
const resolved = resolveValue(value); const resolved = resolveValue(value);
const gop = try ctx.addIdentity(@intFromPtr(resolved.ptr)); const resolved_ptr_id = @intFromPtr(resolved.ptr);
const gop = try ctx.addIdentity(resolved_ptr_id);
if (gop.found_existing) { if (gop.found_existing) {
// we've seen this instance before, return the same object // we've seen this instance before, return the same object
return (js.Object.Global{ .handle = gop.value_ptr.* }).local(self); return (js.Object.Global{ .handle = gop.value_ptr.* }).local(self);
@@ -264,31 +264,27 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object,
// dont' use js_obj.persist(), because we don't want to track this in // dont' use js_obj.persist(), because we don't want to track this in
// context.global_objects, we want to track it in context.identity_map. // context.global_objects, we want to track it in context.identity_map.
v8.v8__Global__New(isolate.handle, js_obj.handle, gop.value_ptr); v8.v8__Global__New(isolate.handle, js_obj.handle, gop.value_ptr);
if (@hasDecl(JsApi.Meta, "finalizer")) { if (resolved.finalizer) |finalizer| {
// It would be great if resolved knew the resolved type, but I const finalizer_ptr_id = finalizer.ptr_id;
// can't figure out how to make that work, since it depends on finalizer.acquireRef(finalizer_ptr_id);
// the [runtime] `value`.
// We need the resolved finalizer, which we have in resolved.
//
// The above if statement would be more clear as:
// if (resolved.finalizer_from_v8) |finalizer| {
// But that's a runtime check.
// Instead, we check if the base has finalizer. The assumption
// here is that if a resolve type has a finalizer, then the base
// should have a finalizer too.
const fc = try ctx.createFinalizerCallback(gop.value_ptr.*, resolved.ptr, resolved.finalizer_from_zig.?);
{
errdefer fc.deinit();
try ctx.identity.finalizer_callbacks.put(ctx.identity_arena, @intFromPtr(resolved.ptr), fc);
}
conditionallyReference(value); const session = ctx.session;
if (@hasDecl(JsApi.Meta, "weak")) { const finalizer_gop = try session.finalizer_callbacks.getOrPut(session.page_arena, finalizer_ptr_id);
if (comptime IS_DEBUG) { if (finalizer_gop.found_existing == false) {
std.debug.assert(JsApi.Meta.weak == true); // This is the first context (and very likely only one) to
} // see this Zig instance. We need to create the FinalizerCallback
v8.v8__Global__SetWeakFinalizer(gop.value_ptr, fc, resolved.finalizer_from_v8, v8.kParameter); // so that we can cleanup on page reset if v8 doesn't finalize.
errdefer _ = session.finalizer_callbacks.remove(finalizer_ptr_id);
finalizer_gop.value_ptr.* = try self.createFinalizerCallback(resolved_ptr_id, finalizer_ptr_id, finalizer.deinit);
} }
const fc = finalizer_gop.value_ptr.*;
const identity_finalizer = try fc.arena.create(Session.FinalizerCallback.Identity);
identity_finalizer.* = .{
.fc = fc,
.identity = ctx.identity,
};
v8.v8__Global__SetWeakFinalizer(gop.value_ptr, identity_finalizer, finalizer.release, v8.kParameter);
} }
return js_obj; return js_obj;
}, },
@@ -1123,12 +1119,19 @@ fn jsUnsignedIntToZig(comptime T: type, max: comptime_int, maybe: u32) !T {
// This function recursively walks the _type union field (if there is one) to // This function recursively walks the _type union field (if there is one) to
// get the most specific class_id possible. // get the most specific class_id possible.
const Resolved = struct { const Resolved = struct {
weak: bool,
ptr: *anyopaque, ptr: *anyopaque,
class_id: u16, class_id: u16,
prototype_chain: []const @import("TaggedOpaque.zig").PrototypeChainEntry, prototype_chain: []const @import("TaggedOpaque.zig").PrototypeChainEntry,
finalizer_from_v8: ?*const fn (handle: ?*const v8.WeakCallbackInfo) callconv(.c) void = null, finalizer: ?Finalizer,
finalizer_from_zig: ?*const fn (ptr: *anyopaque, session: *Session) void = null,
const Finalizer = struct {
// Resolved.ptr is the most specific value in a chain (e.g. IFrame, not EventTarget, Node, ...)
// Finalizer.ptr_id is the most specific value in a chain that defines an acquireRef
ptr_id: usize,
deinit: *const fn (ptr_id: usize, session: *Session) void,
acquireRef: *const fn (ptr_id: usize) void,
release: *const fn (handle: ?*const v8.WeakCallbackInfo) callconv(.c) void,
};
}; };
pub fn resolveValue(value: anytype) Resolved { pub fn resolveValue(value: anytype) Resolved {
const T = bridge.Struct(@TypeOf(value)); const T = bridge.Struct(@TypeOf(value));
@@ -1155,27 +1158,85 @@ pub fn resolveValue(value: anytype) Resolved {
unreachable; unreachable;
} }
fn resolveT(comptime T: type, value: *anyopaque) Resolved { fn resolveT(comptime T: type, value: *T) Resolved {
const Meta = T.JsApi.Meta; const Meta = T.JsApi.Meta;
return .{ return .{
.ptr = value, .ptr = value,
.class_id = Meta.class_id, .class_id = Meta.class_id,
.prototype_chain = &Meta.prototype_chain, .prototype_chain = &Meta.prototype_chain,
.weak = if (@hasDecl(Meta, "weak")) Meta.weak else false, .finalizer = blk: {
.finalizer_from_v8 = if (@hasDecl(Meta, "finalizer")) Meta.finalizer.from_v8 else null, const FT = (comptime findFinalizerType(T)) orelse break :blk null;
.finalizer_from_zig = if (@hasDecl(Meta, "finalizer")) Meta.finalizer.from_zig else null, const getFinalizerPtr = comptime finalizerPtrGetter(T, FT);
const finalizer_ptr = getFinalizerPtr(value);
const Wrap = struct {
fn deinit(ptr_id: usize, session: *Session) void {
FT.deinit(@ptrFromInt(ptr_id), session);
}
fn acquireRef(ptr_id: usize) void {
FT.acquireRef(@ptrFromInt(ptr_id));
}
fn release(handle: ?*const v8.WeakCallbackInfo) callconv(.c) void {
const ptr = v8.v8__WeakCallbackInfo__GetParameter(handle.?).?;
const identity_finalizer: *Session.FinalizerCallback.Identity = @ptrCast(@alignCast(ptr));
const fc = identity_finalizer.fc;
if (identity_finalizer.identity.identity_map.fetchRemove(fc.resolved_ptr_id)) |kv| {
var global = kv.value;
v8.v8__Global__Reset(&global);
}
FT.releaseRef(@ptrFromInt(fc.finalizer_ptr_id), fc.session);
}
};
break :blk .{
.ptr_id = @intFromPtr(finalizer_ptr),
.deinit = Wrap.deinit,
.acquireRef = Wrap.acquireRef,
.release = Wrap.release,
};
},
}; };
} }
fn conditionallyReference(value: anytype) void { // Start at the "resolved" type (the most specific) and work our way up the
const T = bridge.Struct(@TypeOf(value)); // prototype chain looking for the type that defines acquireRef
if (@hasDecl(T, "acquireRef")) { fn findFinalizerType(comptime T: type) ?type {
value.acquireRef(); const S = bridge.Struct(T);
return; if (@hasDecl(S, "acquireRef")) {
return S;
} }
if (@hasField(T, "_proto")) { if (@hasField(S, "_proto")) {
conditionallyReference(value._proto); const ProtoPtr = std.meta.fieldInfo(S, ._proto).type;
const ProtoChild = @typeInfo(ProtoPtr).pointer.child;
return findFinalizerType(ProtoChild);
} }
return null;
}
// Generate a function that follows the _proto pointer chain to get to the finalizer type
fn finalizerPtrGetter(comptime T: type, comptime FT: type) *const fn (*T) *FT {
const S = bridge.Struct(T);
if (S == FT) {
return struct {
fn get(v: *T) *FT {
return v;
}
}.get;
}
if (@hasField(S, "_proto")) {
const ProtoPtr = std.meta.fieldInfo(S, ._proto).type;
const ProtoChild = @typeInfo(ProtoPtr).pointer.child;
const childGetter = comptime finalizerPtrGetter(ProtoChild, FT);
return struct {
fn get(v: *T) *FT {
return childGetter(v._proto);
}
}.get;
}
@compileError("Cannot find path from " ++ @typeName(T) ++ " to " ++ @typeName(FT));
} }
pub fn stackTrace(self: *const Local) !?[]const u8 { pub fn stackTrace(self: *const Local) !?[]const u8 {
@@ -1383,6 +1444,34 @@ pub fn debugContextId(self: *const Local) i32 {
return v8.v8__Context__DebugContextId(self.handle); return v8.v8__Context__DebugContextId(self.handle);
} }
fn createFinalizerCallback(
self: *const Local,
// Key in identity map
// The most specific value (KeyboardEvent, not Event)
resolved_ptr_id: usize,
// The most specific value where finalizers are defined
// What actually gets acquired / released / deinit
finalizer_ptr_id: usize,
deinit: *const fn (ptr_id: usize, session: *Session) void,
) !*Session.FinalizerCallback {
const session = self.ctx.session;
const arena = try session.getArena(.{ .debug = "FinalizerCallback" });
errdefer session.releaseArena(arena);
const fc = try arena.create(Session.FinalizerCallback);
fc.* = .{
.arena = arena,
.session = session,
._deinit = deinit,
.resolved_ptr_id = resolved_ptr_id,
.finalizer_ptr_id = finalizer_ptr_id,
};
return fc;
}
// Encapsulates a Local and a HandleScope. When we're going from V8->Zig // Encapsulates a Local and a HandleScope. When we're going from V8->Zig
// we easily get both a Local and a HandleScope via Caller.init. // we easily get both a Local and a HandleScope via Caller.init.
// But when we're going from Zig -> V8, things are more complicated. // But when we're going from Zig -> V8, things are more complicated.

View File

@@ -20,8 +20,6 @@ const std = @import("std");
const js = @import("js.zig"); const js = @import("js.zig");
const v8 = js.v8; const v8 = js.v8;
const Session = @import("../Session.zig");
const Promise = @This(); const Promise = @This();
local: *const js.Local, local: *const js.Local,
@@ -69,7 +67,7 @@ fn _persist(self: *const Promise, comptime is_global: bool) !(if (is_global) Glo
return .{ .handle = global, .temps = {} }; return .{ .handle = global, .temps = {} };
} }
try ctx.trackTemp(global); try ctx.trackTemp(global);
return .{ .handle = global, .temps = &ctx.identity.temps }; return .{ .handle = global, .temps = &ctx.session.temps };
} }
pub const Temp = G(.temp); pub const Temp = G(.temp);

View File

@@ -25,7 +25,6 @@ const IS_DEBUG = @import("builtin").mode == .Debug;
const v8 = js.v8; const v8 = js.v8;
const JsApis = bridge.JsApis; const JsApis = bridge.JsApis;
const Allocator = std.mem.Allocator;
const Snapshot = @This(); const Snapshot = @This();

View File

@@ -25,7 +25,6 @@ const v8 = js.v8;
const IS_DEBUG = @import("builtin").mode == .Debug; const IS_DEBUG = @import("builtin").mode == .Debug;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Session = @import("../Session.zig");
const Value = @This(); const Value = @This();
@@ -304,7 +303,7 @@ fn _persist(self: *const Value, comptime is_global: bool) !(if (is_global) Globa
return .{ .handle = global, .temps = {} }; return .{ .handle = global, .temps = {} };
} }
try ctx.trackTemp(global); try ctx.trackTemp(global);
return .{ .handle = global, .temps = &ctx.identity.temps }; return .{ .handle = global, .temps = &ctx.session.temps };
} }
pub fn toZig(self: Value, comptime T: type) !T { pub fn toZig(self: Value, comptime T: type) !T {

View File

@@ -18,15 +18,12 @@
const std = @import("std"); const std = @import("std");
const js = @import("js.zig"); const js = @import("js.zig");
const lp = @import("lightpanda");
const log = @import("../../log.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const Session = @import("../Session.zig"); const Session = @import("../Session.zig");
const v8 = js.v8; const v8 = js.v8;
const Caller = @import("Caller.zig"); const Caller = @import("Caller.zig");
const Context = @import("Context.zig");
const IS_DEBUG = @import("builtin").mode == .Debug; const IS_DEBUG = @import("builtin").mode == .Debug;
@@ -104,36 +101,21 @@ pub fn Builder(comptime T: type) type {
} }
return entries; return entries;
} }
pub fn finalizer(comptime func: *const fn (self: *T, shutdown: bool, session: *Session) void) Finalizer {
return .{
.from_zig = struct {
fn wrap(ptr: *anyopaque, session: *Session) void {
func(@ptrCast(@alignCast(ptr)), true, session);
}
}.wrap,
.from_v8 = struct {
fn wrap(handle: ?*const v8.WeakCallbackInfo) callconv(.c) void {
const ptr = v8.v8__WeakCallbackInfo__GetParameter(handle.?).?;
const fc: *Session.FinalizerCallback = @ptrCast(@alignCast(ptr));
const value_ptr = fc.ptr;
if (fc.identity.finalizer_callbacks.contains(@intFromPtr(value_ptr))) {
func(@ptrCast(@alignCast(value_ptr)), false, fc.session);
fc.releaseIdentity();
} else {
// A bit weird, but v8 _requires_ that we release it
// If we don't. We'll 100% crash.
v8.v8__Global__Reset(&fc.global);
}
}
}.wrap,
};
}
}; };
} }
fn releaseRef(comptime T: type, ptr_id: usize, session: *Session) void {
if (@hasDecl(T, "releaseRef")) {
T.releaseRef(@ptrFromInt(ptr_id), session);
return;
}
if (@hasField(T, "_proto")) {
releaseRef(Struct(std.meta.fieldInfo(T, ._proto).type), ptr_id, session);
return;
}
@compileError(@typeName(T) ++ " marked with finalizer without an acquireRef in its prototype chain");
}
pub const Constructor = struct { pub const Constructor = struct {
func: *const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void, func: *const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void,
@@ -414,17 +396,6 @@ pub const Property = struct {
} }
}; };
const Finalizer = struct {
// The finalizer wrapper when called from Zig. This is only called on
// Origin.deinit
from_zig: *const fn (ctx: *anyopaque, session: *Session) void,
// The finalizer wrapper when called from V8. This may never be called
// (hence why we fallback to calling in Origin.deinit). If it is called,
// it is only ever called after we SetWeak on the Global.
from_v8: *const fn (?*const v8.WeakCallbackInfo) callconv(.c) void,
};
pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 { pub fn unknownWindowPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.PropertyCallbackInfo) callconv(.c) u8 {
const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?; const v8_isolate = v8.v8__PropertyCallbackInfo__GetIsolate(handle).?;
var caller: Caller = undefined; var caller: Caller = undefined;

View File

@@ -21,7 +21,6 @@ const std = @import("std");
const Page = @import("Page.zig"); const Page = @import("Page.zig");
const URL = @import("URL.zig"); const URL = @import("URL.zig");
const TreeWalker = @import("webapi/TreeWalker.zig"); const TreeWalker = @import("webapi/TreeWalker.zig");
const CData = @import("webapi/CData.zig");
const Element = @import("webapi/Element.zig"); const Element = @import("webapi/Element.zig");
const Node = @import("webapi/Node.zig"); const Node = @import("webapi/Node.zig");
const isAllWhitespace = @import("../string.zig").isAllWhitespace; const isAllWhitespace = @import("../string.zig").isAllWhitespace;

View File

@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Session = @import("../Session.zig"); const Session = @import("../Session.zig");
@@ -31,7 +33,7 @@ const AbstractRange = @This();
pub const _prototype_root = true; pub const _prototype_root = true;
_rc: u8, _rc: lp.RC(u8) = .{},
_type: Type, _type: Type,
_page_id: u32, _page_id: u32,
_arena: Allocator, _arena: Allocator,
@@ -44,24 +46,18 @@ _start_container: *Node,
_range_link: std.DoublyLinkedList.Node = .{}, _range_link: std.DoublyLinkedList.Node = .{},
pub fn acquireRef(self: *AbstractRange) void { pub fn acquireRef(self: *AbstractRange) void {
self._rc += 1; self._rc.acquire();
} }
pub fn deinit(self: *AbstractRange, shutdown: bool, session: *Session) void { pub fn deinit(self: *AbstractRange, session: *Session) void {
_ = shutdown; if (session.findPageById(self._page_id)) |page| {
const rc = self._rc; page._live_ranges.remove(&self._range_link);
if (comptime IS_DEBUG) {
std.debug.assert(rc != 0);
} }
session.releaseArena(self._arena);
}
if (rc == 1) { pub fn releaseRef(self: *AbstractRange, session: *Session) void {
if (session.findPageById(self._page_id)) |page| { self._rc.release(self, session);
page._live_ranges.remove(&self._range_link);
}
session.releaseArena(self._arena);
return;
}
self._rc = rc - 1;
} }
pub const Type = union(enum) { pub const Type = union(enum) {
@@ -338,8 +334,6 @@ pub const JsApi = struct {
pub const name = "AbstractRange"; pub const name = "AbstractRange";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(AbstractRange.deinit);
}; };
pub const startContainer = bridge.accessor(AbstractRange.getStartContainer, null, .{}); pub const startContainer = bridge.accessor(AbstractRange.getStartContainer, null, .{});

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const Writer = std.Io.Writer; const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
@@ -25,6 +25,7 @@ const Session = @import("../Session.zig");
const Mime = @import("../Mime.zig"); const Mime = @import("../Mime.zig");
const Writer = std.Io.Writer;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
/// https://w3c.github.io/FileAPI/#blob-section /// https://w3c.github.io/FileAPI/#blob-section
@@ -34,6 +35,7 @@ const Blob = @This();
pub const _prototype_root = true; pub const _prototype_root = true;
_type: Type, _type: Type,
_rc: lp.RC(u32),
_arena: Allocator, _arena: Allocator,
@@ -120,6 +122,7 @@ pub fn initWithMimeValidation(
const self = try arena.create(Blob); const self = try arena.create(Blob);
self.* = .{ self.* = .{
._rc = .{},
._arena = arena, ._arena = arena,
._type = .generic, ._type = .generic,
._slice = data, ._slice = data,
@@ -128,11 +131,18 @@ pub fn initWithMimeValidation(
return self; return self;
} }
pub fn deinit(self: *Blob, shutdown: bool, session: *Session) void { pub fn deinit(self: *Blob, session: *Session) void {
_ = shutdown;
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *Blob, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *Blob) void {
self._rc.acquire();
}
const largest_vector = @max(std.simd.suggestVectorLength(u8) orelse 1, 8); const largest_vector = @max(std.simd.suggestVectorLength(u8) orelse 1, 8);
/// Array of possible vector sizes for the current arch in decrementing order. /// Array of possible vector sizes for the current arch in decrementing order.
/// We may move this to some file for SIMD helpers in the future. /// We may move this to some file for SIMD helpers in the future.
@@ -325,8 +335,6 @@ pub const JsApi = struct {
pub const name = "Blob"; pub const name = "Blob";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Blob.deinit);
}; };
pub const constructor = bridge.constructor(Blob.init, .{}); pub const constructor = bridge.constructor(Blob.init, .{});

View File

@@ -24,7 +24,6 @@ const Page = @import("../Page.zig");
const Node = @import("Node.zig"); const Node = @import("Node.zig");
const Element = @import("Element.zig"); const Element = @import("Element.zig");
const DOMException = @import("DOMException.zig");
const Custom = @import("element/html/Custom.zig"); const Custom = @import("element/html/Custom.zig");
const CustomElementDefinition = @import("CustomElementDefinition.zig"); const CustomElementDefinition = @import("CustomElementDefinition.zig");

View File

@@ -61,7 +61,7 @@ _fonts: ?*FontFaceSet = null,
_write_insertion_point: ?*Node = null, _write_insertion_point: ?*Node = null,
_script_created_parser: ?Parser.Streaming = null, _script_created_parser: ?Parser.Streaming = null,
_adopted_style_sheets: ?js.Object.Global = null, _adopted_style_sheets: ?js.Object.Global = null,
_selection: Selection = .init, _selection: Selection = .{ ._rc = .init(1) },
// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#throw-on-dynamic-markup-insertion-counter // https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#throw-on-dynamic-markup-insertion-counter
// Incremented during custom element reactions when parsing. When > 0, // Incremented during custom element reactions when parsing. When > 0,

View File

@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
@@ -55,7 +57,7 @@ _is_trusted: bool = false,
// - 0: no reference, always a transient state going to either 1 or about to be deinit'd // - 0: no reference, always a transient state going to either 1 or about to be deinit'd
// - 1: either zig or v8 have a reference // - 1: either zig or v8 have a reference
// - 2: both zig and v8 have a reference // - 2: both zig and v8 have a reference
_rc: u8 = 0, _rc: lp.RC(u8) = .{},
pub const EventPhase = enum(u8) { pub const EventPhase = enum(u8) {
none = 0, none = 0,
@@ -139,25 +141,16 @@ pub fn initEvent(
} }
pub fn acquireRef(self: *Event) void { pub fn acquireRef(self: *Event) void {
self._rc += 1; self._rc.acquire();
} }
pub fn deinit(self: *Event, shutdown: bool, session: *Session) void { /// Force cleanup on Session shutdown.
if (shutdown) { pub fn deinit(self: *Event, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
return; }
}
const rc = self._rc; pub fn releaseRef(self: *Event, session: *Session) void {
if (comptime IS_DEBUG) { self._rc.release(self, session);
std.debug.assert(rc != 0);
}
if (rc == 1) {
session.releaseArena(self._arena);
} else {
self._rc = rc - 1;
}
} }
pub fn as(self: *Event, comptime T: type) *T { pub fn as(self: *Event, comptime T: type) *T {
@@ -440,8 +433,6 @@ pub const JsApi = struct {
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Event.deinit);
pub const enumerable = false; pub const enumerable = false;
}; };

View File

@@ -60,7 +60,7 @@ pub fn dispatchEvent(self: *EventTarget, event: *Event, page: *Page) !bool {
event._is_trusted = false; event._is_trusted = false;
event.acquireRef(); event.acquireRef();
defer event.deinit(false, page._session); defer _ = event.releaseRef(page._session);
try page._event_manager.dispatch(self, event); try page._event_manager.dispatch(self, event);
return !event._cancelable or !event._prevent_default; return !event._cancelable or !event._prevent_default;
} }

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
@@ -26,7 +27,6 @@ const Blob = @import("Blob.zig");
const File = @This(); const File = @This();
/// `File` inherits `Blob`.
_proto: *Blob, _proto: *Blob,
// TODO: Implement File API. // TODO: Implement File API.
@@ -36,10 +36,6 @@ pub fn init(page: *Page) !*File {
return page._factory.blob(arena, File{ ._proto = undefined }); return page._factory.blob(arena, File{ ._proto = undefined });
} }
pub fn deinit(self: *File, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub const JsApi = struct { pub const JsApi = struct {
pub const bridge = js.Bridge(File); pub const bridge = js.Bridge(File);
@@ -47,8 +43,6 @@ pub const JsApi = struct {
pub const name = "File"; pub const name = "File";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(File.deinit);
}; };
pub const constructor = bridge.constructor(File.init, .{}); pub const constructor = bridge.constructor(File.init, .{});

View File

@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
@@ -31,6 +33,7 @@ const Allocator = std.mem.Allocator;
/// https://developer.mozilla.org/en-US/docs/Web/API/FileReader /// https://developer.mozilla.org/en-US/docs/Web/API/FileReader
const FileReader = @This(); const FileReader = @This();
_rc: lp.RC(u8) = .{},
_page: *Page, _page: *Page,
_proto: *EventTarget, _proto: *EventTarget,
_arena: Allocator, _arena: Allocator,
@@ -70,7 +73,7 @@ pub fn init(page: *Page) !*FileReader {
}); });
} }
pub fn deinit(self: *FileReader, _: bool, session: *Session) void { pub fn deinit(self: *FileReader, session: *Session) void {
if (self._on_abort) |func| func.release(); if (self._on_abort) |func| func.release();
if (self._on_error) |func| func.release(); if (self._on_error) |func| func.release();
if (self._on_load) |func| func.release(); if (self._on_load) |func| func.release();
@@ -81,6 +84,14 @@ pub fn deinit(self: *FileReader, _: bool, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *FileReader, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *FileReader) void {
self._rc.acquire();
}
fn asEventTarget(self: *FileReader) *EventTarget { fn asEventTarget(self: *FileReader) *EventTarget {
return self._proto; return self._proto;
} }
@@ -309,8 +320,6 @@ pub const JsApi = struct {
pub const name = "FileReader"; pub const name = "FileReader";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(FileReader.deinit);
}; };
pub const constructor = bridge.constructor(FileReader.init, .{}); pub const constructor = bridge.constructor(FileReader.init, .{});

View File

@@ -18,7 +18,6 @@
const std = @import("std"); const std = @import("std");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const String = @import("../../string.zig").String;
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const Node = @import("Node.zig"); const Node = @import("Node.zig");

View File

@@ -19,10 +19,8 @@
const std = @import("std"); const std = @import("std");
const String = @import("../../string.zig").String; const String = @import("../../string.zig").String;
const log = @import("../../log.zig");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const color = @import("../color.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
/// https://developer.mozilla.org/en-US/docs/Web/API/ImageData/ImageData /// https://developer.mozilla.org/en-US/docs/Web/API/ImageData/ImageData

View File

@@ -16,6 +16,8 @@
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
@@ -39,6 +41,7 @@ pub fn registerTypes() []const type {
const IntersectionObserver = @This(); const IntersectionObserver = @This();
_rc: lp.RC(u8) = .{},
_arena: Allocator, _arena: Allocator,
_callback: js.Function.Temp, _callback: js.Function.Temp,
_observing: std.ArrayList(*Element) = .{}, _observing: std.ArrayList(*Element) = .{},
@@ -108,15 +111,22 @@ pub fn init(callback: js.Function.Temp, options: ?ObserverInit, page: *Page) !*I
return self; return self;
} }
pub fn deinit(self: *IntersectionObserver, shutdown: bool, session: *Session) void { pub fn deinit(self: *IntersectionObserver, session: *Session) void {
self._callback.release(); self._callback.release();
if ((comptime IS_DEBUG) and !shutdown) { for (self._pending_entries.items) |entry| {
std.debug.assert(self._observing.items.len == 0); entry.deinitIfUnused(session);
} }
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *IntersectionObserver, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *IntersectionObserver) void {
self._rc.acquire();
}
pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void { pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void {
// Check if already observing this target // Check if already observing this target
for (self._observing.items) |elem| { for (self._observing.items) |elem| {
@@ -127,7 +137,7 @@ pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void
// Register with page if this is our first observation // Register with page if this is our first observation
if (self._observing.items.len == 0) { if (self._observing.items.len == 0) {
page.js.strongRef(self); self._rc._refs += 1;
try page.registerIntersectionObserver(self); try page.registerIntersectionObserver(self);
} }
@@ -144,17 +154,19 @@ pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void
} }
pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) void { pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) void {
const original_length = self._observing.items.len;
for (self._observing.items, 0..) |elem, i| { for (self._observing.items, 0..) |elem, i| {
if (elem == target) { if (elem == target) {
_ = self._observing.swapRemove(i); _ = self._observing.swapRemove(i);
_ = self._previous_states.remove(target); _ = self._previous_states.remove(target);
// Remove any pending entries for this target // Remove any pending entries for this target.
// Entries will be cleaned up by V8 GC via the finalizer.
var j: usize = 0; var j: usize = 0;
while (j < self._pending_entries.items.len) { while (j < self._pending_entries.items.len) {
if (self._pending_entries.items[j]._target == target) { if (self._pending_entries.items[j]._target == target) {
const entry = self._pending_entries.swapRemove(j); const entry = self._pending_entries.swapRemove(j);
entry.deinit(false, page._session); entry.deinitIfUnused(page._session);
} else { } else {
j += 1; j += 1;
} }
@@ -163,21 +175,26 @@ pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) voi
} }
} }
if (self._observing.items.len == 0) { if (original_length > 0 and self._observing.items.len == 0) {
page.js.safeWeakRef(self); self._rc._refs -= 1;
} }
} }
pub fn disconnect(self: *IntersectionObserver, page: *Page) void { pub fn disconnect(self: *IntersectionObserver, page: *Page) void {
page.unregisterIntersectionObserver(self);
self._observing.clearRetainingCapacity();
self._previous_states.clearRetainingCapacity();
for (self._pending_entries.items) |entry| { for (self._pending_entries.items) |entry| {
entry.deinit(false, page._session); entry.deinitIfUnused(page._session);
} }
self._pending_entries.clearRetainingCapacity(); self._pending_entries.clearRetainingCapacity();
page.js.safeWeakRef(self); self._previous_states.clearRetainingCapacity();
const observing_count = self._observing.items.len;
self._observing.clearRetainingCapacity();
if (observing_count > 0) {
_ = self.releaseRef(page._session);
}
page.unregisterIntersectionObserver(self);
} }
pub fn takeRecords(self: *IntersectionObserver, page: *Page) ![]*IntersectionObserverEntry { pub fn takeRecords(self: *IntersectionObserver, page: *Page) ![]*IntersectionObserverEntry {
@@ -268,7 +285,6 @@ fn checkIntersection(self: *IntersectionObserver, target: *Element, page: *Page)
._bounding_client_rect = try page._factory.create(data.bounding_client_rect), ._bounding_client_rect = try page._factory.create(data.bounding_client_rect),
._intersection_ratio = data.intersection_ratio, ._intersection_ratio = data.intersection_ratio,
}; };
try self._pending_entries.append(self._arena, entry); try self._pending_entries.append(self._arena, entry);
} }
@@ -310,6 +326,7 @@ pub fn deliverEntries(self: *IntersectionObserver, page: *Page) !void {
} }
pub const IntersectionObserverEntry = struct { pub const IntersectionObserverEntry = struct {
_rc: lp.RC(u8) = .{},
_arena: Allocator, _arena: Allocator,
_time: f64, _time: f64,
_target: *Element, _target: *Element,
@@ -319,10 +336,25 @@ pub const IntersectionObserverEntry = struct {
_intersection_ratio: f64, _intersection_ratio: f64,
_is_intersecting: bool, _is_intersecting: bool,
pub fn deinit(self: *IntersectionObserverEntry, _: bool, session: *Session) void { pub fn deinit(self: *IntersectionObserverEntry, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
fn deinitIfUnused(self: *IntersectionObserverEntry, session: *Session) void {
if (self._rc._refs == 0) {
// hasn't been handed to JS yet.
self.deinit(session);
}
}
pub fn releaseRef(self: *IntersectionObserverEntry, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *IntersectionObserverEntry) void {
self._rc.acquire();
}
pub fn getTarget(self: *const IntersectionObserverEntry) *Element { pub fn getTarget(self: *const IntersectionObserverEntry) *Element {
return self._target; return self._target;
} }
@@ -358,8 +390,6 @@ pub const IntersectionObserverEntry = struct {
pub const name = "IntersectionObserverEntry"; pub const name = "IntersectionObserverEntry";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(IntersectionObserverEntry.deinit);
}; };
pub const target = bridge.accessor(IntersectionObserverEntry.getTarget, null, .{}); pub const target = bridge.accessor(IntersectionObserverEntry.getTarget, null, .{});
@@ -379,8 +409,6 @@ pub const JsApi = struct {
pub const name = "IntersectionObserver"; pub const name = "IntersectionObserver";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(IntersectionObserver.deinit);
}; };
pub const constructor = bridge.constructor(init, .{}); pub const constructor = bridge.constructor(init, .{});

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const String = @import("../../string.zig").String; const String = @import("../../string.zig").String;
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
@@ -39,6 +40,7 @@ pub fn registerTypes() []const type {
const MutationObserver = @This(); const MutationObserver = @This();
_rc: lp.RC(u8) = .{},
_arena: Allocator, _arena: Allocator,
_callback: js.Function.Temp, _callback: js.Function.Temp,
_observing: std.ArrayList(Observing) = .{}, _observing: std.ArrayList(Observing) = .{},
@@ -85,15 +87,20 @@ pub fn init(callback: js.Function.Temp, page: *Page) !*MutationObserver {
return self; return self;
} }
pub fn deinit(self: *MutationObserver, shutdown: bool, session: *Session) void { /// Force cleanup on Session shutdown.
pub fn deinit(self: *MutationObserver, session: *Session) void {
self._callback.release(); self._callback.release();
if ((comptime IS_DEBUG) and !shutdown) {
std.debug.assert(self._observing.items.len == 0);
}
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *MutationObserver, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *MutationObserver) void {
self._rc.acquire();
}
pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, page: *Page) !void { pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, page: *Page) !void {
const arena = self._arena; const arena = self._arena;
@@ -158,7 +165,7 @@ pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions,
// Register with page if this is our first observation // Register with page if this is our first observation
if (self._observing.items.len == 0) { if (self._observing.items.len == 0) {
page.js.strongRef(self); self._rc._refs += 1;
try page.registerMutationObserver(self); try page.registerMutationObserver(self);
} }
@@ -169,13 +176,17 @@ pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions,
} }
pub fn disconnect(self: *MutationObserver, page: *Page) void { pub fn disconnect(self: *MutationObserver, page: *Page) void {
page.unregisterMutationObserver(self);
self._observing.clearRetainingCapacity();
for (self._pending_records.items) |record| { for (self._pending_records.items) |record| {
record.deinit(false, page._session); _ = record.releaseRef(page._session);
} }
self._pending_records.clearRetainingCapacity(); self._pending_records.clearRetainingCapacity();
page.js.safeWeakRef(self); const observing_count = self._observing.items.len;
self._observing.clearRetainingCapacity();
if (observing_count > 0) {
_ = self.releaseRef(page._session);
}
page.unregisterMutationObserver(self);
} }
pub fn takeRecords(self: *MutationObserver, page: *Page) ![]*MutationRecord { pub fn takeRecords(self: *MutationObserver, page: *Page) ![]*MutationRecord {
@@ -348,6 +359,7 @@ pub fn deliverRecords(self: *MutationObserver, page: *Page) !void {
} }
pub const MutationRecord = struct { pub const MutationRecord = struct {
_rc: lp.RC(u8) = .{},
_type: Type, _type: Type,
_target: *Node, _target: *Node,
_arena: Allocator, _arena: Allocator,
@@ -364,10 +376,18 @@ pub const MutationRecord = struct {
characterData, characterData,
}; };
pub fn deinit(self: *MutationRecord, _: bool, session: *Session) void { pub fn deinit(self: *MutationRecord, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *MutationRecord, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *MutationRecord) void {
self._rc.acquire();
}
pub fn getType(self: *const MutationRecord) []const u8 { pub fn getType(self: *const MutationRecord) []const u8 {
return switch (self._type) { return switch (self._type) {
.attributes => "attributes", .attributes => "attributes",
@@ -418,8 +438,6 @@ pub const MutationRecord = struct {
pub const name = "MutationRecord"; pub const name = "MutationRecord";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(MutationRecord.deinit);
}; };
pub const @"type" = bridge.accessor(MutationRecord.getType, null, .{}); pub const @"type" = bridge.accessor(MutationRecord.getType, null, .{});
@@ -441,8 +459,6 @@ pub const JsApi = struct {
pub const name = "MutationObserver"; pub const name = "MutationObserver";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(MutationObserver.deinit);
}; };
pub const constructor = bridge.constructor(MutationObserver.init, .{}); pub const constructor = bridge.constructor(MutationObserver.init, .{});

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const Session = @import("../Session.zig"); const Session = @import("../Session.zig");
@@ -50,14 +51,23 @@ pub fn query(_: *const Permissions, qd: QueryDescriptor, page: *Page) !js.Promis
} }
const PermissionStatus = struct { const PermissionStatus = struct {
_rc: lp.RC(u8) = .{},
_arena: Allocator, _arena: Allocator,
_name: []const u8, _name: []const u8,
_state: []const u8, _state: []const u8,
pub fn deinit(self: *PermissionStatus, _: bool, session: *Session) void { pub fn deinit(self: *PermissionStatus, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *PermissionStatus, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *PermissionStatus) void {
self._rc.acquire();
}
fn getName(self: *const PermissionStatus) []const u8 { fn getName(self: *const PermissionStatus) []const u8 {
return self._name; return self._name;
} }
@@ -72,8 +82,6 @@ const PermissionStatus = struct {
pub const name = "PermissionStatus"; pub const name = "PermissionStatus";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(PermissionStatus.deinit);
}; };
pub const name = bridge.accessor(getName, null, .{}); pub const name = bridge.accessor(getName, null, .{});
pub const state = bridge.accessor(getState, null, .{}); pub const state = bridge.accessor(getState, null, .{});

View File

@@ -28,8 +28,6 @@ const DocumentFragment = @import("DocumentFragment.zig");
const AbstractRange = @import("AbstractRange.zig"); const AbstractRange = @import("AbstractRange.zig");
const DOMRect = @import("DOMRect.zig"); const DOMRect = @import("DOMRect.zig");
const Allocator = std.mem.Allocator;
const Range = @This(); const Range = @This();
_proto: *AbstractRange, _proto: *AbstractRange,
@@ -40,10 +38,6 @@ pub fn init(page: *Page) !*Range {
return page._factory.abstractRange(arena, Range{ ._proto = undefined }, page); return page._factory.abstractRange(arena, Range{ ._proto = undefined }, page);
} }
pub fn deinit(self: *Range, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asAbstractRange(self: *Range) *AbstractRange { pub fn asAbstractRange(self: *Range) *AbstractRange {
return self._proto; return self._proto;
} }
@@ -699,8 +693,6 @@ pub const JsApi = struct {
pub const name = "Range"; pub const name = "Range";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Range.deinit);
}; };
// Constants for compareBoundaryPoints // Constants for compareBoundaryPoints

View File

@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const log = @import("../../log.zig"); const lp = @import("lightpanda");
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
@@ -27,25 +27,33 @@ const Range = @import("Range.zig");
const AbstractRange = @import("AbstractRange.zig"); const AbstractRange = @import("AbstractRange.zig");
const Node = @import("Node.zig"); const Node = @import("Node.zig");
const Event = @import("Event.zig"); const Event = @import("Event.zig");
const Document = @import("Document.zig");
/// https://w3c.github.io/selection-api/ /// https://w3c.github.io/selection-api/
const Selection = @This(); const Selection = @This();
pub const SelectionDirection = enum { backward, forward, none }; pub const SelectionDirection = enum { backward, forward, none };
_rc: lp.RC(u8) = .{},
_range: ?*Range = null, _range: ?*Range = null,
_direction: SelectionDirection = .none, _direction: SelectionDirection = .none,
pub const init: Selection = .{}; pub const init: Selection = .{};
pub fn deinit(self: *Selection, shutdown: bool, session: *Session) void { pub fn deinit(self: *Selection, session: *Session) void {
if (self._range) |r| { if (self._range) |r| {
r.deinit(shutdown, session); r.asAbstractRange().releaseRef(session);
self._range = null; self._range = null;
} }
} }
pub fn releaseRef(self: *Selection, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *Selection) void {
self._rc.acquire();
}
fn dispatchSelectionChangeEvent(page: *Page) !void { fn dispatchSelectionChangeEvent(page: *Page) !void {
const event = try Event.init("selectionchange", .{}, page); const event = try Event.init("selectionchange", .{}, page);
try page._event_manager.dispatch(page.document.asEventTarget(), event); try page._event_manager.dispatch(page.document.asEventTarget(), event);
@@ -695,7 +703,7 @@ pub fn toString(self: *const Selection, page: *Page) ![]const u8 {
fn setRange(self: *Selection, new_range: ?*Range, page: *Page) void { fn setRange(self: *Selection, new_range: ?*Range, page: *Page) void {
if (self._range) |existing| { if (self._range) |existing| {
existing.deinit(false, page._session); _ = existing.asAbstractRange().releaseRef(page._session);
} }
if (new_range) |nr| { if (new_range) |nr| {
nr.asAbstractRange().acquireRef(); nr.asAbstractRange().acquireRef();
@@ -710,7 +718,6 @@ pub const JsApi = struct {
pub const name = "Selection"; pub const name = "Selection";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const finalizer = bridge.finalizer(Selection.deinit);
}; };
pub const anchorNode = bridge.accessor(Selection.getAnchorNode, null, .{}); pub const anchorNode = bridge.accessor(Selection.getAnchorNode, null, .{});

View File

@@ -256,8 +256,7 @@ pub fn createObjectURL(blob: *Blob, page: *Page) ![]const u8 {
.{ page.origin orelse "null", uuid_buf }, .{ page.origin orelse "null", uuid_buf },
); );
try page._blob_urls.put(page.arena, blob_url, blob); try page._blob_urls.put(page.arena, blob_url, blob);
// prevent GC from cleaning up the blob while it's in the registry blob.acquireRef();
page.js.strongRef(blob);
return blob_url; return blob_url;
} }
@@ -267,9 +266,8 @@ pub fn revokeObjectURL(url: []const u8, page: *Page) void {
return; return;
} }
// Remove from registry and release strong ref (no-op if not found)
if (page._blob_urls.fetchRemove(url)) |entry| { if (page._blob_urls.fetchRemove(url)) |entry| {
page.js.weakRef(entry.value); entry.value.releaseRef(page._session);
} }
} }

View File

@@ -19,7 +19,6 @@
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const EventTarget = @import("EventTarget.zig"); const EventTarget = @import("EventTarget.zig");
const Window = @import("Window.zig");
const VisualViewport = @This(); const VisualViewport = @This();

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const log = @import("../../../log.zig"); const log = @import("../../../log.zig");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
@@ -33,6 +34,7 @@ const PlayState = enum {
finished, finished,
}; };
_rc: lp.RC(u32) = .{},
_page: *Page, _page: *Page,
_arena: Allocator, _arena: Allocator,
@@ -62,10 +64,18 @@ pub fn init(page: *Page) !*Animation {
return self; return self;
} }
pub fn deinit(self: *Animation, _: bool, session: *Session) void { pub fn deinit(self: *Animation, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *Animation, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *Animation) void {
self._rc.acquire();
}
pub fn play(self: *Animation, page: *Page) !void { pub fn play(self: *Animation, page: *Page) !void {
if (self._playState == .running) { if (self._playState == .running) {
return; return;
@@ -75,7 +85,7 @@ pub fn play(self: *Animation, page: *Page) !void {
self._playState = .running; self._playState = .running;
// Schedule the transition from .running => .finished in 10ms. // Schedule the transition from .running => .finished in 10ms.
page.js.strongRef(self); self.acquireRef();
try page.js.scheduler.add( try page.js.scheduler.add(
self, self,
Animation.update, Animation.update,
@@ -201,7 +211,7 @@ fn update(ctx: *anyopaque) !?u32 {
} }
// No future change scheduled, set the object weak for garbage collection. // No future change scheduled, set the object weak for garbage collection.
self._page.js.weakRef(self); self.releaseRef(self._page._session);
return null; return null;
} }
@@ -220,8 +230,6 @@ pub const JsApi = struct {
pub const name = "Animation"; pub const name = "Animation";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Animation.deinit);
}; };
pub const play = bridge.function(Animation.play, .{}); pub const play = bridge.function(Animation.play, .{});

View File

@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const String = @import("../../../string.zig").String;
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const CData = @import("../CData.zig"); const CData = @import("../CData.zig");

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const log = @import("../../../log.zig"); const log = @import("../../../log.zig");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
@@ -37,15 +38,9 @@ _data: union(enum) {
radio_node_list: *RadioNodeList, radio_node_list: *RadioNodeList,
name: NodeLive(.name), name: NodeLive(.name),
}, },
_rc: usize = 0, _rc: lp.RC(u32) = .{},
pub fn deinit(self: *NodeList, _: bool, session: *Session) void {
const rc = self._rc;
if (rc > 1) {
self._rc = rc - 1;
return;
}
pub fn deinit(self: *NodeList, session: *Session) void {
switch (self._data) { switch (self._data) {
.selector_list => |list| list.deinit(session), .selector_list => |list| list.deinit(session),
.child_nodes => |cn| cn.deinit(session), .child_nodes => |cn| cn.deinit(session),
@@ -53,8 +48,12 @@ pub fn deinit(self: *NodeList, _: bool, session: *Session) void {
} }
} }
pub fn releaseRef(self: *NodeList, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *NodeList) void { pub fn acquireRef(self: *NodeList) void {
self._rc += 1; self._rc.acquire();
} }
pub fn length(self: *NodeList, page: *Page) !u32 { pub fn length(self: *NodeList, page: *Page) !u32 {
@@ -119,8 +118,12 @@ const Iterator = struct {
const Entry = struct { u32, *Node }; const Entry = struct { u32, *Node };
pub fn deinit(self: *Iterator, shutdown: bool, session: *Session) void { pub fn deinit(self: *Iterator, session: *Session) void {
self.list.deinit(shutdown, session); self.list.deinit(session);
}
pub fn releaseRef(self: *Iterator, session: *Session) void {
self.list.releaseRef(session);
} }
pub fn acquireRef(self: *Iterator) void { pub fn acquireRef(self: *Iterator) void {
@@ -143,8 +146,6 @@ pub const JsApi = struct {
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const enumerable = false; pub const enumerable = false;
pub const weak = true;
pub const finalizer = bridge.finalizer(NodeList.deinit);
}; };
pub const length = bridge.accessor(NodeList.length, null, .{}); pub const length = bridge.accessor(NodeList.length, null, .{});

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
@@ -40,9 +41,15 @@ pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type {
return page._factory.create(Self{ .inner = inner }); return page._factory.create(Self{ .inner = inner });
} }
pub fn deinit(self: *Self, shutdown: bool, session: *Session) void { pub fn deinit(self: *Self, session: *Session) void {
if (@hasDecl(Inner, "deinit")) { _ = self;
self.inner.deinit(shutdown, session); _ = session;
}
pub fn releaseRef(self: *Self, session: *Session) void {
// Release the reference to the inner type that we acquired
if (@hasDecl(Inner, "releaseRef")) {
self.inner.releaseRef(session);
} }
} }
@@ -73,8 +80,6 @@ pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type {
pub const Meta = struct { pub const Meta = struct {
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Self.deinit);
}; };
pub const next = bridge.function(Self.next, .{ .null_as_undefined = true }); pub const next = bridge.function(Self.next, .{ .null_as_undefined = true });

View File

@@ -20,7 +20,6 @@
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda"); const lp = @import("lightpanda");
const log = @import("../../../log.zig");
const crypto = @import("../../../sys/libcrypto.zig"); const crypto = @import("../../../sys/libcrypto.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");

View File

@@ -20,12 +20,10 @@
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda"); const lp = @import("lightpanda");
const log = @import("../../../log.zig");
const crypto = @import("../../../sys/libcrypto.zig"); const crypto = @import("../../../sys/libcrypto.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Algorithm = @import("algorithm.zig").Algorithm;
const CryptoKey = @import("../CryptoKey.zig"); const CryptoKey = @import("../CryptoKey.zig");

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
@@ -25,6 +26,7 @@ const Allocator = std.mem.Allocator;
const FontFace = @This(); const FontFace = @This();
_rc: lp.RC(u8) = .{},
_arena: Allocator, _arena: Allocator,
_family: []const u8, _family: []const u8,
@@ -42,10 +44,18 @@ pub fn init(family: []const u8, source: []const u8, page: *Page) !*FontFace {
return self; return self;
} }
pub fn deinit(self: *FontFace, _: bool, session: *Session) void { pub fn deinit(self: *FontFace, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *FontFace, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *FontFace) void {
self._rc.acquire();
}
pub fn getFamily(self: *const FontFace) []const u8 { pub fn getFamily(self: *const FontFace) []const u8 {
return self._family; return self._family;
} }
@@ -67,8 +77,6 @@ pub const JsApi = struct {
pub const name = "FontFace"; pub const name = "FontFace";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(FontFace.deinit);
}; };
pub const constructor = bridge.constructor(FontFace.init, .{}); pub const constructor = bridge.constructor(FontFace.init, .{});

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
@@ -28,6 +29,7 @@ const Allocator = std.mem.Allocator;
const FontFaceSet = @This(); const FontFaceSet = @This();
_rc: lp.RC(u8) = .{},
_proto: *EventTarget, _proto: *EventTarget,
_arena: Allocator, _arena: Allocator,
@@ -41,10 +43,18 @@ pub fn init(page: *Page) !*FontFaceSet {
}); });
} }
pub fn deinit(self: *FontFaceSet, _: bool, session: *Session) void { pub fn deinit(self: *FontFaceSet, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *FontFaceSet, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *FontFaceSet) void {
self._rc.acquire();
}
pub fn asEventTarget(self: *FontFaceSet) *EventTarget { pub fn asEventTarget(self: *FontFaceSet) *EventTarget {
return self._proto; return self._proto;
} }
@@ -95,8 +105,6 @@ pub const JsApi = struct {
pub const name = "FontFaceSet"; pub const name = "FontFaceSet";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(FontFaceSet.deinit);
}; };
pub const size = bridge.property(0, .{ .template = false, .readonly = true }); pub const size = bridge.property(0, .{ .template = false, .readonly = true });

View File

@@ -22,7 +22,6 @@ const reflect = @import("../../reflect.zig");
const log = @import("../../../log.zig"); const log = @import("../../../log.zig");
const global_event_handlers = @import("../global_event_handlers.zig"); const global_event_handlers = @import("../global_event_handlers.zig");
const GlobalEventHandlersLookup = global_event_handlers.Lookup;
const GlobalEventHandler = global_event_handlers.Handler; const GlobalEventHandler = global_event_handlers.Handler;
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");

View File

@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const log = @import("../../../../log.zig");
const js = @import("../../../js/js.zig"); const js = @import("../../../js/js.zig");
const Page = @import("../../../Page.zig"); const Page = @import("../../../Page.zig");
const Window = @import("../../Window.zig"); const Window = @import("../../Window.zig");

View File

@@ -5,10 +5,6 @@ const URL = @import("../../../URL.zig");
const Node = @import("../../Node.zig"); const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig"); const Element = @import("../../Element.zig");
const HtmlElement = @import("../Html.zig"); const HtmlElement = @import("../Html.zig");
const Event = @import("../../Event.zig");
const log = @import("../../../../log.zig");
const IS_DEBUG = @import("builtin").mode == .Debug;
const Image = @This(); const Image = @This();
_proto: *HtmlElement, _proto: *HtmlElement,

View File

@@ -17,7 +17,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const log = @import("../../../../log.zig");
const js = @import("../../../js/js.zig"); const js = @import("../../../js/js.zig");
const Page = @import("../../../Page.zig"); const Page = @import("../../../Page.zig");

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
@@ -25,6 +26,7 @@ const Allocator = std.mem.Allocator;
const TextDecoder = @This(); const TextDecoder = @This();
_rc: lp.RC(u8) = .{},
_fatal: bool, _fatal: bool,
_arena: Allocator, _arena: Allocator,
_ignore_bom: bool, _ignore_bom: bool,
@@ -60,10 +62,18 @@ pub fn init(label_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*TextDecoder {
return self; return self;
} }
pub fn deinit(self: *TextDecoder, _: bool, session: *Session) void { pub fn deinit(self: *TextDecoder, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *TextDecoder, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *TextDecoder) void {
self._rc.acquire();
}
pub fn getIgnoreBOM(self: *const TextDecoder) bool { pub fn getIgnoreBOM(self: *const TextDecoder) bool {
return self._ignore_bom; return self._ignore_bom;
} }
@@ -109,8 +119,6 @@ pub const JsApi = struct {
pub const name = "TextDecoder"; pub const name = "TextDecoder";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(TextDecoder.deinit);
}; };
pub const constructor = bridge.constructor(TextDecoder.init, .{}); pub const constructor = bridge.constructor(TextDecoder.init, .{});

View File

@@ -22,7 +22,6 @@ const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
const Event = @import("../Event.zig"); const Event = @import("../Event.zig");
const Allocator = std.mem.Allocator;
const CompositionEvent = @This(); const CompositionEvent = @This();
@@ -54,10 +53,6 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*CompositionEvent {
return event; return event;
} }
pub fn deinit(self: *CompositionEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *CompositionEvent) *Event { pub fn asEvent(self: *CompositionEvent) *Event {
return self._proto; return self._proto;
} }
@@ -73,8 +68,6 @@ pub const JsApi = struct {
pub const name = "CompositionEvent"; pub const name = "CompositionEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(CompositionEvent.deinit);
}; };
pub const constructor = bridge.constructor(CompositionEvent.init, .{}); pub const constructor = bridge.constructor(CompositionEvent.init, .{});

View File

@@ -73,11 +73,19 @@ pub fn initCustomEvent(
self._detail = detail_; self._detail = detail_;
} }
pub fn deinit(self: *CustomEvent, shutdown: bool, session: *Session) void { pub fn deinit(self: *CustomEvent, session: *Session) void {
if (self._detail) |d| { if (self._detail) |d| {
d.release(); d.release();
} }
self._proto.deinit(shutdown, session); self._proto.deinit(session);
}
pub fn acquireRef(self: *CustomEvent) void {
self._proto.acquireRef();
}
pub fn releaseRef(self: *CustomEvent, session: *Session) void {
self._proto._rc.release(self, session);
} }
pub fn asEvent(self: *CustomEvent) *Event { pub fn asEvent(self: *CustomEvent) *Event {
@@ -95,8 +103,6 @@ pub const JsApi = struct {
pub const name = "CustomEvent"; pub const name = "CustomEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(CustomEvent.deinit);
pub const enumerable = false; pub const enumerable = false;
}; };

View File

@@ -80,11 +80,19 @@ fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *ErrorEvent, shutdown: bool, session: *Session) void { pub fn deinit(self: *ErrorEvent, session: *Session) void {
if (self._error) |e| { if (self._error) |e| {
e.release(); e.release();
} }
self._proto.deinit(shutdown, session); self._proto.deinit(session);
}
pub fn acquireRef(self: *ErrorEvent) void {
self._proto.acquireRef();
}
pub fn releaseRef(self: *ErrorEvent, session: *Session) void {
self._proto._rc.release(self, session);
} }
pub fn asEvent(self: *ErrorEvent) *Event { pub fn asEvent(self: *ErrorEvent) *Event {

View File

@@ -70,10 +70,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *FocusEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *FocusEvent) *Event { pub fn asEvent(self: *FocusEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -89,8 +85,6 @@ pub const JsApi = struct {
pub const name = "FocusEvent"; pub const name = "FocusEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(FocusEvent.deinit);
}; };
pub const constructor = bridge.constructor(FocusEvent.init, .{}); pub const constructor = bridge.constructor(FocusEvent.init, .{});

View File

@@ -24,7 +24,6 @@ const Session = @import("../../Session.zig");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Event = @import("../Event.zig"); const Event = @import("../Event.zig");
const UIEvent = @import("UIEvent.zig");
const FormData = @import("../net/FormData.zig"); const FormData = @import("../net/FormData.zig");
@@ -67,10 +66,6 @@ fn initWithTrusted(arena: Allocator, typ: String, maybe_options: ?Options, trust
return event; return event;
} }
pub fn deinit(self: *FormDataEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *FormDataEvent) *Event { pub fn asEvent(self: *FormDataEvent) *Event {
return self._proto; return self._proto;
} }
@@ -86,8 +81,6 @@ pub const JsApi = struct {
pub const name = "FormDataEvent"; pub const name = "FormDataEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(FormDataEvent.deinit);
}; };
pub const constructor = bridge.constructor(FormDataEvent.init, .{}); pub const constructor = bridge.constructor(FormDataEvent.init, .{});

View File

@@ -83,10 +83,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *InputEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *InputEvent) *Event { pub fn asEvent(self: *InputEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -110,8 +106,6 @@ pub const JsApi = struct {
pub const name = "InputEvent"; pub const name = "InputEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(InputEvent.deinit);
}; };
pub const constructor = bridge.constructor(InputEvent.init, .{}); pub const constructor = bridge.constructor(InputEvent.init, .{});

View File

@@ -229,10 +229,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *KeyboardEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *KeyboardEvent) *Event { pub fn asEvent(self: *KeyboardEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -296,8 +292,6 @@ pub const JsApi = struct {
pub const name = "KeyboardEvent"; pub const name = "KeyboardEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(KeyboardEvent.deinit);
}; };
pub const constructor = bridge.constructor(KeyboardEvent.init, .{}); pub const constructor = bridge.constructor(KeyboardEvent.init, .{});

View File

@@ -73,11 +73,19 @@ fn initWithTrusted(arena: Allocator, typ: String, opts_: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *MessageEvent, shutdown: bool, session: *Session) void { pub fn deinit(self: *MessageEvent, session: *Session) void {
if (self._data) |d| { if (self._data) |d| {
d.release(); d.release();
} }
self._proto.deinit(shutdown, session); self._proto.deinit(session);
}
pub fn acquireRef(self: *MessageEvent) void {
self._proto.acquireRef();
}
pub fn releaseRef(self: *MessageEvent, session: *Session) void {
self._proto._rc.release(self, session);
} }
pub fn asEvent(self: *MessageEvent) *Event { pub fn asEvent(self: *MessageEvent) *Event {
@@ -103,8 +111,6 @@ pub const JsApi = struct {
pub const name = "MessageEvent"; pub const name = "MessageEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(MessageEvent.deinit);
}; };
pub const constructor = bridge.constructor(MessageEvent.init, .{}); pub const constructor = bridge.constructor(MessageEvent.init, .{});

View File

@@ -121,10 +121,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *MouseEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *MouseEvent) *Event { pub fn asEvent(self: *MouseEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -203,8 +199,6 @@ pub const JsApi = struct {
pub const name = "MouseEvent"; pub const name = "MouseEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(MouseEvent.deinit);
}; };
pub const constructor = bridge.constructor(MouseEvent.init, .{}); pub const constructor = bridge.constructor(MouseEvent.init, .{});

View File

@@ -83,10 +83,6 @@ fn initWithTrusted(
return event; return event;
} }
pub fn deinit(self: *NavigationCurrentEntryChangeEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *NavigationCurrentEntryChangeEvent) *Event { pub fn asEvent(self: *NavigationCurrentEntryChangeEvent) *Event {
return self._proto; return self._proto;
} }
@@ -106,8 +102,6 @@ pub const JsApi = struct {
pub const name = "NavigationCurrentEntryChangeEvent"; pub const name = "NavigationCurrentEntryChangeEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(NavigationCurrentEntryChangeEvent.deinit);
}; };
pub const constructor = bridge.constructor(NavigationCurrentEntryChangeEvent.init, .{}); pub const constructor = bridge.constructor(NavigationCurrentEntryChangeEvent.init, .{});

View File

@@ -66,10 +66,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *PageTransitionEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *PageTransitionEvent) *Event { pub fn asEvent(self: *PageTransitionEvent) *Event {
return self._proto; return self._proto;
} }
@@ -85,8 +81,6 @@ pub const JsApi = struct {
pub const name = "PageTransitionEvent"; pub const name = "PageTransitionEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(PageTransitionEvent.deinit);
}; };
pub const constructor = bridge.constructor(PageTransitionEvent.init, .{}); pub const constructor = bridge.constructor(PageTransitionEvent.init, .{});

View File

@@ -128,10 +128,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*PointerEvent {
return event; return event;
} }
pub fn deinit(self: *PointerEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *PointerEvent) *Event { pub fn asEvent(self: *PointerEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -191,8 +187,6 @@ pub const JsApi = struct {
pub const name = "PointerEvent"; pub const name = "PointerEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(PointerEvent.deinit);
}; };
pub const constructor = bridge.constructor(PointerEvent.init, .{}); pub const constructor = bridge.constructor(PointerEvent.init, .{});

View File

@@ -67,10 +67,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *PopStateEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *PopStateEvent) *Event { pub fn asEvent(self: *PopStateEvent) *Event {
return self._proto; return self._proto;
} }
@@ -92,8 +88,6 @@ pub const JsApi = struct {
pub const name = "PopStateEvent"; pub const name = "PopStateEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(PopStateEvent.deinit);
}; };
pub const constructor = bridge.constructor(PopStateEvent.init, .{}); pub const constructor = bridge.constructor(PopStateEvent.init, .{});

View File

@@ -68,10 +68,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *ProgressEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *ProgressEvent) *Event { pub fn asEvent(self: *ProgressEvent) *Event {
return self._proto; return self._proto;
} }
@@ -96,8 +92,6 @@ pub const JsApi = struct {
pub const name = "ProgressEvent"; pub const name = "ProgressEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(ProgressEvent.deinit);
}; };
pub const constructor = bridge.constructor(ProgressEvent.init, .{}); pub const constructor = bridge.constructor(ProgressEvent.init, .{});

View File

@@ -22,7 +22,6 @@ const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
const Event = @import("../Event.zig"); const Event = @import("../Event.zig");
const Allocator = std.mem.Allocator;
const PromiseRejectionEvent = @This(); const PromiseRejectionEvent = @This();
@@ -57,14 +56,22 @@ pub fn init(typ: []const u8, opts_: ?Options, page: *Page) !*PromiseRejectionEve
return event; return event;
} }
pub fn deinit(self: *PromiseRejectionEvent, shutdown: bool, session: *Session) void { pub fn deinit(self: *PromiseRejectionEvent, session: *Session) void {
if (self._reason) |r| { if (self._reason) |r| {
r.release(); r.release();
} }
if (self._promise) |p| { if (self._promise) |p| {
p.release(); p.release();
} }
self._proto.deinit(shutdown, session); self._proto.deinit(session);
}
pub fn acquireRef(self: *PromiseRejectionEvent) void {
self._proto.acquireRef();
}
pub fn releaseRef(self: *PromiseRejectionEvent, session: *Session) void {
self._proto._rc.release(self, session);
} }
pub fn asEvent(self: *PromiseRejectionEvent) *Event { pub fn asEvent(self: *PromiseRejectionEvent) *Event {
@@ -86,8 +93,6 @@ pub const JsApi = struct {
pub const name = "PromiseRejectionEvent"; pub const name = "PromiseRejectionEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(PromiseRejectionEvent.deinit);
}; };
pub const constructor = bridge.constructor(PromiseRejectionEvent.init, .{}); pub const constructor = bridge.constructor(PromiseRejectionEvent.init, .{});

View File

@@ -67,10 +67,6 @@ fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool
return event; return event;
} }
pub fn deinit(self: *SubmitEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *SubmitEvent) *Event { pub fn asEvent(self: *SubmitEvent) *Event {
return self._proto; return self._proto;
} }
@@ -86,8 +82,6 @@ pub const JsApi = struct {
pub const name = "SubmitEvent"; pub const name = "SubmitEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(SubmitEvent.deinit);
}; };
pub const constructor = bridge.constructor(SubmitEvent.init, .{}); pub const constructor = bridge.constructor(SubmitEvent.init, .{});

View File

@@ -59,10 +59,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*TextEvent {
return event; return event;
} }
pub fn deinit(self: *TextEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *TextEvent) *Event { pub fn asEvent(self: *TextEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -101,8 +97,6 @@ pub const JsApi = struct {
pub const name = "TextEvent"; pub const name = "TextEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(TextEvent.deinit);
}; };
// No constructor - TextEvent is created via document.createEvent('TextEvent') // No constructor - TextEvent is created via document.createEvent('TextEvent')

View File

@@ -71,10 +71,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*UIEvent {
return event; return event;
} }
pub fn deinit(self: *UIEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn as(self: *UIEvent, comptime T: type) *T { pub fn as(self: *UIEvent, comptime T: type) *T {
return self.is(T).?; return self.is(T).?;
} }
@@ -122,8 +118,6 @@ pub const JsApi = struct {
pub const name = "UIEvent"; pub const name = "UIEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(UIEvent.deinit);
}; };
pub const constructor = bridge.constructor(UIEvent.init, .{}); pub const constructor = bridge.constructor(UIEvent.init, .{});

View File

@@ -87,10 +87,6 @@ pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*WheelEvent {
return event; return event;
} }
pub fn deinit(self: *WheelEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *WheelEvent) *Event { pub fn asEvent(self: *WheelEvent) *Event {
return self._proto.asEvent(); return self._proto.asEvent();
} }
@@ -118,8 +114,6 @@ pub const JsApi = struct {
pub const name = "WheelEvent"; pub const name = "WheelEvent";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(WheelEvent.deinit);
}; };
pub const constructor = bridge.constructor(WheelEvent.init, .{}); pub const constructor = bridge.constructor(WheelEvent.init, .{});

View File

@@ -27,8 +27,6 @@ const Page = @import("../../Page.zig");
const Event = @import("../Event.zig"); const Event = @import("../Event.zig");
const EventTarget = @import("../EventTarget.zig"); const EventTarget = @import("../EventTarget.zig");
const IS_DEBUG = @import("builtin").mode == .Debug;
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation // https://developer.mozilla.org/en-US/docs/Web/API/Navigation
const Navigation = @This(); const Navigation = @This();

View File

@@ -18,7 +18,6 @@
const std = @import("std"); const std = @import("std");
const URL = @import("../URL.zig"); const URL = @import("../URL.zig");
const EventTarget = @import("../EventTarget.zig");
const NavigationState = @import("root.zig").NavigationState; const NavigationState = @import("root.zig").NavigationState;
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");

View File

@@ -62,7 +62,7 @@ pub fn init(input: Input, options: ?InitOpts, page: *Page) !js.Promise {
} }
const response = try Response.init(null, .{ .status = 0 }, page); const response = try Response.init(null, .{ .status = 0 }, page);
errdefer response.deinit(true, page._session); errdefer response.deinit(page._session);
const fetch = try response._arena.create(Fetch); const fetch = try response._arena.create(Fetch);
fetch.* = .{ fetch.* = .{
@@ -225,7 +225,7 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
return ls.toLocal(self._resolver).resolve("fetch done", js_val); return ls.toLocal(self._resolver).resolve("fetch done", js_val);
} }
fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void { fn httpErrorCallback(ctx: *anyopaque, _: anyerror) void {
const self: *Fetch = @ptrCast(@alignCast(ctx)); const self: *Fetch = @ptrCast(@alignCast(ctx));
var response = self._response; var response = self._response;
@@ -234,7 +234,7 @@ fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
// clear this. (defer since `self is in the response's arena). // clear this. (defer since `self is in the response's arena).
defer if (self._owns_response) { defer if (self._owns_response) {
response.deinit(err == error.Abort, self._page._session); response.deinit(self._page._session);
self._owns_response = false; self._owns_response = false;
}; };
@@ -256,7 +256,7 @@ fn httpShutdownCallback(ctx: *anyopaque) void {
if (self._owns_response) { if (self._owns_response) {
var response = self._response; var response = self._response;
response._transfer = null; response._transfer = null;
response.deinit(true, self._page._session); response.deinit(self._page._session);
// Do not access `self` after this point: the Fetch struct was // Do not access `self` after this point: the Fetch struct was
// allocated from response._arena which has been released. // allocated from response._arena which has been released.
} }

View File

@@ -22,7 +22,6 @@ const log = @import("../../../log.zig");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const Node = @import("../Node.zig");
const Form = @import("../element/html/Form.zig"); const Form = @import("../element/html/Form.zig");
const Element = @import("../Element.zig"); const Element = @import("../Element.zig");
const KeyValueList = @import("../KeyValueList.zig"); const KeyValueList = @import("../KeyValueList.zig");

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const HttpClient = @import("../../HttpClient.zig"); const HttpClient = @import("../../HttpClient.zig");
@@ -38,6 +39,7 @@ pub const Type = enum {
opaqueredirect, opaqueredirect,
}; };
_rc: lp.RC(u8) = .{},
_status: u16, _status: u16,
_arena: Allocator, _arena: Allocator,
_headers: *Headers, _headers: *Headers,
@@ -78,18 +80,22 @@ pub fn init(body_: ?[]const u8, opts_: ?InitOpts, page: *Page) !*Response {
return self; return self;
} }
pub fn deinit(self: *Response, shutdown: bool, session: *Session) void { pub fn deinit(self: *Response, session: *Session) void {
if (self._transfer) |transfer| { if (self._transfer) |transfer| {
if (shutdown) { transfer.abort(error.Abort);
transfer.terminate();
} else {
transfer.abort(error.Abort);
}
self._transfer = null; self._transfer = null;
} }
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *Response, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *Response) void {
self._rc.acquire();
}
pub fn getStatus(self: *const Response) u16 { pub fn getStatus(self: *const Response) u16 {
return self._status; return self._status;
} }
@@ -197,8 +203,6 @@ pub const JsApi = struct {
pub const name = "Response"; pub const name = "Response";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(Response.deinit);
}; };
pub const constructor = bridge.constructor(Response.init, .{}); pub const constructor = bridge.constructor(Response.init, .{});

View File

@@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const js = @import("../../js/js.zig"); const js = @import("../../js/js.zig");
const log = @import("../../../log.zig"); const log = @import("../../../log.zig");
@@ -29,7 +30,6 @@ const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig"); const Session = @import("../../Session.zig");
const Node = @import("../Node.zig"); const Node = @import("../Node.zig");
const Blob = @import("../Blob.zig");
const Event = @import("../Event.zig"); const Event = @import("../Event.zig");
const Headers = @import("Headers.zig"); const Headers = @import("Headers.zig");
const EventTarget = @import("../EventTarget.zig"); const EventTarget = @import("../EventTarget.zig");
@@ -39,6 +39,7 @@ const Allocator = std.mem.Allocator;
const IS_DEBUG = @import("builtin").mode == .Debug; const IS_DEBUG = @import("builtin").mode == .Debug;
const XMLHttpRequest = @This(); const XMLHttpRequest = @This();
_rc: lp.RC(u8) = .{},
_page: *Page, _page: *Page,
_proto: *XMLHttpRequestEventTarget, _proto: *XMLHttpRequestEventTarget,
_arena: Allocator, _arena: Allocator,
@@ -88,21 +89,18 @@ const ResponseType = enum {
pub fn init(page: *Page) !*XMLHttpRequest { pub fn init(page: *Page) !*XMLHttpRequest {
const arena = try page.getArena(.{ .debug = "XMLHttpRequest" }); const arena = try page.getArena(.{ .debug = "XMLHttpRequest" });
errdefer page.releaseArena(arena); errdefer page.releaseArena(arena);
return page._factory.xhrEventTarget(arena, XMLHttpRequest{ const xhr = try page._factory.xhrEventTarget(arena, XMLHttpRequest{
._page = page, ._page = page,
._arena = arena, ._arena = arena,
._proto = undefined, ._proto = undefined,
._request_headers = try Headers.init(null, page), ._request_headers = try Headers.init(null, page),
}); });
return xhr;
} }
pub fn deinit(self: *XMLHttpRequest, shutdown: bool, session: *Session) void { pub fn deinit(self: *XMLHttpRequest, session: *Session) void {
if (self._transfer) |transfer| { if (self._transfer) |transfer| {
if (shutdown) { transfer.abort(error.Abort);
transfer.terminate();
} else {
transfer.abort(error.Abort);
}
self._transfer = null; self._transfer = null;
} }
@@ -138,6 +136,14 @@ pub fn deinit(self: *XMLHttpRequest, shutdown: bool, session: *Session) void {
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
pub fn releaseRef(self: *XMLHttpRequest, session: *Session) void {
self._rc.release(self, session);
}
pub fn acquireRef(self: *XMLHttpRequest) void {
self._rc.acquire();
}
fn asEventTarget(self: *XMLHttpRequest) *EventTarget { fn asEventTarget(self: *XMLHttpRequest) *EventTarget {
return self._proto._proto; return self._proto._proto;
} }
@@ -245,8 +251,6 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void {
.error_callback = httpErrorCallback, .error_callback = httpErrorCallback,
.shutdown_callback = httpShutdownCallback, .shutdown_callback = httpShutdownCallback,
}); });
page.js.strongRef(self);
} }
fn handleBlobUrl(self: *XMLHttpRequest, page: *Page) !void { fn handleBlobUrl(self: *XMLHttpRequest, page: *Page) !void {
@@ -388,6 +392,7 @@ fn httpStartCallback(transfer: *HttpClient.Transfer) !void {
log.debug(.http, "request start", .{ .method = self._method, .url = self._url, .source = "xhr" }); log.debug(.http, "request start", .{ .method = self._method, .url = self._url, .source = "xhr" });
} }
self._transfer = transfer; self._transfer = transfer;
self.acquireRef();
} }
fn httpHeaderCallback(transfer: *HttpClient.Transfer, header: net_http.Header) !void { fn httpHeaderCallback(transfer: *HttpClient.Transfer, header: net_http.Header) !void {
@@ -486,15 +491,17 @@ fn httpDoneCallback(ctx: *anyopaque) !void {
.loaded = loaded, .loaded = loaded,
}, page); }, page);
page.js.weakRef(self); self.releaseRef(page._session);
} }
fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void { fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
const self: *XMLHttpRequest = @ptrCast(@alignCast(ctx)); const self: *XMLHttpRequest = @ptrCast(@alignCast(ctx));
// http client will close it after an error, it isn't safe to keep around // http client will close it after an error, it isn't safe to keep around
self._transfer = null;
self.handleError(err); self.handleError(err);
self._page.js.weakRef(self); if (self._transfer != null) {
self._transfer = null;
self.releaseRef(self._page._session);
}
} }
fn httpShutdownCallback(ctx: *anyopaque) void { fn httpShutdownCallback(ctx: *anyopaque) void {
@@ -505,10 +512,10 @@ fn httpShutdownCallback(ctx: *anyopaque) void {
pub fn abort(self: *XMLHttpRequest) void { pub fn abort(self: *XMLHttpRequest) void {
self.handleError(error.Abort); self.handleError(error.Abort);
if (self._transfer) |transfer| { if (self._transfer) |transfer| {
transfer.abort(error.Abort);
self._transfer = null; self._transfer = null;
transfer.abort(error.Abort);
self.releaseRef(self._page._session);
} }
self._page.js.weakRef(self);
} }
fn handleError(self: *XMLHttpRequest, err: anyerror) void { fn handleError(self: *XMLHttpRequest, err: anyerror) void {
@@ -582,8 +589,6 @@ pub const JsApi = struct {
pub const name = "XMLHttpRequest"; pub const name = "XMLHttpRequest";
pub const prototype_chain = bridge.prototypeChain(); pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined; pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(XMLHttpRequest.deinit);
}; };
pub const constructor = bridge.constructor(XMLHttpRequest.init, .{}); pub const constructor = bridge.constructor(XMLHttpRequest.init, .{});

View File

@@ -25,7 +25,6 @@ const json = std.json;
const Incrementing = @import("id.zig").Incrementing; const Incrementing = @import("id.zig").Incrementing;
const log = @import("../log.zig"); const log = @import("../log.zig");
const App = @import("../App.zig");
const Notification = @import("../Notification.zig"); const Notification = @import("../Notification.zig");
const Client = @import("../Server.zig").Client; const Client = @import("../Server.zig").Client;
@@ -35,7 +34,6 @@ const Browser = @import("../browser/Browser.zig");
const Session = @import("../browser/Session.zig"); const Session = @import("../browser/Session.zig");
const Page = @import("../browser/Page.zig"); const Page = @import("../browser/Page.zig");
const Mime = @import("../browser/Mime.zig"); const Mime = @import("../browser/Mime.zig");
const HttpClient = @import("../browser/HttpClient.zig");
const InterceptState = @import("domains/fetch.zig").InterceptState; const InterceptState = @import("domains/fetch.zig").InterceptState;

View File

@@ -18,7 +18,6 @@
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda"); const lp = @import("lightpanda");
const log = @import("../../log.zig");
const markdown = lp.markdown; const markdown = lp.markdown;
const SemanticTree = lp.SemanticTree; const SemanticTree = lp.SemanticTree;
const interactive = lp.interactive; const interactive = lp.interactive;

View File

@@ -24,9 +24,6 @@ const log = @import("../../log.zig");
const URL = @import("../../browser/URL.zig"); const URL = @import("../../browser/URL.zig");
const js = @import("../../browser/js/js.zig"); const js = @import("../../browser/js/js.zig");
// TODO: hard coded IDs
const LOADER_ID = "LOADERID42AA389647D702B4D805F49A";
pub fn processMessage(cmd: anytype) !void { pub fn processMessage(cmd: anytype) !void {
const action = std.meta.stringToEnum(enum { const action = std.meta.stringToEnum(enum {
getTargets, getTargets,

View File

@@ -17,7 +17,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const IS_DEBUG = @import("builtin").mode == .Debug;
pub fn toPageId(comptime id_type: enum { frame_id, loader_id }, input: []const u8) !u32 { pub fn toPageId(comptime id_type: enum { frame_id, loader_id }, input: []const u8) !u32 {
const err = switch (comptime id_type) { const err = switch (comptime id_type) {

View File

@@ -19,10 +19,6 @@
const std = @import("std"); const std = @import("std");
const json = std.json; const json = std.json;
const posix = std.posix; const posix = std.posix;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const Testing = @This();
const CDP = @import("CDP.zig"); const CDP = @import("CDP.zig");
const Server = @import("../Server.zig"); const Server = @import("../Server.zig");

View File

@@ -209,6 +209,37 @@ noinline fn assertionFailure(comptime ctx: []const u8, args: anytype) noreturn {
@import("crash_handler.zig").crash(ctx, args, @returnAddress()); @import("crash_handler.zig").crash(ctx, args, @returnAddress());
} }
// Reference counting helper
pub fn RC(comptime T: type) type {
return struct {
_refs: T = 0,
pub fn init(refs: T) @This() {
return .{ ._refs = refs };
}
pub fn acquire(self: *@This()) void {
self._refs += 1;
}
pub fn release(self: *@This(), value: anytype, session: *Session) void {
if (comptime IS_DEBUG) {
std.debug.assert(self._refs > 0);
}
const refs = self._refs - 1;
self._refs = refs;
if (refs > 0) {
return;
}
value.deinit(session);
if (session.finalizer_callbacks.fetchRemove(@intFromPtr(value))) |kv| {
session.releaseArena(kv.value.arena);
}
}
};
}
test { test {
std.testing.refAllDecls(@This()); std.testing.refAllDecls(@This());
} }

View File

@@ -49,9 +49,6 @@ const Opts = struct {
pub var opts = Opts{}; pub var opts = Opts{};
// synchronizes writes to the output
var out_lock: Thread.Mutex = .{};
// synchronizes access to last_log // synchronizes access to last_log
var last_log_lock: Thread.Mutex = .{}; var last_log_lock: Thread.Mutex = .{};

View File

@@ -110,5 +110,3 @@ pub fn handleRead(server: *Server, arena: std.mem.Allocator, req: protocol.Reque
return server.sendError(req_id, .InternalError, "Failed to serialize resource content"); return server.sendError(req_id, .InternalError, "Failed to serialize resource content");
}; };
} }
const testing = @import("../testing.zig");

View File

@@ -1,5 +1,4 @@
const std = @import("std"); const std = @import("std");
const lp = @import("lightpanda");
const protocol = @import("protocol.zig"); const protocol = @import("protocol.zig");
const resources = @import("resources.zig"); const resources = @import("resources.zig");
const Server = @import("Server.zig"); const Server = @import("Server.zig");

View File

@@ -4,9 +4,7 @@ const lp = @import("lightpanda");
const log = lp.log; const log = lp.log;
const js = lp.js; const js = lp.js;
const Element = @import("../browser/webapi/Element.zig");
const DOMNode = @import("../browser/webapi/Node.zig"); const DOMNode = @import("../browser/webapi/Node.zig");
const Selector = @import("../browser/webapi/selector/Selector.zig");
const protocol = @import("protocol.zig"); const protocol = @import("protocol.zig");
const Server = @import("Server.zig"); const Server = @import("Server.zig");
const CDPNode = @import("../cdp/Node.zig"); const CDPNode = @import("../cdp/Node.zig");

View File

@@ -17,10 +17,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const posix = std.posix;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const Config = @import("../Config.zig"); const Config = @import("../Config.zig");
const libcurl = @import("../sys/libcurl.zig"); const libcurl = @import("../sys/libcurl.zig");
@@ -29,18 +25,12 @@ const log = @import("lightpanda").log;
const assert = @import("lightpanda").assert; const assert = @import("lightpanda").assert;
pub const ENABLE_DEBUG = false; pub const ENABLE_DEBUG = false;
const IS_DEBUG = builtin.mode == .Debug;
pub const Blob = libcurl.CurlBlob; pub const Blob = libcurl.CurlBlob;
pub const WaitFd = libcurl.CurlWaitFd; pub const WaitFd = libcurl.CurlWaitFd;
pub const writefunc_error = libcurl.curl_writefunc_error; pub const writefunc_error = libcurl.curl_writefunc_error;
const Error = libcurl.Error; const Error = libcurl.Error;
const ErrorMulti = libcurl.ErrorMulti;
const errorFromCode = libcurl.errorFromCode;
const errorMFromCode = libcurl.errorMFromCode;
const errorCheck = libcurl.errorCheck;
const errorMCheck = libcurl.errorMCheck;
pub fn curl_version() [*c]const u8 { pub fn curl_version() [*c]const u8 {
return libcurl.curl_version(); return libcurl.curl_version();

View File

@@ -9,7 +9,6 @@ const App = @import("../App.zig");
const Config = @import("../Config.zig"); const Config = @import("../Config.zig");
const telemetry = @import("telemetry.zig"); const telemetry = @import("telemetry.zig");
const Runtime = @import("../network/Runtime.zig"); const Runtime = @import("../network/Runtime.zig");
const Connection = @import("../network/http.zig").Connection;
const URL = "https://telemetry.lightpanda.io"; const URL = "https://telemetry.lightpanda.io";
const BUFFER_SIZE = 1024; const BUFFER_SIZE = 1024;