Compare commits

..

1 Commits

Author SHA1 Message Date
Karl Seguin
de0a04a58e Relax assertion on httpclient abort
It's ok to still have transfers, as long as whatever transfers still exists
are in an aborted state.
2026-04-02 17:59:17 +08:00
11 changed files with 97 additions and 147 deletions

View File

@@ -235,10 +235,6 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
} }
} }
if (comptime IS_DEBUG and abort_all) {
std.debug.assert(self.active == 0);
}
{ {
var q = &self.queue; var q = &self.queue;
var n = q.first; var n = q.first;
@@ -259,12 +255,16 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void {
} }
if (comptime IS_DEBUG and abort_all) { if (comptime IS_DEBUG and abort_all) {
std.debug.assert(self.in_use.first == null); // Even after an abort_all, we could still have transfers, but, at the
// very least, they should all be flagged as aborted.
const running = self.handles.perform() catch |err| { var it = self.in_use.first;
lp.assert(false, "multi perform in abort", .{ .err = err }); var leftover: usize = 0;
}; while (it) |node| : (it = node.next) {
std.debug.assert(running == 0); const conn: *http.Connection = @fieldParentPtr("node", node);
std.debug.assert((Transfer.fromConnection(conn) catch unreachable).aborted);
leftover += 1;
}
std.debug.assert(self.active == leftover);
} }
} }

View File

