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.
This commit is contained in:
Karl Seguin committed 2026-09-23 16:07:03 +08:00
1 parent 31198445dd
commit 0016fb0813
2 files changed
+19

No files matched your search

+14
View File
@@ -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.
+5
View File
@@ -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;