script/dom/
shadowroot.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::collections::HashMap;
7use std::collections::hash_map::Entry;
8
9use dom_struct::dom_struct;
10use html5ever::serialize::TraversalScope;
11use js::rust::{HandleValue, MutableHandleValue};
12use script_bindings::error::{ErrorResult, Fallible};
13use script_bindings::script_runtime::JSContext;
14use servo_arc::Arc;
15use style::author_styles::AuthorStyles;
16use style::dom::TElement;
17use style::invalidation::element::restyle_hints::RestyleHint;
18use style::shared_lock::SharedRwLockReadGuard;
19use style::stylesheets::Stylesheet;
20use style::stylist::{CascadeData, Stylist};
21use stylo_atoms::Atom;
22
23use crate::conversions::Convert;
24use crate::dom::bindings::cell::{DomRefCell, RefMut};
25use crate::dom::bindings::codegen::Bindings::ElementBinding::GetHTMLOptions;
26use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::HTMLSlotElement_Binding::HTMLSlotElementMethods;
27use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
28use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
29    ShadowRootMode, SlotAssignmentMode,
30};
31use crate::dom::bindings::codegen::UnionTypes::{
32    TrustedHTMLOrNullIsEmptyString, TrustedHTMLOrString,
33};
34use crate::dom::bindings::frozenarray::CachedFrozenArray;
35use crate::dom::bindings::inheritance::Castable;
36use crate::dom::bindings::num::Finite;
37use crate::dom::bindings::reflector::reflect_dom_object;
38use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom, ToLayout};
39use crate::dom::bindings::str::DOMString;
40use crate::dom::css::cssstylesheet::CSSStyleSheet;
41use crate::dom::css::stylesheetlist::{StyleSheetList, StyleSheetListOwner};
42use crate::dom::document::Document;
43use crate::dom::documentfragment::DocumentFragment;
44use crate::dom::documentorshadowroot::{
45    DocumentOrShadowRoot, ServoStylesheetInDocument, StylesheetSource,
46};
47use crate::dom::element::Element;
48use crate::dom::html::htmlslotelement::HTMLSlotElement;
49use crate::dom::htmldetailselement::DetailsNameGroups;
50use crate::dom::node::{
51    BindContext, IsShadowTree, Node, NodeDamage, NodeFlags, NodeTraits, ShadowIncluding,
52    UnbindContext, VecPreOrderInsertionHelper,
53};
54use crate::dom::trustedhtml::TrustedHTML;
55use crate::dom::types::EventTarget;
56use crate::dom::virtualmethods::{VirtualMethods, vtable_for};
57use crate::dom::window::Window;
58use crate::script_runtime::CanGc;
59use crate::stylesheet_set::StylesheetSetRef;
60
61/// Whether a shadow root hosts an User Agent widget.
62#[derive(JSTraceable, MallocSizeOf, PartialEq)]
63pub(crate) enum IsUserAgentWidget {
64    No,
65    Yes,
66}
67
68/// <https://dom.spec.whatwg.org/#interface-shadowroot>
69#[dom_struct]
70pub(crate) struct ShadowRoot {
71    document_fragment: DocumentFragment,
72    document_or_shadow_root: DocumentOrShadowRoot,
73    document: Dom<Document>,
74    host: Dom<Element>,
75    /// List of author styles associated with nodes in this shadow tree.
76    #[custom_trace]
77    author_styles: DomRefCell<AuthorStyles<ServoStylesheetInDocument>>,
78    stylesheet_list: MutNullableDom<StyleSheetList>,
79    window: Dom<Window>,
80
81    /// <https://dom.spec.whatwg.org/#dom-shadowroot-mode>
82    mode: ShadowRootMode,
83
84    /// <https://dom.spec.whatwg.org/#dom-shadowroot-slotassignment>
85    slot_assignment_mode: SlotAssignmentMode,
86
87    /// <https://dom.spec.whatwg.org/#dom-shadowroot-clonable>
88    clonable: bool,
89
90    /// <https://dom.spec.whatwg.org/#shadowroot-available-to-element-internals>
91    available_to_element_internals: Cell<bool>,
92
93    slots: DomRefCell<HashMap<DOMString, Vec<Dom<HTMLSlotElement>>>>,
94
95    is_user_agent_widget: bool,
96
97    /// <https://dom.spec.whatwg.org/#shadowroot-declarative>
98    declarative: Cell<bool>,
99
100    /// <https://dom.spec.whatwg.org/#shadowroot-serializable>
101    serializable: Cell<bool>,
102
103    /// <https://dom.spec.whatwg.org/#shadowroot-delegates-focus>
104    delegates_focus: Cell<bool>,
105
106    /// The constructed stylesheet that is adopted by this [ShadowRoot].
107    /// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
108    adopted_stylesheets: DomRefCell<Vec<Dom<CSSStyleSheet>>>,
109
110    /// Cached frozen array of [`Self::adopted_stylesheets`]
111    #[ignore_malloc_size_of = "mozjs"]
112    adopted_stylesheets_frozen_types: CachedFrozenArray,
113
114    details_name_groups: DomRefCell<Option<DetailsNameGroups>>,
115}
116
117impl ShadowRoot {
118    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
119    fn new_inherited(
120        host: &Element,
121        document: &Document,
122        mode: ShadowRootMode,
123        slot_assignment_mode: SlotAssignmentMode,
124        clonable: bool,
125        is_user_agent_widget: IsUserAgentWidget,
126    ) -> ShadowRoot {
127        let document_fragment = DocumentFragment::new_inherited(document);
128        let node = document_fragment.upcast::<Node>();
129        node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, true);
130        node.set_flag(
131            NodeFlags::IS_CONNECTED,
132            host.upcast::<Node>().is_connected(),
133        );
134
135        ShadowRoot {
136            document_fragment,
137            document_or_shadow_root: DocumentOrShadowRoot::new(document.window()),
138            document: Dom::from_ref(document),
139            host: Dom::from_ref(host),
140            author_styles: DomRefCell::new(AuthorStyles::new()),
141            stylesheet_list: MutNullableDom::new(None),
142            window: Dom::from_ref(document.window()),
143            mode,
144            slot_assignment_mode,
145            clonable,
146            available_to_element_internals: Cell::new(false),
147            slots: Default::default(),
148            is_user_agent_widget: is_user_agent_widget == IsUserAgentWidget::Yes,
149            declarative: Cell::new(false),
150            serializable: Cell::new(false),
151            delegates_focus: Cell::new(false),
152            adopted_stylesheets: Default::default(),
153            adopted_stylesheets_frozen_types: CachedFrozenArray::new(),
154            details_name_groups: Default::default(),
155        }
156    }
157
158    pub(crate) fn new(
159        host: &Element,
160        document: &Document,
161        mode: ShadowRootMode,
162        slot_assignment_mode: SlotAssignmentMode,
163        clonable: bool,
164        is_user_agent_widget: IsUserAgentWidget,
165        can_gc: CanGc,
166    ) -> DomRoot<ShadowRoot> {
167        reflect_dom_object(
168            Box::new(ShadowRoot::new_inherited(
169                host,
170                document,
171                mode,
172                slot_assignment_mode,
173                clonable,
174                is_user_agent_widget,
175            )),
176            document.window(),
177            can_gc,
178        )
179    }
180
181    pub(crate) fn owner_doc(&self) -> &Document {
182        &self.document
183    }
184
185    pub(crate) fn get_focused_element(&self) -> Option<DomRoot<Element>> {
186        // XXX get retargeted focused element
187        None
188    }
189
190    pub(crate) fn stylesheet_count(&self) -> usize {
191        self.author_styles.borrow().stylesheets.len()
192    }
193
194    pub(crate) fn stylesheet_at(&self, index: usize) -> Option<DomRoot<CSSStyleSheet>> {
195        let stylesheets = &self.author_styles.borrow().stylesheets;
196
197        stylesheets
198            .get(index)
199            .and_then(|s| s.owner.get_cssom_object())
200    }
201
202    /// Add a stylesheet owned by `owner_node` to the list of shadow root sheets, in the
203    /// correct tree position. Additionally, ensure that owned stylesheet is inserted before
204    /// any constructed stylesheet.
205    ///
206    /// <https://drafts.csswg.org/cssom/#documentorshadowroot-final-css-style-sheets>
207    #[cfg_attr(crown, expect(crown::unrooted_must_root))] // Owner needs to be rooted already necessarily.
208    pub(crate) fn add_owned_stylesheet(&self, owner_node: &Element, sheet: Arc<Stylesheet>) {
209        let stylesheets = &mut self.author_styles.borrow_mut().stylesheets;
210
211        // FIXME(stevennovaryo): This is almost identical with the one in Document::add_stylesheet.
212        let insertion_point = stylesheets
213            .iter()
214            .find(|sheet_in_shadow| {
215                match &sheet_in_shadow.owner {
216                    StylesheetSource::Element(other_node) => {
217                        owner_node.upcast::<Node>().is_before(other_node.upcast())
218                    },
219                    // Non-constructed stylesheet should be ordered before the
220                    // constructed ones.
221                    StylesheetSource::Constructed(_) => true,
222                }
223            })
224            .cloned();
225
226        if self.document.has_browsing_context() {
227            let document_context = self.window.web_font_context();
228            self.window
229                .layout_mut()
230                .load_web_fonts_from_stylesheet(&sheet, &document_context);
231        }
232
233        DocumentOrShadowRoot::add_stylesheet(
234            StylesheetSource::Element(Dom::from_ref(owner_node)),
235            StylesheetSetRef::Author(stylesheets),
236            sheet,
237            insertion_point,
238            self.document.style_shared_lock(),
239        );
240    }
241
242    /// Append a constructed stylesheet to the back of shadow root stylesheet set.
243    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
244    pub(crate) fn append_constructed_stylesheet(&self, cssom_stylesheet: &CSSStyleSheet) {
245        debug_assert!(cssom_stylesheet.is_constructed());
246
247        let stylesheets = &mut self.author_styles.borrow_mut().stylesheets;
248        let sheet = cssom_stylesheet.style_stylesheet().clone();
249
250        let insertion_point = stylesheets.iter().last().cloned();
251
252        if self.document.has_browsing_context() {
253            let document_context = self.window.web_font_context();
254            self.window
255                .layout_mut()
256                .load_web_fonts_from_stylesheet(&sheet, &document_context);
257        }
258
259        DocumentOrShadowRoot::add_stylesheet(
260            StylesheetSource::Constructed(Dom::from_ref(cssom_stylesheet)),
261            StylesheetSetRef::Author(stylesheets),
262            sheet,
263            insertion_point,
264            self.document.style_shared_lock(),
265        );
266    }
267
268    /// Remove a stylesheet owned by `owner` from the list of shadow root sheets.
269    #[cfg_attr(crown, expect(crown::unrooted_must_root))] // Owner needs to be rooted already necessarily.
270    pub(crate) fn remove_stylesheet(&self, owner: StylesheetSource, s: &Arc<Stylesheet>) {
271        DocumentOrShadowRoot::remove_stylesheet(
272            owner,
273            s,
274            StylesheetSetRef::Author(&mut self.author_styles.borrow_mut().stylesheets),
275        )
276    }
277
278    pub(crate) fn invalidate_stylesheets(&self) {
279        self.document.invalidate_shadow_roots_stylesheets();
280        self.author_styles.borrow_mut().stylesheets.force_dirty();
281        // Mark the host element dirty so a reflow will be performed.
282        self.host.upcast::<Node>().dirty(NodeDamage::Style);
283
284        // Also mark the host element with `RestyleHint::restyle_subtree` so a reflow
285        // can traverse into the shadow tree.
286        let mut restyle = self.document.ensure_pending_restyle(&self.host);
287        restyle.hint.insert(RestyleHint::restyle_subtree());
288    }
289
290    /// Remove any existing association between the provided id and any elements
291    /// in this shadow tree.
292    pub(crate) fn unregister_element_id(&self, to_unregister: &Element, id: Atom, _can_gc: CanGc) {
293        self.document_or_shadow_root.unregister_named_element(
294            self.document_fragment.id_map(),
295            to_unregister,
296            &id,
297        );
298    }
299
300    /// Associate an element present in this shadow tree with the provided id.
301    pub(crate) fn register_element_id(&self, element: &Element, id: Atom, _can_gc: CanGc) {
302        let root = self
303            .upcast::<Node>()
304            .inclusive_ancestors(ShadowIncluding::No)
305            .last()
306            .unwrap();
307        self.document_or_shadow_root.register_named_element(
308            self.document_fragment.id_map(),
309            element,
310            &id,
311            root,
312        );
313    }
314
315    pub(crate) fn register_slot(&self, slot: &HTMLSlotElement) {
316        debug!("Registering slot with name={:?}", slot.Name().str());
317
318        let mut slots = self.slots.borrow_mut();
319
320        let slots_with_the_same_name = slots.entry(slot.Name()).or_default();
321
322        // Insert the slot before the first element that comes after it in tree order
323        slots_with_the_same_name.insert_pre_order(slot, self.upcast::<Node>());
324    }
325
326    pub(crate) fn unregister_slot(&self, name: DOMString, slot: &HTMLSlotElement) {
327        debug!("Unregistering slot with name={:?}", name.str());
328
329        let mut slots = self.slots.borrow_mut();
330        let Entry::Occupied(mut entry) = slots.entry(name) else {
331            panic!("slot is not registered");
332        };
333        entry.get_mut().retain(|s| slot != &**s);
334    }
335
336    /// Find the first slot with the given name among this root's descendants in tree order
337    pub(crate) fn slot_for_name(&self, name: &DOMString) -> Option<DomRoot<HTMLSlotElement>> {
338        self.slots
339            .borrow()
340            .get(name)
341            .and_then(|slots| slots.first())
342            .map(|slot| slot.as_rooted())
343    }
344
345    pub(crate) fn has_slot_descendants(&self) -> bool {
346        !self.slots.borrow().is_empty()
347    }
348
349    pub(crate) fn set_available_to_element_internals(&self, value: bool) {
350        self.available_to_element_internals.set(value);
351    }
352
353    /// <https://dom.spec.whatwg.org/#shadowroot-available-to-element-internals>
354    pub(crate) fn is_available_to_element_internals(&self) -> bool {
355        self.available_to_element_internals.get()
356    }
357
358    pub(crate) fn is_user_agent_widget(&self) -> bool {
359        self.is_user_agent_widget
360    }
361
362    pub(crate) fn set_declarative(&self, declarative: bool) {
363        self.declarative.set(declarative);
364    }
365
366    pub(crate) fn is_declarative(&self) -> bool {
367        self.declarative.get()
368    }
369
370    pub(crate) fn shadow_root_mode(&self) -> ShadowRootMode {
371        self.mode
372    }
373
374    pub(crate) fn set_serializable(&self, serializable: bool) {
375        self.serializable.set(serializable);
376    }
377
378    pub(crate) fn set_delegates_focus(&self, delegates_focus: bool) {
379        self.delegates_focus.set(delegates_focus);
380    }
381
382    pub(crate) fn details_name_groups(&self) -> RefMut<'_, DetailsNameGroups> {
383        RefMut::map(
384            self.details_name_groups.borrow_mut(),
385            |details_name_groups| details_name_groups.get_or_insert_default(),
386        )
387    }
388}
389
390impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {
391    /// <https://html.spec.whatwg.org/multipage/#dom-document-activeelement>
392    fn GetActiveElement(&self) -> Option<DomRoot<Element>> {
393        self.document_or_shadow_root
394            .get_active_element(self.get_focused_element(), None, None)
395    }
396
397    /// <https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint>
398    fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<DomRoot<Element>> {
399        // Return the result of running the retargeting algorithm with context object
400        // and the original result as input.
401        match self.document_or_shadow_root.element_from_point(
402            x,
403            y,
404            None,
405            self.document.has_browsing_context(),
406        ) {
407            Some(e) => {
408                let retargeted_node = e.upcast::<EventTarget>().retarget(self.upcast());
409                retargeted_node.downcast::<Element>().map(DomRoot::from_ref)
410            },
411            None => None,
412        }
413    }
414
415    /// <https://drafts.csswg.org/cssom-view/#dom-document-elementsfrompoint>
416    fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<DomRoot<Element>> {
417        // Return the result of running the retargeting algorithm with context object
418        // and the original result as input
419        let mut elements = Vec::new();
420        for e in self
421            .document_or_shadow_root
422            .elements_from_point(x, y, None, self.document.has_browsing_context())
423            .iter()
424        {
425            let retargeted_node = e.upcast::<EventTarget>().retarget(self.upcast());
426            if let Some(element) = retargeted_node.downcast::<Element>().map(DomRoot::from_ref) {
427                elements.push(element);
428            }
429        }
430        elements
431    }
432
433    /// <https://dom.spec.whatwg.org/#dom-shadowroot-mode>
434    fn Mode(&self) -> ShadowRootMode {
435        self.mode
436    }
437
438    /// <https://dom.spec.whatwg.org/#dom-delegates-focus>
439    fn DelegatesFocus(&self) -> bool {
440        self.delegates_focus.get()
441    }
442
443    /// <https://dom.spec.whatwg.org/#dom-shadowroot-clonable>
444    fn Clonable(&self) -> bool {
445        self.clonable
446    }
447
448    /// <https://dom.spec.whatwg.org/#dom-serializable>
449    fn Serializable(&self) -> bool {
450        self.serializable.get()
451    }
452
453    /// <https://dom.spec.whatwg.org/#dom-shadowroot-host>
454    fn Host(&self) -> DomRoot<Element> {
455        self.host.as_rooted()
456    }
457
458    /// <https://drafts.csswg.org/cssom/#dom-document-stylesheets>
459    fn StyleSheets(&self) -> DomRoot<StyleSheetList> {
460        self.stylesheet_list.or_init(|| {
461            StyleSheetList::new(
462                &self.window,
463                StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)),
464                CanGc::note(),
465            )
466        })
467    }
468
469    /// <https://html.spec.whatwg.org/multipage/#dom-shadowroot-gethtml>
470    fn GetHTML(&self, options: &GetHTMLOptions, can_gc: CanGc) -> DOMString {
471        // > ShadowRoot's getHTML(options) method steps are to return the result of HTML fragment serialization
472        // >  algorithm with this, options["serializableShadowRoots"], and options["shadowRoots"].
473        self.upcast::<Node>().html_serialize(
474            TraversalScope::ChildrenOnly(None),
475            options.serializableShadowRoots,
476            options.shadowRoots.clone(),
477            can_gc,
478        )
479    }
480
481    /// <https://html.spec.whatwg.org/multipage/#dom-shadowroot-innerhtml>
482    fn GetInnerHTML(&self, can_gc: CanGc) -> Fallible<TrustedHTMLOrNullIsEmptyString> {
483        // ShadowRoot's innerHTML getter steps are to return the result of running fragment serializing
484        // algorithm steps with this and true.
485        self.upcast::<Node>()
486            .fragment_serialization_algorithm(true, can_gc)
487            .map(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString)
488    }
489
490    /// <https://html.spec.whatwg.org/multipage/#dom-shadowroot-innerhtml>
491    fn SetInnerHTML(&self, value: TrustedHTMLOrNullIsEmptyString, can_gc: CanGc) -> ErrorResult {
492        // Step 1. Let compliantString be the result of invoking the Get Trusted Type compliant string algorithm
493        // with TrustedHTML, this's relevant global object, the given value, "ShadowRoot innerHTML", and "script".
494        let value = TrustedHTML::get_trusted_script_compliant_string(
495            &self.owner_global(),
496            value.convert(),
497            "ShadowRoot innerHTML",
498            can_gc,
499        )?;
500
501        // Step 2. Let context be this's host.
502        let context = self.Host();
503
504        // Step 3. Let fragment be the result of invoking the fragment parsing algorithm steps with context and
505        // compliantString.
506        //
507        // NOTE: The spec doesn't strictly tell us to bail out here, but
508        // we can't continue if parsing failed
509        let frag = context.parse_fragment(value, can_gc)?;
510
511        // Step 4. Replace all with fragment within this.
512        Node::replace_all(Some(frag.upcast()), self.upcast(), can_gc);
513        Ok(())
514    }
515
516    /// <https://dom.spec.whatwg.org/#dom-shadowroot-slotassignment>
517    fn SlotAssignment(&self) -> SlotAssignmentMode {
518        self.slot_assignment_mode
519    }
520
521    /// <https://html.spec.whatwg.org/multipage/#dom-shadowroot-sethtmlunsafe>
522    fn SetHTMLUnsafe(&self, value: TrustedHTMLOrString, can_gc: CanGc) -> ErrorResult {
523        // Step 1. Let compliantHTML be the result of invoking the
524        // Get Trusted Type compliant string algorithm with TrustedHTML,
525        // this's relevant global object, html, "ShadowRoot setHTMLUnsafe", and "script".
526        let value = TrustedHTML::get_trusted_script_compliant_string(
527            &self.owner_global(),
528            value,
529            "ShadowRoot setHTMLUnsafe",
530            can_gc,
531        )?;
532        // Step 2. Unsafely set HTMl given this, this's shadow host, and complaintHTML
533        let target = self.upcast::<Node>();
534        let context_element = self.Host();
535
536        Node::unsafely_set_html(target, &context_element, value, can_gc);
537        Ok(())
538    }
539
540    // https://dom.spec.whatwg.org/#dom-shadowroot-onslotchange
541    event_handler!(onslotchange, GetOnslotchange, SetOnslotchange);
542
543    /// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
544    fn AdoptedStyleSheets(&self, context: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
545        self.adopted_stylesheets_frozen_types.get_or_init(
546            || {
547                self.adopted_stylesheets
548                    .borrow()
549                    .clone()
550                    .iter()
551                    .map(|sheet| sheet.as_rooted())
552                    .collect()
553            },
554            context,
555            retval,
556            can_gc,
557        );
558    }
559
560    /// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
561    fn SetAdoptedStyleSheets(
562        &self,
563        context: JSContext,
564        val: HandleValue,
565        can_gc: CanGc,
566    ) -> ErrorResult {
567        let result = DocumentOrShadowRoot::set_adopted_stylesheet_from_jsval(
568            context,
569            self.adopted_stylesheets.borrow_mut().as_mut(),
570            val,
571            &StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)),
572            can_gc,
573        );
574
575        // If update is successful, clear the FrozenArray cache.
576        if result.is_ok() {
577            self.adopted_stylesheets_frozen_types.clear();
578        }
579
580        result
581    }
582}
583
584impl VirtualMethods for ShadowRoot {
585    fn super_type(&self) -> Option<&dyn VirtualMethods> {
586        Some(self.upcast::<DocumentFragment>() as &dyn VirtualMethods)
587    }
588
589    fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) {
590        if let Some(s) = self.super_type() {
591            s.bind_to_tree(context, can_gc);
592        }
593
594        // TODO(stevennovaryo): Handle adoptedStylesheet to deal with different
595        //                      constructor document.
596        if context.tree_connected {
597            let document = self.owner_document();
598            document.register_shadow_root(self);
599        }
600
601        let shadow_root = self.upcast::<Node>();
602
603        shadow_root.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected);
604
605        let context = BindContext::new(shadow_root, IsShadowTree::Yes);
606
607        // avoid iterate over the shadow root itself
608        for node in shadow_root.traverse_preorder(ShadowIncluding::Yes).skip(1) {
609            node.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected);
610
611            // Out-of-document elements never have the descendants flag set
612            debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS));
613            vtable_for(&node).bind_to_tree(&context, can_gc);
614        }
615    }
616
617    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
618        if let Some(s) = self.super_type() {
619            s.unbind_from_tree(context, can_gc);
620        }
621
622        if context.tree_connected {
623            let document = self.owner_document();
624            document.unregister_shadow_root(self);
625        }
626    }
627}
628
629#[expect(unsafe_code)]
630pub(crate) trait LayoutShadowRootHelpers<'dom> {
631    fn get_host_for_layout(self) -> LayoutDom<'dom, Element>;
632    fn get_style_data_for_layout(self) -> &'dom CascadeData;
633    fn is_ua_widget(&self) -> bool;
634    unsafe fn flush_stylesheets<E: TElement>(
635        self,
636        stylist: &mut Stylist,
637        guard: &SharedRwLockReadGuard,
638    );
639}
640
641impl<'dom> LayoutShadowRootHelpers<'dom> for LayoutDom<'dom, ShadowRoot> {
642    #[inline]
643    #[expect(unsafe_code)]
644    fn get_host_for_layout(self) -> LayoutDom<'dom, Element> {
645        unsafe { self.unsafe_get().host.to_layout() }
646    }
647
648    #[inline]
649    #[expect(unsafe_code)]
650    fn get_style_data_for_layout(self) -> &'dom CascadeData {
651        fn is_sync<T: Sync>() {}
652        let _ = is_sync::<CascadeData>;
653        unsafe { &self.unsafe_get().author_styles.borrow_for_layout().data }
654    }
655
656    #[inline]
657    fn is_ua_widget(&self) -> bool {
658        self.unsafe_get().is_user_agent_widget()
659    }
660
661    // FIXME(nox): This uses the dreaded borrow_mut_for_layout so this should
662    // probably be revisited.
663    #[inline]
664    #[expect(unsafe_code)]
665    unsafe fn flush_stylesheets<E: TElement>(
666        self,
667        stylist: &mut Stylist,
668        guard: &SharedRwLockReadGuard,
669    ) {
670        let author_styles = unsafe { self.unsafe_get().author_styles.borrow_mut_for_layout() };
671        if author_styles.stylesheets.dirty() {
672            author_styles.flush::<E>(stylist, guard);
673        }
674    }
675}
676
677impl Convert<devtools_traits::ShadowRootMode> for ShadowRootMode {
678    fn convert(self) -> devtools_traits::ShadowRootMode {
679        match self {
680            ShadowRootMode::Open => devtools_traits::ShadowRootMode::Open,
681            ShadowRootMode::Closed => devtools_traits::ShadowRootMode::Closed,
682        }
683    }
684}