@@ -351,30 +351,6 @@ pub fn deinit(self: *Page, abort_http: bool) void {
session.releaseArena(qn.arena); session.releaseArena(qn.arena);
} }
{
// Release all objects we're referencing
{
var it = self._blob_urls.valueIterator();
while (it.next()) |blob| {
blob.*.releaseRef(session);
}
}
{
var it: ?*std.DoublyLinkedList.Node = self._mutation_observers.first;
while (it) |node| : (it = node.next) {
const observer: *MutationObserver = @fieldParentPtr("node", node);
observer.releaseRef(session);
}
}
for (self._intersection_observers.items) |observer| {
observer.releaseRef(session);
}
self.window._document._selection.releaseRef(session);
}
session.browser.env.destroyContext(self.js); session.browser.env.destroyContext(self.js);
self._script_manager.shutdown = true; self._script_manager.shutdown = true;
@@ -1362,24 +1338,20 @@ pub fn schedulePerformanceObserverDelivery(self: *Page) !void {
} }
pub fn registerMutationObserver(self: *Page, observer: *MutationObserver) !void { pub fn registerMutationObserver(self: *Page, observer: *MutationObserver) !void {
observer.acquireRef();
self._mutation_observers.append(&observer.node); self._mutation_observers.append(&observer.node);
} }
pub fn unregisterMutationObserver(self: *Page, observer: *MutationObserver) void { pub fn unregisterMutationObserver(self: *Page, observer: *MutationObserver) void {
observer.releaseRef(self._session);
self._mutation_observers.remove(&observer.node); self._mutation_observers.remove(&observer.node);
} }
pub fn registerIntersectionObserver(self: *Page, observer: *IntersectionObserver) !void { pub fn registerIntersectionObserver(self: *Page, observer: *IntersectionObserver) !void {
observer.acquireRef();
try self._intersection_observers.append(self.arena, observer); try self._intersection_observers.append(self.arena, observer);
} }
pub fn unregisterIntersectionObserver(self: *Page, observer: *IntersectionObserver) void { pub fn unregisterIntersectionObserver(self: *Page, observer: *IntersectionObserver) void {
for (self._intersection_observers.items, 0..) |obs, i| { for (self._intersection_observers.items, 0..) |obs, i| {
if (obs == observer) { if (obs == observer) {
observer.releaseRef(self._session);
_ = self._intersection_observers.swapRemove(i); _ = self._intersection_observers.swapRemove(i);
return; return;
} }

View File

@@ -501,11 +501,7 @@ pub const FinalizerCallback = struct {
session: *Session, session: *Session,
resolved_ptr_id: usize, resolved_ptr_id: usize,
finalizer_ptr_id: usize, finalizer_ptr_id: usize,
release_ref: *const fn (ptr_id: usize, session: *Session) void, _deinit: *const fn (ptr_id: usize, session: *Session) void,
// Track how many identities (JS worlds) reference this FC.
// Only cleanup when all identities have finalized.
identity_count: u8 = 0,
// For every FinalizerCallback we'll have 1+ FinalizerCallback.Identity: one // For every FinalizerCallback we'll have 1+ FinalizerCallback.Identity: one
// for every identity that gets the instance. In most cases, that'l be 1. // for every identity that gets the instance. In most cases, that'l be 1.
@@ -514,9 +510,8 @@ pub const FinalizerCallback = struct {
fc: *Session.FinalizerCallback, fc: *Session.FinalizerCallback,
}; };
// Called during page reset to force cleanup regardless of identity_count.
fn deinit(self: *FinalizerCallback, session: *Session) void { fn deinit(self: *FinalizerCallback, session: *Session) void {
self.release_ref(self.finalizer_ptr_id, session); self._deinit(self.finalizer_ptr_id, session);
session.releaseArena(self.arena); session.releaseArena(self.arena);
} }
}; };

View File

@@ -266,6 +266,7 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object,
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 (resolved.finalizer) |finalizer| { if (resolved.finalizer) |finalizer| {
const finalizer_ptr_id = finalizer.ptr_id; const finalizer_ptr_id = finalizer.ptr_id;
finalizer.acquireRef(finalizer_ptr_id);
const session = ctx.session; const session = ctx.session;
const finalizer_gop = try session.finalizer_callbacks.getOrPut(session.page_arena, finalizer_ptr_id); const finalizer_gop = try session.finalizer_callbacks.getOrPut(session.page_arena, finalizer_ptr_id);
@@ -274,8 +275,7 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object,
// see this Zig instance. We need to create the FinalizerCallback // see this Zig instance. We need to create the FinalizerCallback
// so that we can cleanup on page reset if v8 doesn't finalize. // so that we can cleanup on page reset if v8 doesn't finalize.
errdefer _ = session.finalizer_callbacks.remove(finalizer_ptr_id); errdefer _ = session.finalizer_callbacks.remove(finalizer_ptr_id);
finalizer.acquire_ref(finalizer_ptr_id); finalizer_gop.value_ptr.* = try self.createFinalizerCallback(resolved_ptr_id, finalizer_ptr_id, finalizer.deinit);
finalizer_gop.value_ptr.* = try self.createFinalizerCallback(resolved_ptr_id, finalizer_ptr_id, finalizer.release_ref_from_zig);
} }
const fc = finalizer_gop.value_ptr.*; const fc = finalizer_gop.value_ptr.*;
const identity_finalizer = try fc.arena.create(Session.FinalizerCallback.Identity); const identity_finalizer = try fc.arena.create(Session.FinalizerCallback.Identity);
@@ -283,9 +283,8 @@ pub fn mapZigInstanceToJs(self: *const Local, js_obj_handle: ?*const v8.Object,
.fc = fc, .fc = fc,
.identity = ctx.identity, .identity = ctx.identity,
}; };
fc.identity_count += 1;
v8.v8__Global__SetWeakFinalizer(gop.value_ptr, identity_finalizer, finalizer.release_ref, v8.kParameter); v8.v8__Global__SetWeakFinalizer(gop.value_ptr, identity_finalizer, finalizer.release, v8.kParameter);
} }
return js_obj; return js_obj;
}, },
@@ -1129,9 +1128,9 @@ const Resolved = struct {
// Resolved.ptr is the most specific value in a chain (e.g. IFrame, not EventTarget, Node, ...) // 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 // Finalizer.ptr_id is the most specific value in a chain that defines an acquireRef
ptr_id: usize, ptr_id: usize,
acquire_ref: *const fn (ptr_id: usize) void, deinit: *const fn (ptr_id: usize, session: *Session) void,
release_ref: *const fn (handle: ?*const v8.WeakCallbackInfo) callconv(.c) void, acquireRef: *const fn (ptr_id: usize) void,
release_ref_from_zig: *const fn (ptr_id: usize, session: *Session) void, release: *const fn (handle: ?*const v8.WeakCallbackInfo) callconv(.c) void,
}; };
}; };
pub fn resolveValue(value: anytype) Resolved { pub fn resolveValue(value: anytype) Resolved {
@@ -1171,49 +1170,32 @@ fn resolveT(comptime T: type, value: *T) Resolved {
const finalizer_ptr = getFinalizerPtr(value); const finalizer_ptr = getFinalizerPtr(value);
const Wrap = struct { const Wrap = struct {
fn deinit(ptr_id: usize, session: *Session) void {
FT.deinit(@ptrFromInt(ptr_id), session);
}
fn acquireRef(ptr_id: usize) void { fn acquireRef(ptr_id: usize) void {
FT.acquireRef(@ptrFromInt(ptr_id)); FT.acquireRef(@ptrFromInt(ptr_id));
} }
fn releaseRef(handle: ?*const v8.WeakCallbackInfo) callconv(.c) void { fn release(handle: ?*const v8.WeakCallbackInfo) callconv(.c) void {
const ptr = v8.v8__WeakCallbackInfo__GetParameter(handle.?).?; const ptr = v8.v8__WeakCallbackInfo__GetParameter(handle.?).?;
const identity_finalizer: *Session.FinalizerCallback.Identity = @ptrCast(@alignCast(ptr)); const identity_finalizer: *Session.FinalizerCallback.Identity = @ptrCast(@alignCast(ptr));
const fc = identity_finalizer.fc; const fc = identity_finalizer.fc;
const session = fc.session;
const finalizer_ptr_id = fc.finalizer_ptr_id;
// Remove from this identity's map
if (identity_finalizer.identity.identity_map.fetchRemove(fc.resolved_ptr_id)) |kv| { if (identity_finalizer.identity.identity_map.fetchRemove(fc.resolved_ptr_id)) |kv| {
var global = kv.value; var global = kv.value;
v8.v8__Global__Reset(&global); v8.v8__Global__Reset(&global);
} }
const identity_count = fc.identity_count; FT.releaseRef(@ptrFromInt(fc.finalizer_ptr_id), fc.session);
if (identity_count == 1) {
// All IsolatedWorlds that reference this object have
// released it. Release the instance ref, remove the
// FinalizerCallback and free it.
FT.releaseRef(@ptrFromInt(finalizer_ptr_id), session);
const removed = session.finalizer_callbacks.remove(finalizer_ptr_id);
if (comptime IS_DEBUG) {
std.debug.assert(removed);
}
session.releaseArena(fc.arena);
} else {
fc.identity_count = identity_count - 1;
}
}
fn releaseRefFromZig(ptr_id: usize, session: *Session) void {
FT.releaseRef(@ptrFromInt(ptr_id), session);
} }
}; };
break :blk .{ break :blk .{
.ptr_id = @intFromPtr(finalizer_ptr), .ptr_id = @intFromPtr(finalizer_ptr),
.acquire_ref = Wrap.acquireRef, .deinit = Wrap.deinit,
.release_ref = Wrap.releaseRef, .acquireRef = Wrap.acquireRef,
.release_ref_from_zig = Wrap.releaseRefFromZig, .release = Wrap.release,
}; };
}, },
}; };
@@ -1472,7 +1454,7 @@ fn createFinalizerCallback(
// The most specific value where finalizers are defined // The most specific value where finalizers are defined
// What actually gets acquired / released / deinit // What actually gets acquired / released / deinit
finalizer_ptr_id: usize, finalizer_ptr_id: usize,
release_ref: *const fn (ptr_id: usize, session: *Session) void, deinit: *const fn (ptr_id: usize, session: *Session) void,
) !*Session.FinalizerCallback { ) !*Session.FinalizerCallback {
const session = self.ctx.session; const session = self.ctx.session;
@@ -1483,7 +1465,7 @@ fn createFinalizerCallback(
fc.* = .{ fc.* = .{
.arena = arena, .arena = arena,
.session = session, .session = session,
.release_ref = release_ref, ._deinit = deinit,
.resolved_ptr_id = resolved_ptr_id, .resolved_ptr_id = resolved_ptr_id,
.finalizer_ptr_id = finalizer_ptr_id, .finalizer_ptr_id = finalizer_ptr_id,
}; };

View File

@@ -4,7 +4,7 @@
<div id=empty></div> <div id=empty></div>
<div id=one><p id=p10></p></div> <div id=one><p id=p10></p></div>
<!--<script id=childNodes> <script id=childNodes>
const div = $('#d1'); const div = $('#d1');
const children = div.childNodes; const children = div.childNodes;
testing.expectEqual(true, children instanceof NodeList); testing.expectEqual(true, children instanceof NodeList);
@@ -65,24 +65,24 @@
testing.expectEqual([], Array.from(empty.values())); testing.expectEqual([], Array.from(empty.values()));
testing.expectEqual([], Array.from(empty.entries())); testing.expectEqual([], Array.from(empty.entries()));
testing.expectEqual([], Array.from(empty)); testing.expectEqual([], Array.from(empty));
</script> --> </script>
<script id=one> <script id=one>
const one = $('#one').childNodes; const one = $('#one').childNodes;
// const p10 = $('#p10'); const p10 = $('#p10');
// testing.expectEqual(1, one.length); testing.expectEqual(1, one.length);
// testing.expectEqual(p10, one[0]); testing.expectEqual(p10, one[0]);
// testing.expectEqual([0], Array.from(one.keys())); testing.expectEqual([0], Array.from(one.keys()));
// testing.expectEqual([p10], Array.from(one.values())); testing.expectEqual([p10], Array.from(one.values()));
// testing.expectEqual([[0, p10]], Array.from(one.entries())); testing.expectEqual([[0, p10]], Array.from(one.entries()));
// testing.expectEqual([p10], Array.from(one)); testing.expectEqual([p10], Array.from(one));
let foreach = []; let foreach = [];
one.forEach((p) => foreach.push(p)); one.forEach((p) => foreach.push(p));
testing.expectEqual([p10], foreach); testing.expectEqual([p10], foreach);
</script> </script>
<!-- <script id=contains> <script id=contains>
testing.expectEqual(true, document.contains(document)); testing.expectEqual(true, document.contains(document));
testing.expectEqual(true, $('#d1').contains($('#d1'))); testing.expectEqual(true, $('#d1').contains($('#d1')));
testing.expectEqual(true, document.contains($('#d1'))); testing.expectEqual(true, document.contains($('#d1')));
@@ -94,4 +94,3 @@
testing.expectEqual(false, $('#d1').contains($('#empty'))); testing.expectEqual(false, $('#d1').contains($('#empty')));
testing.expectEqual(false, $('#d1').contains($('#p10'))); testing.expectEqual(false, $('#d1').contains($('#p10')));
</script> </script>
-->

View File

@@ -114,9 +114,7 @@ pub fn init(callback: js.Function.Temp, options: ?ObserverInit, page: *Page) !*I
pub fn deinit(self: *IntersectionObserver, session: *Session) void { pub fn deinit(self: *IntersectionObserver, session: *Session) void {
self._callback.release(); self._callback.release();
for (self._pending_entries.items) |entry| { for (self._pending_entries.items) |entry| {
// These were never handed to v8, they do not have a corresponding entry.deinitIfUnused(session);
// FinalizerCallback. We 100% own them.
entry.deinit(session);
} }
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
@@ -137,11 +135,14 @@ pub fn observe(self: *IntersectionObserver, target: *Element, page: *Page) !void
} }
} }
try self._observing.append(self._arena, target); // Register with page if this is our first observation
if (self._observing.items.len == 1) { if (self._observing.items.len == 0) {
self._rc._refs += 1;
try page.registerIntersectionObserver(self); try page.registerIntersectionObserver(self);
} }
try self._observing.append(self._arena, target);
// Don't initialize previous state yet - let checkIntersection do it // Don't initialize previous state yet - let checkIntersection do it
// This ensures we get an entry on first observation // This ensures we get an entry on first observation
@@ -165,7 +166,7 @@ pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) voi
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(page._session); entry.deinitIfUnused(page._session);
} else { } else {
j += 1; j += 1;
} }
@@ -175,21 +176,25 @@ pub fn unobserve(self: *IntersectionObserver, target: *Element, page: *Page) voi
} }
if (original_length > 0 and self._observing.items.len == 0) { if (original_length > 0 and self._observing.items.len == 0) {
page.unregisterIntersectionObserver(self); self._rc._refs -= 1;
} }
} }
pub fn disconnect(self: *IntersectionObserver, page: *Page) void { pub fn disconnect(self: *IntersectionObserver, page: *Page) void {
for (self._pending_entries.items) |entry| { for (self._pending_entries.items) |entry| {
entry.deinit(page._session); entry.deinitIfUnused(page._session);
} }
self._pending_entries.clearRetainingCapacity(); self._pending_entries.clearRetainingCapacity();
self._previous_states.clearRetainingCapacity(); self._previous_states.clearRetainingCapacity();
if (self._observing.items.len > 0) { const observing_count = self._observing.items.len;
page.unregisterIntersectionObserver(self);
}
self._observing.clearRetainingCapacity(); self._observing.clearRetainingCapacity();
page.unregisterIntersectionObserver(self);
if (observing_count > 0) {
_ = self.releaseRef(page._session);
}
} }
pub fn takeRecords(self: *IntersectionObserver, page: *Page) ![]*IntersectionObserverEntry { pub fn takeRecords(self: *IntersectionObserver, page: *Page) ![]*IntersectionObserverEntry {
@@ -335,6 +340,13 @@ pub const IntersectionObserverEntry = struct {
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 { pub fn releaseRef(self: *IntersectionObserverEntry, session: *Session) void {
self._rc.release(self, session); self._rc.release(self, session);
} }

View File

@@ -87,12 +87,8 @@ pub fn init(callback: js.Function.Temp, page: *Page) !*MutationObserver {
return self; return self;
} }
/// Force cleanup on Session shutdown.
pub fn deinit(self: *MutationObserver, session: *Session) void { pub fn deinit(self: *MutationObserver, session: *Session) void {
for (self._pending_records.items) |record| {
// These were never handed to v8, they do not have a corresponding
// FinalizerCallback. We 100% own them.
record.deinit(session);
}
self._callback.release(); self._callback.release();
session.releaseArena(self._arena); session.releaseArena(self._arena);
} }
@@ -167,14 +163,16 @@ pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions,
} }
} }
// Register with page if this is our first observation
if (self._observing.items.len == 0) {
self._rc._refs += 1;
try page.registerMutationObserver(self);
}
try self._observing.append(arena, .{ try self._observing.append(arena, .{
.target = target, .target = target,
.options = store_options, .options = store_options,
}); });
if (self._observing.items.len == 1) {
try page.registerMutationObserver(self);
}
} }
pub fn disconnect(self: *MutationObserver, page: *Page) void { pub fn disconnect(self: *MutationObserver, page: *Page) void {
@@ -182,11 +180,13 @@ pub fn disconnect(self: *MutationObserver, page: *Page) void {
_ = record.releaseRef(page._session); _ = record.releaseRef(page._session);
} }
self._pending_records.clearRetainingCapacity(); self._pending_records.clearRetainingCapacity();
const observing_count = self._observing.items.len;
if (self._observing.items.len > 0) {
page.unregisterMutationObserver(self);
}
self._observing.clearRetainingCapacity(); 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 {

View File

@@ -42,8 +42,8 @@ _rc: lp.RC(u32) = .{},
pub fn deinit(self: *NodeList, session: *Session) void { pub fn deinit(self: *NodeList, session: *Session) void {
switch (self._data) { switch (self._data) {
.child_nodes => |cn| cn.deinit(session),
.selector_list => |list| list.deinit(session), .selector_list => |list| list.deinit(session),
.child_nodes => |cn| cn.deinit(session),
else => {}, else => {},
} }
} }
@@ -92,12 +92,7 @@ pub fn entries(self: *NodeList, page: *Page) !*EntryIterator {
pub fn forEach(self: *NodeList, cb: js.Function, page: *Page) !void { pub fn forEach(self: *NodeList, cb: js.Function, page: *Page) !void {
var i: i32 = 0; var i: i32 = 0;
var it = try self.values(page); var it = try self.values(page);
// the iterator takes a reference against our list
defer self.releaseRef(page._session);
while (true) : (i += 1) { while (true) : (i += 1) {
const next = try it.next(page); const next = try it.next(page);
if (next.done) { if (next.done) {

View File

@@ -26,8 +26,7 @@ pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type {
const R = reflect(Inner, field); const R = reflect(Inner, field);
return struct { return struct {
_inner: Inner, inner: Inner,
_rc: lp.RC(u8) = .{},
const Self = @This(); const Self = @This();
@@ -39,31 +38,29 @@ pub fn Entry(comptime Inner: type, comptime field: ?[]const u8) type {
}; };
pub fn init(inner: Inner, page: *Page) !*Self { pub fn init(inner: Inner, page: *Page) !*Self {
const self = try page._factory.create(Self{ ._inner = inner }); return page._factory.create(Self{ .inner = inner });
if (@hasDecl(Inner, "acquireRef")) {
self._inner.acquireRef();
}
return self;
} }
pub fn deinit(self: *Self, session: *Session) void { pub fn deinit(self: *Self, session: *Session) void {
if (@hasDecl(Inner, "releaseRef")) { _ = self;
self._inner.releaseRef(session); _ = session;
}
session.factory.destroy(self);
} }
pub fn releaseRef(self: *Self, session: *Session) void { pub fn releaseRef(self: *Self, session: *Session) void {
self._rc.release(self, session); // Release the reference to the inner type that we acquired
if (@hasDecl(Inner, "releaseRef")) {
self.inner.releaseRef(session);
}
} }
pub fn acquireRef(self: *Self) void { pub fn acquireRef(self: *Self) void {
self._rc.acquire(); if (@hasDecl(Inner, "acquireRef")) {
self.inner.acquireRef();
}
} }
pub fn next(self: *Self, page: *Page) if (R.has_error_return) anyerror!Result else Result { pub fn next(self: *Self, page: *Page) if (R.has_error_return) anyerror!Result else Result {
const entry = (if (comptime R.has_error_return) try self._inner.next(page) else self._inner.next(page)) orelse { const entry = (if (comptime R.has_error_return) try self.inner.next(page) else self.inner.next(page)) orelse {
return .{ .done = true, .value = null }; return .{ .done = true, .value = null };
}; };

View File

@@ -90,13 +90,13 @@ 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);
const self = try 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 self; return xhr;
} }
pub fn deinit(self: *XMLHttpRequest, session: *Session) void { pub fn deinit(self: *XMLHttpRequest, session: *Session) void {
@@ -243,10 +243,7 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void {
try page.headersForRequest(&headers); try page.headersForRequest(&headers);
} }
self.acquireRef(); try http_client.request(.{
self._active_request = true;
http_client.request(.{
.ctx = self, .ctx = self,
.url = self._url, .url = self._url,
.method = self._method, .method = self._method,
@@ -263,10 +260,9 @@ pub fn send(self: *XMLHttpRequest, body_: ?[]const u8) !void {
.done_callback = httpDoneCallback, .done_callback = httpDoneCallback,
.error_callback = httpErrorCallback, .error_callback = httpErrorCallback,
.shutdown_callback = httpShutdownCallback, .shutdown_callback = httpShutdownCallback,
}) catch |err| { });
self.releaseSelfRef(); self.acquireRef();
return err; self._active_request = true;
};
} }
fn handleBlobUrl(self: *XMLHttpRequest, page: *Page) !void { fn handleBlobUrl(self: *XMLHttpRequest, page: *Page) !void {
@@ -522,7 +518,6 @@ fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void {
fn httpShutdownCallback(ctx: *anyopaque) void { fn httpShutdownCallback(ctx: *anyopaque) void {
const self: *XMLHttpRequest = @ptrCast(@alignCast(ctx)); const self: *XMLHttpRequest = @ptrCast(@alignCast(ctx));
self._transfer = null; self._transfer = null;
self.releaseSelfRef();
} }
pub fn abort(self: *XMLHttpRequest) void { pub fn abort(self: *XMLHttpRequest) void {

View File

@@ -259,6 +259,9 @@ pub fn RC(comptime T: type) type {
return; return;
} }
value.deinit(session); value.deinit(session);
if (session.finalizer_callbacks.fetchRemove(@intFromPtr(value))) |kv| {
session.releaseArena(kv.value.arena);
}
} }
pub fn format(self: @This(), writer: *std.Io.Writer) !void { pub fn format(self: @This(), writer: *std.Io.Writer) !void {