diff --git a/src/Inbox.zig b/src/Inbox.zig index 1828f0f6..0064d2aa 100644 --- a/src/Inbox.zig +++ b/src/Inbox.zig @@ -73,6 +73,21 @@ pub fn pop(self: *Inbox) ?*Message { return @fieldParentPtr("node", node); } +// Peek for a message matching `predicate` without removing it. Used by +// syncRequest to notice a queued teardown command (which sync_wait can't +// safely dispatch mid-parse) so it can abort the blocking fetch instead +// of stalling for the full per-request timeout. +pub fn contains(self: *Inbox, predicate: *const fn (*Message) bool) bool { + self.mutex.lock(); + defer self.mutex.unlock(); + var it = self.queue.first; + while (it) |node| : (it = node.next) { + const msg: *Message = @fieldParentPtr("node", node); + if (predicate(msg)) return true; + } + return false; +} + // Cherry-pick the first message for which `predicate(msg)` returns // true, removing it from the queue. Walks the queue in FIFO order; // non-matching messages stay in place. Used to dispatch only the diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index 884a5f88..ec08fab0 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -36,6 +36,7 @@ const CustomElementReactions = @import("CustomElementReactions.zig"); const URL = @import("URL.zig"); const Blob = @import("webapi/Blob.zig"); +const FileList = @import("webapi/FileList.zig"); const Node = @import("webapi/Node.zig"); const Event = @import("webapi/Event.zig"); const EventTarget = @import("webapi/EventTarget.zig"); @@ -57,9 +58,11 @@ const CSSStyleSheet = @import("webapi/css/CSSStyleSheet.zig"); const CustomElementDefinition = @import("webapi/CustomElementDefinition.zig"); const PageTransitionEvent = @import("webapi/event/PageTransitionEvent.zig"); const SubmitEvent = @import("webapi/event/SubmitEvent.zig"); +const popover = @import("webapi/element/popover.zig"); const NavigationKind = @import("webapi/navigation/root.zig").NavigationKind; const KeyboardEvent = @import("webapi/event/KeyboardEvent.zig"); const MouseEvent = @import("webapi/event/MouseEvent.zig"); +const WheelEvent = @import("webapi/event/WheelEvent.zig"); const HttpClient = @import("HttpClient.zig"); @@ -145,6 +148,10 @@ _event_target_attr_listeners: GlobalEventHandlersLookup = .empty, // Blob URL registry for URL.createObjectURL/revokeObjectURL _blob_urls: std.StringHashMapUnmanaged(*Blob) = .{}, +// FileLists owned by `` elements. Each holds refs on its +// File objects (reference counted via their Blob proto); released at teardown. +_file_lists: std.ArrayList(*FileList) = .{}, + /// `load` events that'll be fired before window's `load` event. /// A call to `documentIsComplete` (which calls `_documentIsComplete`) resets it. /// Double-buffered so that dispatching load events (which may trigger JS that @@ -403,6 +410,12 @@ pub fn deinit(self: *Frame) void { } } + for (self._file_lists.items) |file_list| { + for (file_list._files) |file| { + file._proto.releaseRef(page); + } + } + { var node: ?*std.DoublyLinkedList.Node = self._mutation_observers.first; while (node) |n| { @@ -887,7 +900,10 @@ pub fn abortTransfers(self: *Frame) void { for (self.child_frames.items) |child| { child.abortTransfers(); } - self._session.browser.http_client.abortOwner(&self._http_owner); + const http_client = &self._session.browser.http_client; + http_client.abortOwner(&self._http_owner); + // abortOwner misses deferred contexts whose transfer already completed. + http_client.deferring_layer.cancelFrame(self._frame_id); } pub fn documentIsLoaded(self: *Frame) void { @@ -1646,6 +1662,11 @@ pub fn registerIntersectionObserver(self: *Frame, observer: *IntersectionObserve try self._intersection_observers.append(self.arena, observer); } +// Tracks a file input's FileList so its File refs are released at teardown. +pub fn trackFileList(self: *Frame, file_list: *FileList) !void { + try self._file_lists.append(self.arena, file_list); +} + pub fn unregisterIntersectionObserver(self: *Frame, observer: *IntersectionObserver) void { for (self._intersection_observers.items, 0..) |obs, i| { if (obs == observer) { @@ -3044,24 +3065,11 @@ pub fn removeNode(self: *Frame, parent: *Node, child: *Node, opts: RemoveNodeOpt null; const children = parent._children.?; - switch (children.*) { - .one => |n| { - lp.assert(n == child, "Frame.removeNode.one", .{}); - parent._children = null; - self._factory.destroy(children); - }, - .list => |list| { - list.remove(&child._child_link); - - // Should not be possible to get a child list with a single node. - // While it doesn't cause any problems, it indicates an bug in the - // code as these should always be represented as .{.one = node} - const first = list.first.?; - if (first.next == null) { - children.* = .{ .one = Node.linkToNode(first) }; - self._factory.destroy(list); - } - }, + children.remove(&child._child_link); + if (children.first == null) { + // last child removed; drop the list so a childless node holds no allocation + parent._children = null; + self._factory.destroy(children); } // grab this before we null the parent const was_connected = child.isConnected(); @@ -3127,6 +3135,8 @@ pub fn removeNode(self: *Frame, parent: *Node, child: *Node, opts: RemoveNodeOpt Element.Html.Custom.enqueueDisconnectedCallbackOnElement(el, self); + popover.removeFromOpen(el, self); + // If a