@@ -32,6 +32,8 @@ const StyleManager = @import("StyleManager.zig");
3232const Parser = @import ("parser/Parser.zig" );
3333const h5e = @import ("parser/html5ever.zig" );
3434
35+ const CustomElementReactions = @import ("CustomElementReactions.zig" );
36+
3537const URL = @import ("URL.zig" );
3638const Blob = @import ("webapi/Blob.zig" );
3739const Node = @import ("webapi/Node.zig" );
@@ -188,6 +190,12 @@ _upgrading_element: ?*Node = null,
188190// List of custom elements that were created before their definition was registered
189191_undefined_custom_elements : std .ArrayList (* Element .Html .Custom ) = .{},
190192
193+ // Pending custom-element reactions (connected/disconnected/adopted/attribute
194+ // changed). Reactions are enqueued during DOM mutation and drained at the
195+ // outer algorithm boundary — set up by the JS bridge for [CEReactions]
196+ // methods and by the parser pump on each yield.
197+ _ce_reactions : CustomElementReactions ,
198+
191199// for heap allocations and managing WebAPI objects
192200_factory : * Factory ,
193201
@@ -287,6 +295,7 @@ pub fn init(self: *Frame, frame_id: u32, page: *Page, parent: ?*Frame) !void {
287295 ._type = if (parent == null ) .root else .frame ,
288296 ._style_manager = undefined ,
289297 ._script_manager = undefined ,
298+ ._ce_reactions = .{ .allocator = arena },
290299 ._event_manager = EventManager .init (arena , self ),
291300 };
292301 self ._to_load = & self ._to_load_1 ;
@@ -1852,7 +1861,7 @@ pub fn adoptNodeTree(self: *Frame, node: *Node, old_owner: *Document, new_owner:
18521861
18531862 // Per spec, adopted steps run on each element after its document is set.
18541863 if (node .is (Element )) | el | {
1855- Element .Html .Custom .invokeAdoptedCallbackOnElement (el , old_owner , new_owner , self );
1864+ Element .Html .Custom .enqueueAdoptedCallbackOnElement (el , old_owner , new_owner , self );
18561865 }
18571866
18581867 var it = node .childrenIterator ();
@@ -2588,7 +2597,7 @@ pub fn createElementNS(self: *Frame, namespace: Element.Namespace, name: []const
25882597 if (element ._attributes ) | attributes | {
25892598 var it = attributes .iterator ();
25902599 while (it .next ()) | attr | {
2591- Element .Html .Custom .invokeAttributeChangedCallbackOnElement (
2600+ Element .Html .Custom .enqueueAttributeChangedCallbackOnElement (
25922601 element ,
25932602 attr ._name ,
25942603 null , // old_value is null for initial attributes
@@ -2981,7 +2990,7 @@ pub fn removeNode(self: *Frame, parent: *Node, child: *Node, opts: RemoveNodeOpt
29812990 self .removeElementIdWithMaps (id_maps .? , id );
29822991 }
29832992
2984- Element .Html .Custom .invokeDisconnectedCallbackOnElement (el , self );
2993+ Element .Html .Custom .enqueueDisconnectedCallbackOnElement (el , self );
29852994
29862995 // If a <style> element is being removed, remove its sheet from the list
29872996 if (el .is (Element .Html .Style )) | style | {
@@ -3004,11 +3013,8 @@ pub fn appendAllChildren(self: *Frame, parent: *Node, target: *Node) !void {
30043013 self .domChanged ();
30053014 const dest_connected = target .isConnected ();
30063015
3007- // Use firstChild() instead of iterator to handle cases where callbacks
3008- // (like custom element connectedCallback) modify the parent during iteration.
3009- // The iterator captures "next" pointers that can become stale.
3010- while (parent .firstChild ()) | child | {
3011- // Check if child was connected BEFORE removing it from parent
3016+ var it = parent .childrenIterator ();
3017+ while (it .next ()) | child | {
30123018 const child_was_connected = child .isConnected ();
30133019 self .removeNode (parent , child , .{ .will_be_reconnected = dest_connected });
30143020 try self .appendNode (target , child , .{ .child_already_connected = child_was_connected });
@@ -3019,31 +3025,16 @@ pub fn insertAllChildrenBefore(self: *Frame, fragment: *Node, parent: *Node, ref
30193025 self .domChanged ();
30203026 const dest_connected = parent .isConnected ();
30213027
3022- // Use firstChild() instead of iterator to handle cases where callbacks
3023- // (like custom element connectedCallback) modify the fragment during iteration.
3024- // The iterator captures "next" pointers that can become stale.
3025- while (fragment .firstChild ()) | child | {
3026- // Check if child was connected BEFORE removing it from fragment
3028+ var it = fragment .childrenIterator ();
3029+ while (it .next ()) | child | {
30273030 const child_was_connected = child .isConnected ();
30283031 self .removeNode (fragment , child , .{ .will_be_reconnected = dest_connected });
3029- // A callback fired by a previous iteration's insert (e.g. a custom
3030- // element's connectedCallback) may have detached ref_node from
3031- // parent. In that case, fall back to append so the remaining
3032- // children still land in `parent` in source order.
3033- if (ref_node ._parent == parent ) {
3034- try self .insertNodeRelative (
3035- parent ,
3036- child ,
3037- .{ .before = ref_node },
3038- .{ .child_already_connected = child_was_connected },
3039- );
3040- } else {
3041- try self .appendNode (
3042- parent ,
3043- child ,
3044- .{ .child_already_connected = child_was_connected },
3045- );
3046- }
3032+ try self .insertNodeRelative (
3033+ parent ,
3034+ child ,
3035+ .{ .before = ref_node },
3036+ .{ .child_already_connected = child_was_connected },
3037+ );
30473038 }
30483039}
30493040
@@ -3167,7 +3158,7 @@ pub fn _insertNodeRelative(self: *Frame, comptime from_parser: bool, parent: *No
31673158 if (el .getAttributeSafe (comptime .wrap ("id" ))) | id | {
31683159 try self .addElementId (parent , el , id );
31693160 }
3170- try Element .Html .Custom .invokeConnectedCallbackOnElement (true , el , self );
3161+ try Element .Html .Custom .enqueueConnectedCallbackOnElement (true , el , self );
31713162 }
31723163 }
31733164 return ;
@@ -3213,7 +3204,7 @@ pub fn _insertNodeRelative(self: *Frame, comptime from_parser: bool, parent: *No
32133204 }
32143205
32153206 if (should_invoke_connected ) {
3216- try Element .Html .Custom .invokeConnectedCallbackOnElement (false , el , self );
3207+ try Element .Html .Custom .enqueueConnectedCallbackOnElement (false , el , self );
32173208 }
32183209 }
32193210}
@@ -3223,7 +3214,7 @@ pub fn attributeChange(self: *Frame, element: *Element, name: String, value: Str
32233214 log .err (.bug , "build.attributeChange" , .{ .tag = element .getTag (), .name = name , .value = value , .err = err , .type = self ._type , .url = self .url });
32243215 };
32253216
3226- Element .Html .Custom .invokeAttributeChangedCallbackOnElement (element , name , old_value , value , null , self );
3217+ Element .Html .Custom .enqueueAttributeChangedCallbackOnElement (element , name , old_value , value , null , self );
32273218
32283219 var it : ? * std.DoublyLinkedList.Node = self ._mutation_observers .first ;
32293220 while (it ) | node | : (it = node .next ) {
@@ -3249,7 +3240,7 @@ pub fn attributeRemove(self: *Frame, element: *Element, name: String, old_value:
32493240 log .err (.bug , "build.attributeRemove" , .{ .tag = element .getTag (), .name = name , .err = err , .type = self ._type , .url = self .url });
32503241 };
32513242
3252- Element .Html .Custom .invokeAttributeChangedCallbackOnElement (element , name , old_value , null , null , self );
3243+ Element .Html .Custom .enqueueAttributeChangedCallbackOnElement (element , name , old_value , null , null , self );
32533244
32543245 var it : ? * std.DoublyLinkedList.Node = self ._mutation_observers .first ;
32553246 while (it ) | node | : (it = node .next ) {
0 commit comments