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