Log on lack of Reaction scope in debug

Flag Range.cloneContents as CEReactions

setAttributeNode triggers single reaction, rather than a remove + add.
This commit is contained in:
Karl Seguin
2026-05-18 14:07:53 +08:00
parent a8f01ae253
commit 67a9aba79c
3 changed files with 7 additions and 3 deletions

View File

@@ -97,7 +97,7 @@ fn route(self: *Self, frame: *Frame, reaction: Reaction) !void {
return;
}
if (comptime IS_DEBUG) {
lp.log.err(.bug, "custom element scope", .{.note = "Missing explicit reaction scope, using fallback. This log is only generated in debug builds."});
lp.log.err(.bug, "custom element scope", .{ .note = "Missing explicit reaction scope, using fallback. This log is only generated in debug builds." });
}
try self.backup_queue.append(self.allocator, reaction);
if (!self.backup_scheduled) {

View File

@@ -720,7 +720,7 @@ pub const JsApi = struct {
pub const cloneRange = bridge.function(Range.cloneRange, .{ .dom_exception = true });
pub const insertNode = bridge.function(Range.insertNode, .{ .dom_exception = true, .ce_reactions = true });
pub const deleteContents = bridge.function(Range.deleteContents, .{ .dom_exception = true, .ce_reactions = true });
pub const cloneContents = bridge.function(Range.cloneContents, .{ .dom_exception = true });
pub const cloneContents = bridge.function(Range.cloneContents, .{ .dom_exception = true, .ce_reactions = true });
pub const extractContents = bridge.function(Range.extractContents, .{ .dom_exception = true, .ce_reactions = true });
pub const surroundContents = bridge.function(Range.surroundContents, .{ .dom_exception = true, .ce_reactions = true });
pub const createContextualFragment = bridge.function(Range.createContextualFragment, .{ .dom_exception = true, .ce_reactions = true });

View File

@@ -255,7 +255,11 @@ pub const List = struct {
const existing_attribute = try self.getAttribute(attribute._name, element, frame);
if (existing_attribute) |ea| {
try self.delete(ea._name, element, frame);
// Per DOM "replace an attribute": one handle-attribute-changes call
// with (old, new), not a remove-then-add that would fire two
// attributeChanged reactions. Detach the old wrapper; put() updates
// the entry in place and fires the single reaction.
ea._element = null;
}
const entry = try self.put(attribute._name, attribute._value, element, frame);