From ee432c54b8279a43b9834e3996d29e31dc544b63 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Mon, 29 Dec 2025 16:18:09 +0300 Subject: [PATCH 1/2] prefer `DoublyLinkedList` for storing `MutationObserver`s in `Page` --- src/browser/Page.zig | 43 ++++++++++++------------- src/browser/webapi/MutationObserver.zig | 3 ++ 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 498652fd..481ff0ff 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -98,7 +98,7 @@ _element_assigned_slots: Element.AssignedSlotLookup = .{}, _script_manager: ScriptManager, // List of active MutationObservers -_mutation_observers: std.ArrayList(*MutationObserver) = .{}, +_mutation_observers: std.DoublyLinkedList = .{}, _mutation_delivery_scheduled: bool = false, _mutation_delivery_depth: u32 = 0, @@ -1025,17 +1025,12 @@ pub fn getElementByIdFromNode(self: *Page, node: *Node, id: []const u8) ?*Elemen return null; } -pub fn registerMutationObserver(self: *Page, observer: *MutationObserver) !void { - try self._mutation_observers.append(self.arena, observer); +pub inline fn registerMutationObserver(self: *Page, observer: *MutationObserver) !void { + self._mutation_observers.append(&observer.node); } -pub fn unregisterMutationObserver(self: *Page, observer: *MutationObserver) void { - for (self._mutation_observers.items, 0..) |obs, i| { - if (obs == observer) { - _ = self._mutation_observers.swapRemove(i); - return; - } - } +pub inline fn unregisterMutationObserver(self: *Page, observer: *MutationObserver) void { + self._mutation_observers.remove(&observer.node); } pub fn registerIntersectionObserver(self: *Page, observer: *IntersectionObserver) !void { @@ -1126,11 +1121,9 @@ pub fn deliverMutations(self: *Page) void { return; } - // Iterate backwards to handle observers that disconnect during their callback - var i = self._mutation_observers.items.len; - while (i > 0) { - i -= 1; - const observer = self._mutation_observers.items[i]; + var it: ?*std.DoublyLinkedList.Node = self._mutation_observers.first; + while (it) |node| : (it = node.next) { + const observer: *MutationObserver = @fieldParentPtr("node", node); observer.deliverRecords(self) catch |err| { log.err(.page, "page.deliverMutations", .{ .err = err }); }; @@ -2048,7 +2041,9 @@ pub fn attributeChange(self: *Page, element: *Element, name: []const u8, value: Element.Html.Custom.invokeAttributeChangedCallbackOnElement(element, name, old_value, value, self); - for (self._mutation_observers.items) |observer| { + var it: ?*std.DoublyLinkedList.Node = self._mutation_observers.first; + while (it) |node| : (it = node.next) { + const observer: *MutationObserver = @fieldParentPtr("node", node); observer.notifyAttributeChange(element, name, old_value, self) catch |err| { log.err(.page, "attributeChange.notifyObserver", .{ .err = err }); }; @@ -2072,7 +2067,9 @@ pub fn attributeRemove(self: *Page, element: *Element, name: []const u8, old_val Element.Html.Custom.invokeAttributeChangedCallbackOnElement(element, name, old_value, null, self); - for (self._mutation_observers.items) |observer| { + var it: ?*std.DoublyLinkedList.Node = self._mutation_observers.first; + while (it) |node| : (it = node.next) { + const observer: *MutationObserver = @fieldParentPtr("node", node); observer.notifyAttributeChange(element, name, old_value, self) catch |err| { log.err(.page, "attributeRemove.notifyObserver", .{ .err = err }); }; @@ -2162,7 +2159,7 @@ fn findMatchingSlot(node: *Node, slot_name: []const u8) ?*Element.Html.Slot { } pub fn hasMutationObservers(self: *const Page) bool { - return self._mutation_observers.items.len > 0; + return self._mutation_observers.first != null; } pub fn getCustomizedBuiltInDefinition(self: *Page, element: *Element) ?*CustomElementDefinition { @@ -2178,8 +2175,9 @@ pub fn characterDataChange( target: *Node, old_value: []const u8, ) void { - // Notify mutation observers - for (self._mutation_observers.items) |observer| { + var it: ?*std.DoublyLinkedList.Node = self._mutation_observers.first; + while (it) |node| : (it = node.next) { + const observer: *MutationObserver = @fieldParentPtr("node", node); observer.notifyCharacterDataChange(target, old_value, self) catch |err| { log.err(.page, "cdataChange.notifyObserver", .{ .err = err }); }; @@ -2204,8 +2202,9 @@ pub fn childListChange( } } - // Notify mutation observers - for (self._mutation_observers.items) |observer| { + var it: ?*std.DoublyLinkedList.Node = self._mutation_observers.first; + while (it) |node| : (it = node.next) { + const observer: *MutationObserver = @fieldParentPtr("node", node); observer.notifyChildListChange(target, added_nodes, removed_nodes, previous_sibling, next_sibling, self) catch |err| { log.err(.page, "childListChange.notifyObserver", .{ .err = err }); }; diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig index 7d97a94b..e2af1504 100644 --- a/src/browser/webapi/MutationObserver.zig +++ b/src/browser/webapi/MutationObserver.zig @@ -34,6 +34,8 @@ const MutationObserver = @This(); _callback: js.Function, _observing: std.ArrayList(Observing) = .{}, _pending_records: std.ArrayList(*MutationRecord) = .{}, +/// Intrusively linked to next element (see Page.zig). +node: std.DoublyLinkedList.Node = .{}, const Observing = struct { target: *Node, @@ -53,6 +55,7 @@ pub const ObserveOptions = struct { pub fn init(callback: js.Function, page: *Page) !*MutationObserver { return page._factory.create(MutationObserver{ ._callback = callback, + .node = .{}, }); } From 43c30f8a34bc058262835c94b22e7cc2bca70244 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 30 Dec 2025 11:39:09 +0300 Subject: [PATCH 2/2] avoid `inline` + don't initialize `node` --- src/browser/Page.zig | 4 ++-- src/browser/webapi/MutationObserver.zig | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 481ff0ff..b7392d58 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -1025,11 +1025,11 @@ pub fn getElementByIdFromNode(self: *Page, node: *Node, id: []const u8) ?*Elemen return null; } -pub inline fn registerMutationObserver(self: *Page, observer: *MutationObserver) !void { +pub fn registerMutationObserver(self: *Page, observer: *MutationObserver) !void { self._mutation_observers.append(&observer.node); } -pub inline fn unregisterMutationObserver(self: *Page, observer: *MutationObserver) void { +pub fn unregisterMutationObserver(self: *Page, observer: *MutationObserver) void { self._mutation_observers.remove(&observer.node); } diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig index e2af1504..9e1acbdc 100644 --- a/src/browser/webapi/MutationObserver.zig +++ b/src/browser/webapi/MutationObserver.zig @@ -55,7 +55,6 @@ pub const ObserveOptions = struct { pub fn init(callback: js.Function, page: *Page) !*MutationObserver { return page._factory.create(MutationObserver{ ._callback = callback, - .node = .{}, }); }