mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-09-28 15:35:43 -04:00
Merge pull request #3195 from lightpanda-io/ce-reaction-shadowdom-pierce
webapi: Custom Element reactions need pierce the shadowdom
This commit is contained in:
8 files changed
+247
-48
No files matched your search
+29
-3
@@ -2497,6 +2497,12 @@ pub fn adoptNodeTree(self: *Frame, node: *Node, old_owner: *Document, new_owner:
|
||||
// Per spec, adopted steps run on each element after its document is set.
|
||||
if (node.is(Element)) |el| {
|
||||
Element.Html.Custom.enqueueAdoptedCallbackOnElement(el, old_owner, new_owner, self);
|
||||
|
||||
// The shadow tree follows its host across documents: re-own it and
|
||||
// run its adopted reactions too (spec: shadow-including descendants).
|
||||
if (el.hostedShadowRoot(self)) |shadow_root| {
|
||||
try self.adoptNodeTree(shadow_root.asNode(), old_owner, new_owner);
|
||||
}
|
||||
}
|
||||
|
||||
var it = node.childrenIterator();
|
||||
@@ -2632,6 +2638,9 @@ pub fn removeNode(self: *Frame, parent: *Node, child: *Node, opts: RemoveNodeOpt
|
||||
}
|
||||
|
||||
Element.Html.Custom.enqueueDisconnectedCallbackOnElement(el, self);
|
||||
Element.Html.Custom.enqueueShadowTreeCallbacks(el, .disconnected, self) catch |err| {
|
||||
log.warn(.bug, "ce_reactions enqueue fail", .{ .err = err });
|
||||
};
|
||||
|
||||
popover.removeFromOpen(el, self);
|
||||
|
||||
@@ -2704,19 +2713,31 @@ pub fn moveAllChildren(self: *Frame, source: *Node, parent: *Node, ref_node: ?*N
|
||||
// Every child shares source's root, and source itself doesn't move.
|
||||
const previous_root = source.getRootNode(.{});
|
||||
|
||||
// Fragment insertion adopts like single-node insertion does. Every child
|
||||
// shares source's owner document, so one comparison covers them all.
|
||||
const source_owner = source.ownerDocument(self);
|
||||
const parent_owner = parent.ownerDocument(self) orelse parent.as(Document);
|
||||
const adopting = source_owner != null and source_owner.? != parent_owner;
|
||||
|
||||
var it = source.childrenIterator();
|
||||
while (it.next()) |child| {
|
||||
try moved.append(self.call_arena, child);
|
||||
self.removeNode(source, child, .{ .reconnect_to = parent, .notify_observers = false });
|
||||
self.removeNode(source, child, .{
|
||||
.reconnect_to = if (adopting) null else parent,
|
||||
.notify_observers = false,
|
||||
});
|
||||
if (adopting) {
|
||||
try self.adoptNodeTree(child, source_owner.?, parent_owner);
|
||||
}
|
||||
if (ref_node) |ref| {
|
||||
try self.insertNodeRelative(
|
||||
parent,
|
||||
child,
|
||||
.{ .before = ref },
|
||||
.{ .previous_root = previous_root, .notify_observers = false, .run_ready = false },
|
||||
.{ .previous_root = previous_root, .adopting_to_new_document = adopting, .notify_observers = false, .run_ready = false },
|
||||
);
|
||||
} else {
|
||||
try self.appendNode(parent, child, .{ .previous_root = previous_root, .notify_observers = false, .run_ready = false });
|
||||
try self.appendNode(parent, child, .{ .previous_root = previous_root, .adopting_to_new_document = adopting, .notify_observers = false, .run_ready = false });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2906,6 +2927,7 @@ pub fn _insertNodeRelative(self: *Frame, comptime from_parser: bool, parent: *No
|
||||
|
||||
if (should_invoke_connected) {
|
||||
try Element.Html.Custom.enqueueConnectedCallbackOnElement(false, el, self);
|
||||
try Element.Html.Custom.enqueueShadowTreeCallbacks(el, .connected, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2977,11 +2999,15 @@ pub fn signalSlotChange(self: *Frame, slot: *Element.Html.Slot) void {
|
||||
}
|
||||
|
||||
pub fn getCustomizedBuiltInDefinition(self: *Frame, element: *Element) ?*CustomElementDefinition {
|
||||
if (!element._flags.customized_builtin) {
|
||||
return null;
|
||||
}
|
||||
return self._customized_builtin_definitions.get(element);
|
||||
}
|
||||
|
||||
pub fn setCustomizedBuiltInDefinition(self: *Frame, element: *Element, definition: *CustomElementDefinition) !void {
|
||||
try self._customized_builtin_definitions.put(self.arena, element, definition);
|
||||
element._flags.customized_builtin = true;
|
||||
}
|
||||
|
||||
// --- Live range update methods (DOM spec §4.2.3, §4.2.4, §4.7, §4.8) ---
|
||||
|
||||
@@ -138,7 +138,7 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
|
||||
switch (opts.shadow) {
|
||||
.skip => {},
|
||||
.complete, .rendered => {
|
||||
if (frame._element_shadow_roots.get(el)) |shadow| {
|
||||
if (el.hostedShadowRoot(frame)) |shadow| {
|
||||
try children(shadow.asNode(), opts, writer, frame);
|
||||
// In rendered mode, light DOM is only shown through slots, not directly
|
||||
if (opts.shadow == .rendered) {
|
||||
@@ -153,7 +153,7 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
|
||||
}
|
||||
},
|
||||
.declarative => |declarative| {
|
||||
if (frame._element_shadow_roots.get(el)) |shadow| {
|
||||
if (el.hostedShadowRoot(frame)) |shadow| {
|
||||
if (shouldSerializeShadow(shadow, declarative)) {
|
||||
try writeDeclarativeShadow(shadow, opts, writer, frame);
|
||||
}
|
||||
@@ -223,7 +223,7 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri
|
||||
pub fn getHTML(node: *Node, declarative: Opts.Shadow.Declarative, writer: *std.Io.Writer, frame: *Frame) !void {
|
||||
const opts = Opts{ .shadow = .{ .declarative = declarative } };
|
||||
if (node.is(Node.Element)) |el| {
|
||||
if (frame._element_shadow_roots.get(el)) |shadow| {
|
||||
if (el.hostedShadowRoot(frame)) |shadow| {
|
||||
if (shouldSerializeShadow(shadow, declarative)) {
|
||||
// if the element's shadowroot tree is rendered before its
|
||||
// children (assume the opts say that it should serialize the
|
||||
|
||||
@@ -59,7 +59,7 @@ fn htmlAsChildrenInner(frame: *Frame, node: *Node, html: []const u8, opts: Fragm
|
||||
slotting.assignSlottablesForTree(root, frame);
|
||||
}
|
||||
if (node.is(Element)) |el| {
|
||||
if (frame._element_shadow_roots.get(el)) |shadow_root| {
|
||||
if (el.hostedShadowRoot(frame)) |shadow_root| {
|
||||
slotting.assignSlottablesForTree(shadow_root.asNode(), frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,7 +439,7 @@ const Context = struct {
|
||||
// path (cf. dump.zig's default .rendered mode), so we always pierce; the
|
||||
// early-return tags above can never be valid shadow hosts, so only this
|
||||
// generic path needs the check.
|
||||
if (self.frame._element_shadow_roots.get(el)) |shadow| {
|
||||
if (el.hostedShadowRoot(self.frame)) |shadow| {
|
||||
try self.renderChildren(shadow.asNode());
|
||||
} else {
|
||||
try self.renderChildren(el.asNode());
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<body>
|
||||
<script src="../testing.js"></script>
|
||||
<script id="shadow_tree_callbacks">
|
||||
{
|
||||
let calls = [];
|
||||
|
||||
class MyElement extends HTMLElement {
|
||||
connectedCallback() { calls.push('connected'); }
|
||||
adoptedCallback(oldDoc, newDoc) {
|
||||
calls.push('adopted');
|
||||
calls.push(oldDoc === document);
|
||||
calls.push(newDoc === otherDoc);
|
||||
}
|
||||
disconnectedCallback() { calls.push('disconnected'); }
|
||||
}
|
||||
customElements.define('my-shadowed-element', MyElement);
|
||||
|
||||
const otherDoc = document.implementation.createHTMLDocument('other');
|
||||
|
||||
function makeHost(mode) {
|
||||
const host = document.createElement('div');
|
||||
const shadow = host.attachShadow({mode: mode});
|
||||
shadow.appendChild(document.createElement('my-shadowed-element'));
|
||||
return host;
|
||||
}
|
||||
|
||||
// Case 1: inserting a host fires connectedCallback inside its shadow tree.
|
||||
{
|
||||
const host = makeHost('open');
|
||||
calls = [];
|
||||
document.body.appendChild(host);
|
||||
testing.expectEqual('connected', calls.join(','));
|
||||
|
||||
// and removing it fires disconnectedCallback.
|
||||
calls = [];
|
||||
host.remove();
|
||||
testing.expectEqual('disconnected', calls.join(','));
|
||||
}
|
||||
|
||||
// Case 2: closed shadow trees get the callbacks too.
|
||||
{
|
||||
const host = makeHost('closed');
|
||||
calls = [];
|
||||
document.body.appendChild(host);
|
||||
host.remove();
|
||||
testing.expectEqual('connected,disconnected', calls.join(','));
|
||||
}
|
||||
|
||||
// Case 3: nested shadow trees (a host inside another host's shadow tree).
|
||||
{
|
||||
const outer = document.createElement('div');
|
||||
outer.attachShadow({mode: 'open'}).appendChild(makeHost('open'));
|
||||
calls = [];
|
||||
document.body.appendChild(outer);
|
||||
outer.remove();
|
||||
testing.expectEqual('connected,disconnected', calls.join(','));
|
||||
}
|
||||
|
||||
// Case 4: adopting a connected host into another document runs the shadow
|
||||
// tree's adopted (and disconnected/connected) reactions, and re-owns it.
|
||||
{
|
||||
const host = makeHost('open');
|
||||
const inner = host.shadowRoot.firstChild;
|
||||
document.body.appendChild(host);
|
||||
calls = [];
|
||||
otherDoc.body.appendChild(host);
|
||||
testing.expectEqual('disconnected,adopted,true,true,connected', calls.join(','));
|
||||
testing.expectEqual(otherDoc, inner.ownerDocument);
|
||||
}
|
||||
|
||||
// Case 5: inserting a shadow root (a DocumentFragment) into another
|
||||
// document moves and adopts its children.
|
||||
{
|
||||
const host = makeHost('open');
|
||||
const shadow = host.shadowRoot;
|
||||
document.body.appendChild(host);
|
||||
calls = [];
|
||||
otherDoc.body.appendChild(shadow);
|
||||
testing.expectEqual('disconnected,adopted,true,true,connected', calls.join(','));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
@@ -132,8 +132,19 @@ pub const Namespace = enum(u8) {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Flags = packed struct(u8) {
|
||||
shadow_host: bool = false,
|
||||
customized_builtin: bool = false,
|
||||
_unused: u6 = 0,
|
||||
};
|
||||
|
||||
_type: Type,
|
||||
_namespace: Namespace = .html,
|
||||
// Presence hints for the frame's element-keyed side tables: a set bit means
|
||||
// "maybe in the map" (the map stays the authority), a clear bit skips the
|
||||
// lookup. Turns the per-element map probe in tree walks into a bit test on
|
||||
// memory the walk already touches. Fits in existing struct padding.
|
||||
_flags: Flags = .{},
|
||||
_attributes: Attribute.List = .{},
|
||||
// In debug, set so that we can check that we have a proper contiguous block
|
||||
// of memory for the entire chain (and thus, simple pointer arithmetics will
|
||||
@@ -624,31 +635,6 @@ pub fn setDir(self: *Element, value: []const u8, frame: *Frame) !void {
|
||||
return self.setAttributeSafe(comptime .wrap("dir"), .wrap(value), frame);
|
||||
}
|
||||
|
||||
// ARIAMixin - ARIA attribute reflection
|
||||
pub fn getAriaAtomic(self: *const Element) ?[]const u8 {
|
||||
return self.getAttributeSafe(comptime .wrap("aria-atomic"));
|
||||
}
|
||||
|
||||
pub fn setAriaAtomic(self: *Element, value: ?[]const u8, frame: *Frame) !void {
|
||||
if (value) |v| {
|
||||
try self.setAttributeSafe(comptime .wrap("aria-atomic"), .wrap(v), frame);
|
||||
} else {
|
||||
try self.removeAttribute(comptime .wrap("aria-atomic"), frame);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getAriaLive(self: *const Element) ?[]const u8 {
|
||||
return self.getAttributeSafe(comptime .wrap("aria-live"));
|
||||
}
|
||||
|
||||
pub fn setAriaLive(self: *Element, value: ?[]const u8, frame: *Frame) !void {
|
||||
if (value) |v| {
|
||||
try self.setAttributeSafe(comptime .wrap("aria-live"), .wrap(v), frame);
|
||||
} else {
|
||||
try self.removeAttribute(comptime .wrap("aria-live"), frame);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getClassName(self: *const Element) []const u8 {
|
||||
return self.getAttributeSafe(comptime .wrap("class")) orelse "";
|
||||
}
|
||||
@@ -815,7 +801,7 @@ pub fn setAttributeSafe(self: *Element, name: String, value: String, frame: *Fra
|
||||
}
|
||||
|
||||
pub fn getShadowRoot(self: *Element, frame: *Frame) ?*ShadowRoot {
|
||||
const shadow_root = frame._element_shadow_roots.get(self) orelse return null;
|
||||
const shadow_root = self.hostedShadowRoot(frame) orelse return null;
|
||||
if (shadow_root._mode == .closed) return null;
|
||||
return shadow_root;
|
||||
}
|
||||
@@ -852,7 +838,7 @@ pub fn attachShadow(self: *Element, opts: ShadowRoot.AttachOptions, frame: *Fram
|
||||
}
|
||||
}
|
||||
|
||||
if (frame._element_shadow_roots.get(self)) |existing| {
|
||||
if (self.hostedShadowRoot(frame)) |existing| {
|
||||
// Imperative attachShadow over a declarative shadow root with a matching
|
||||
// mode empties it and returns the same root. The parser
|
||||
// (opts.declarative) never replaces an existing root.
|
||||
@@ -866,9 +852,20 @@ pub fn attachShadow(self: *Element, opts: ShadowRoot.AttachOptions, frame: *Fram
|
||||
|
||||
const shadow_root = try ShadowRoot.init(self, opts, frame);
|
||||
try frame._element_shadow_roots.put(frame.arena, self, shadow_root);
|
||||
self._flags.shadow_host = true;
|
||||
return shadow_root;
|
||||
}
|
||||
|
||||
// The shadow root this element hosts, closed ones included (the JS-facing
|
||||
// getShadowRoot filters those). The flag check skips the map probe for the
|
||||
// overwhelming majority of elements, which host nothing.
|
||||
pub fn hostedShadowRoot(self: *Element, frame: *const Frame) ?*ShadowRoot {
|
||||
if (!self._flags.shadow_host) {
|
||||
return null;
|
||||
}
|
||||
return frame._element_shadow_roots.get(self);
|
||||
}
|
||||
|
||||
pub fn insertAdjacentElement(
|
||||
self: *Element,
|
||||
position: []const u8,
|
||||
@@ -1789,7 +1786,7 @@ pub fn clone(self: *Element, deep: bool, frame: *Frame) !*Node {
|
||||
|
||||
// Per spec, a clonable shadow root is cloned along with its host — its
|
||||
// children always deep-cloned, even when the host clone is shallow.
|
||||
if (frame._element_shadow_roots.get(self)) |shadow| {
|
||||
if (self.hostedShadowRoot(frame)) |shadow| {
|
||||
if (shadow._clonable) {
|
||||
const cloned_shadow = node.as(Element).attachShadow(.{
|
||||
.mode = shadow._mode,
|
||||
@@ -2362,8 +2359,50 @@ pub const JsApi = struct {
|
||||
pub const localName = bridge.accessor(Element.getLocalName, null, .{});
|
||||
pub const id = bridge.accessor(Element.getId, Element.setId, .{ .ce_reactions = true });
|
||||
pub const slot = bridge.accessor(Element.getSlot, Element.setSlot, .{ .ce_reactions = true });
|
||||
pub const ariaAtomic = bridge.accessor(Element.getAriaAtomic, Element.setAriaAtomic, .{ .ce_reactions = true });
|
||||
pub const ariaLive = bridge.accessor(Element.getAriaLive, Element.setAriaLive, .{ .ce_reactions = true });
|
||||
pub const role = ariaAccessor("role");
|
||||
pub const ariaAtomic = ariaAccessor("aria-atomic");
|
||||
pub const ariaAutoComplete = ariaAccessor("aria-autocomplete");
|
||||
pub const ariaBrailleLabel = ariaAccessor("aria-braillelabel");
|
||||
pub const ariaBrailleRoleDescription = ariaAccessor("aria-brailleroledescription");
|
||||
pub const ariaBusy = ariaAccessor("aria-busy");
|
||||
pub const ariaChecked = ariaAccessor("aria-checked");
|
||||
pub const ariaColCount = ariaAccessor("aria-colcount");
|
||||
pub const ariaColIndex = ariaAccessor("aria-colindex");
|
||||
pub const ariaColIndexText = ariaAccessor("aria-colindextext");
|
||||
pub const ariaColSpan = ariaAccessor("aria-colspan");
|
||||
pub const ariaCurrent = ariaAccessor("aria-current");
|
||||
pub const ariaDescription = ariaAccessor("aria-description");
|
||||
pub const ariaDisabled = ariaAccessor("aria-disabled");
|
||||
pub const ariaExpanded = ariaAccessor("aria-expanded");
|
||||
pub const ariaHasPopup = ariaAccessor("aria-haspopup");
|
||||
pub const ariaHidden = ariaAccessor("aria-hidden");
|
||||
pub const ariaInvalid = ariaAccessor("aria-invalid");
|
||||
pub const ariaKeyShortcuts = ariaAccessor("aria-keyshortcuts");
|
||||
pub const ariaLabel = ariaAccessor("aria-label");
|
||||
pub const ariaLevel = ariaAccessor("aria-level");
|
||||
pub const ariaLive = ariaAccessor("aria-live");
|
||||
pub const ariaModal = ariaAccessor("aria-modal");
|
||||
pub const ariaMultiLine = ariaAccessor("aria-multiline");
|
||||
pub const ariaMultiSelectable = ariaAccessor("aria-multiselectable");
|
||||
pub const ariaOrientation = ariaAccessor("aria-orientation");
|
||||
pub const ariaPlaceholder = ariaAccessor("aria-placeholder");
|
||||
pub const ariaPosInSet = ariaAccessor("aria-posinset");
|
||||
pub const ariaPressed = ariaAccessor("aria-pressed");
|
||||
pub const ariaReadOnly = ariaAccessor("aria-readonly");
|
||||
pub const ariaRelevant = ariaAccessor("aria-relevant");
|
||||
pub const ariaRequired = ariaAccessor("aria-required");
|
||||
pub const ariaRoleDescription = ariaAccessor("aria-roledescription");
|
||||
pub const ariaRowCount = ariaAccessor("aria-rowcount");
|
||||
pub const ariaRowIndex = ariaAccessor("aria-rowindex");
|
||||
pub const ariaRowIndexText = ariaAccessor("aria-rowindextext");
|
||||
pub const ariaRowSpan = ariaAccessor("aria-rowspan");
|
||||
pub const ariaSelected = ariaAccessor("aria-selected");
|
||||
pub const ariaSetSize = ariaAccessor("aria-setsize");
|
||||
pub const ariaSort = ariaAccessor("aria-sort");
|
||||
pub const ariaValueMax = ariaAccessor("aria-valuemax");
|
||||
pub const ariaValueMin = ariaAccessor("aria-valuemin");
|
||||
pub const ariaValueNow = ariaAccessor("aria-valuenow");
|
||||
pub const ariaValueText = ariaAccessor("aria-valuetext");
|
||||
pub const dir = bridge.accessor(Element.getDir, Element.setDir, .{ .ce_reactions = true });
|
||||
pub const className = bridge.accessor(Element.getClassName, Element.setClassName, .{ .ce_reactions = true });
|
||||
pub const classList = bridge.accessor(Element.getClassList, Element.setClassList, .{ .ce_reactions = true });
|
||||
@@ -2463,6 +2502,23 @@ pub const JsApi = struct {
|
||||
pub const scroll = bridge.function(Element.scrollTo, .{});
|
||||
pub const scrollTo = bridge.function(Element.scrollTo, .{});
|
||||
pub const scrollBy = bridge.function(Element.scrollBy, .{});
|
||||
|
||||
fn ariaAccessor(comptime attr: []const u8) js.bridge.Accessor {
|
||||
const R = struct {
|
||||
pub fn get(self: *const Element) ?[]const u8 {
|
||||
return self.getAttributeSafe(.wrap(attr));
|
||||
}
|
||||
|
||||
pub fn set(self: *Element, value: ?[]const u8, frame: *Frame) !void {
|
||||
if (value) |v| {
|
||||
try self.setAttributeSafe(.wrap(attr), .wrap(v), frame);
|
||||
} else {
|
||||
try self.removeAttribute(.wrap(attr), frame);
|
||||
}
|
||||
}
|
||||
};
|
||||
return bridge.accessor(R.get, R.set, .{ .ce_reactions = true });
|
||||
}
|
||||
};
|
||||
|
||||
pub const Build = struct {
|
||||
@@ -2502,3 +2558,11 @@ const testing = @import("../../testing.zig");
|
||||
test "WebApi: Element" {
|
||||
try testing.htmlRunner("element", .{});
|
||||
}
|
||||
|
||||
test "Element: div chain slot size" {
|
||||
// Guard against accidental growth: new Element fields (e.g. _flags) must
|
||||
// fit in existing padding. Debug is larger from the _proto_canary fields.
|
||||
const Div = @import("element/html/Div.zig");
|
||||
const slot = comptime Factory.chainOffsetOf(Div, Div) + @sizeOf(Div);
|
||||
try testing.expectEqual(if (comptime lp.IS_DEBUG) 120 else 74, slot);
|
||||
}
|
||||
@@ -25,6 +25,7 @@ const Frame = @import("../../../Frame.zig");
|
||||
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const TreeWalker = @import("../../TreeWalker.zig");
|
||||
const Document = @import("../../Document.zig");
|
||||
const HtmlElement = @import("../Html.zig");
|
||||
const CustomElementDefinition = @import("../../CustomElementDefinition.zig");
|
||||
@@ -142,16 +143,40 @@ pub fn enqueueDisconnectedCallbackOnElement(element: *Element, frame: *Frame) vo
|
||||
|
||||
// Enqueues an atomic-move reaction (moveBefore). Unlike connect/disconnect there
|
||||
// is no dedup state to flip: a move always fires, and the element's connected
|
||||
// state is unchanged by the move.
|
||||
// state is unchanged by the move. The element's shadow tree (if any) always
|
||||
// moves with it.
|
||||
pub fn enqueueMoveCallbackOnElement(element: *Element, frame: *Frame) void {
|
||||
if (element.is(Custom)) |custom| {
|
||||
if (custom._definition == null) return;
|
||||
} else {
|
||||
if (frame.getCustomizedBuiltInDefinition(element) == null) return;
|
||||
const eligible = if (element.is(Custom)) |custom|
|
||||
custom._definition != null
|
||||
else
|
||||
frame.getCustomizedBuiltInDefinition(element) != null;
|
||||
|
||||
if (eligible) {
|
||||
frame._ce_reactions.enqueueMove(frame, element) catch |err| {
|
||||
log.warn(.bug, "ce_reactions enqueue fail", .{ .err = err });
|
||||
};
|
||||
}
|
||||
|
||||
const shadow_root = element.hostedShadowRoot(frame) orelse return;
|
||||
var tw = TreeWalker.FullExcludeSelf.Elements.init(shadow_root.asNode(), .{});
|
||||
while (tw.next()) |el| {
|
||||
enqueueMoveCallbackOnElement(el, frame);
|
||||
}
|
||||
}
|
||||
|
||||
// Reactions descend through the shadodom, so when an element is connected or
|
||||
// disconnected, we need to enqueue the connect/disconnect callback for any
|
||||
// nested element including those nested in a shadow root.
|
||||
pub fn enqueueShadowTreeCallbacks(host: *Element, comptime reaction: enum { connected, disconnected }, frame: *Frame) error{OutOfMemory}!void {
|
||||
const shadow_root = host.hostedShadowRoot(frame) orelse return;
|
||||
var tw = TreeWalker.FullExcludeSelf.Elements.init(shadow_root.asNode(), .{});
|
||||
while (tw.next()) |el| {
|
||||
switch (comptime reaction) {
|
||||
.connected => try enqueueConnectedCallbackOnElement(false, el, frame),
|
||||
.disconnected => enqueueDisconnectedCallbackOnElement(el, frame),
|
||||
}
|
||||
try enqueueShadowTreeCallbacks(el, reaction, frame);
|
||||
}
|
||||
frame._ce_reactions.enqueueMove(frame, element) catch |err| {
|
||||
log.warn(.bug, "ce_reactions enqueue fail", .{ .err = err });
|
||||
};
|
||||
}
|
||||
|
||||
pub fn enqueueAdoptedCallbackOnElement(element: *Element, old_document: *Document, new_document: *Document, frame: *Frame) void {
|
||||
|
||||
@@ -41,7 +41,7 @@ pub fn isSlottable(node: *Node) bool {
|
||||
pub fn findSlot(slottable: *Node, comptime open_only: bool, frame: *Frame) ?*Slot {
|
||||
const parent = slottable.parentElement() orelse return null;
|
||||
|
||||
const shadow_root = frame._element_shadow_roots.get(parent) orelse return null;
|
||||
const shadow_root = parent.hostedShadowRoot(frame) orelse return null;
|
||||
|
||||
if (open_only and shadow_root._mode != .open) {
|
||||
return null;
|
||||
@@ -172,7 +172,7 @@ fn subtreeHasSlot(node: *Node) bool {
|
||||
pub fn insertionSteps(parent: *Node, child: *Node, in_fragment_parse: bool, frame: *Frame) void {
|
||||
// The new child may be a slottable to assign in the parent's shadow tree.
|
||||
if (parent.is(Element)) |parent_el| {
|
||||
if (frame._element_shadow_roots.get(parent_el) != null and isSlottable(child)) {
|
||||
if (parent_el.hostedShadowRoot(frame) != null and isSlottable(child)) {
|
||||
assignASlot(child, frame);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user