From 0016fb0813155befd6be8bc48dcef76e5922153e Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 23 Sep 2026 16:07:03 +0800 Subject: [PATCH] mem: Track / Free abandoned html5ever parsers A parser created via document.open() is only freed when done/close is called (or some parse error happens). If close() isn't called, the parser is leaked. This has the Frame track documents that call open so that, on Frame.deinit(), it can free any parser still associated with the document. --- src/browser/Frame.zig | 14 ++++++++++++++ src/browser/webapi/Document.zig | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index 38e078594..237450ac3 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -133,6 +133,10 @@ _event_target_attr_listeners: GlobalEventHandlersLookup = .empty, // File objects (reference counted via their Blob proto); released at teardown. _file_lists: std.ArrayList(*FileList) = .empty, +// List of Documents which called document.open() and potentially need to have +// the parser freed. +_script_created_parser_docs: std.ArrayList(*Document) = .empty, + // Every matchMedia() result of this document, so a viewport change can fire // their `change`. _media_query_lists: std.ArrayList(*MediaQueryList) = .empty, @@ -456,6 +460,16 @@ pub fn deinit(self: *Frame) void { self._parse_state.deinit(self); + for (self._script_created_parser_docs.items) |doc| { + const parser = &(doc._script_created_parser orelse continue); + if (parser.parser.frame != self) { + // The document was closed and re-opened on another frame + continue; + } + parser.deinit(); + doc._script_created_parser = null; + } + // Unregister CookieStore from session notifications before the JS // context (and thus the scheduler) is destroyed, otherwise a late // mutation could schedule a callback that never runs. diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index 0a283a8e2..ee95c6d91 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -1157,6 +1157,11 @@ pub fn open(self: *Document, call_frame: *Frame) !*Document { // gone for good, as in Chrome. frame.cancelQueuedNavigation(); + if (std.mem.indexOfScalar(*Document, frame._script_created_parser_docs.items, self) == null) { + // have the page track this document (if it isn't already) + // so that, on shutdown, it can close the parser if needed. + try frame._script_created_parser_docs.append(frame.arena, self); + } self._script_created_parser = Parser.Streaming.init(frame.arena, doc_node, frame, .{ .allow_declarative_shadow = true }); try self._script_created_parser.?.start(); frame._parse_mode = .document;