tweak custom element callbacks

This commit is contained in:
Karl Seguin
2025-11-22 12:25:12 +08:00
parent 357df22fab
commit 3c010f0e73
3 changed files with 54 additions and 4 deletions

View File

@@ -107,6 +107,8 @@ _intersection_delivery_scheduled: bool = false,
// Lookup for customized built-in elements. Maps element pointer to definition.
_customized_builtin_definitions: std.AutoHashMapUnmanaged(*Element, *CustomElementDefinition) = .{},
_customized_builtin_connected_callback_invoked: std.AutoHashMapUnmanaged(*Element, void) = .{},
_customized_builtin_disconnected_callback_invoked: std.AutoHashMapUnmanaged(*Element, void) = .{},
// This is set when an element is being upgraded (constructor is called).
// The constructor can access this to get the element being upgraded.
@@ -223,6 +225,8 @@ fn reset(self: *Page, comptime initializing: bool) !void {
self._intersection_observers = .{};
self._intersection_delivery_scheduled = false;
self._customized_builtin_definitions = .{};
self._customized_builtin_connected_callback_invoked = .{};
self._customized_builtin_disconnected_callback_invoked = .{};
self._undefined_custom_elements = .{};
try self.registerBackgroundTasks();
@@ -1380,13 +1384,14 @@ pub fn appendNode(self: *Page, parent: *Node, child: *Node, opts: InsertNodeOpts
pub fn appendAllChildren(self: *Page, parent: *Node, target: *Node) !void {
self.domChanged();
const is_connected = parent.isConnected();
const dest_connected = target.isConnected();
var it = parent.childrenIterator();
while (it.next()) |child| {
// Check if child was connected BEFORE removing it from parent
const child_was_connected = child.isConnected();
self.removeNode(parent, child, .{ .will_be_reconnected = dest_connected });
try self.appendNode(target, child, .{ .child_already_connected = is_connected });
try self.appendNode(target, child, .{ .child_already_connected = child_was_connected });
}
}
@@ -1500,14 +1505,19 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod
// 1. A disconnected child became connected (parent.isConnected() == true)
// 2. Child is being added to a shadow tree (parent_in_shadow == true)
// In both cases, we need to update ID maps and invoke callbacks
// Only invoke connectedCallback if the root child is transitioning from
// disconnected to connected. When that happens, all descendants should also
// get connectedCallback invoked (they're becoming connected as a group).
const should_invoke_connected = parent_is_connected and !opts.child_already_connected;
var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(child, .{});
while (tw.next()) |el| {
if (el.getAttributeSafe("id")) |id| {
try self.addElementId(el.asNode()._parent.?, el, id);
}
// Only invoke connected callback if actually connected to document
if (parent_is_connected) {
if (should_invoke_connected) {
Element.Html.Custom.invokeConnectedCallbackOnElement(el, self);
}
}

View File

@@ -136,6 +136,10 @@ fn upgradeElement(self: *CustomElementRegistry, element: *Element, page: *Page)
fn upgradeCustomElement(custom: *@import("element/html/Custom.zig"), definition: *CustomElementDefinition, page: *Page) !void {
custom._definition = definition;
// Reset callback flags since this is a fresh upgrade
custom._connected_callback_invoked = false;
custom._disconnected_callback_invoked = false;
const node = custom.asNode();
const prev_upgrading = page._upgrading_element;
page._upgrading_element = node;

View File

@@ -32,6 +32,8 @@ const Custom = @This();
_proto: *HtmlElement,
_tag_name: String,
_definition: ?*CustomElementDefinition,
_connected_callback_invoked: bool = false,
_disconnected_callback_invoked: bool = false,
pub fn asElement(self: *Custom) *Element {
return self._proto._proto;
@@ -41,10 +43,20 @@ pub fn asNode(self: *Custom) *Node {
}
pub fn invokeConnectedCallback(self: *Custom, page: *Page) void {
// Only invoke if we haven't already called it while connected
if (self._connected_callback_invoked) return;
self._connected_callback_invoked = true;
self._disconnected_callback_invoked = false;
self.invokeCallback("connectedCallback", .{}, page);
}
pub fn invokeDisconnectedCallback(self: *Custom, page: *Page) void {
// Only invoke if we haven't already called it while disconnected
if (self._disconnected_callback_invoked) return;
self._disconnected_callback_invoked = true;
self._connected_callback_invoked = false;
self.invokeCallback("disconnectedCallback", .{}, page);
}
@@ -63,6 +75,16 @@ pub fn invokeConnectedCallbackOnElement(element: *Element, page: *Page) void {
}
// Customized built-in element
// Check if we've already invoked connectedCallback while connected
if (page._customized_builtin_connected_callback_invoked.contains(element)) return;
page._customized_builtin_connected_callback_invoked.put(
page.arena,
element,
{},
) catch return;
_ = page._customized_builtin_disconnected_callback_invoked.remove(element);
invokeCallbackOnElement(element, "connectedCallback", .{}, page);
}
@@ -74,6 +96,16 @@ pub fn invokeDisconnectedCallbackOnElement(element: *Element, page: *Page) void
}
// Customized built-in element
// Check if we've already invoked disconnectedCallback while disconnected
if (page._customized_builtin_disconnected_callback_invoked.contains(element)) return;
page._customized_builtin_disconnected_callback_invoked.put(
page.arena,
element,
{},
) catch return;
_ = page._customized_builtin_connected_callback_invoked.remove(element);
invokeCallbackOnElement(element, "disconnectedCallback", .{}, page);
}
@@ -119,6 +151,10 @@ pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void {
// Attach the definition
try page.setCustomizedBuiltInDefinition(element, definition);
// Reset callback flags since this is a fresh upgrade
_ = page._customized_builtin_connected_callback_invoked.remove(element);
_ = page._customized_builtin_disconnected_callback_invoked.remove(element);
// Invoke constructor
const prev_upgrading = page._upgrading_element;
const node = element.asNode();