1use 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::context::{JSContext, NoGC};
12use script_bindings::cell::{DomRefCell, RefMut};
13use script_bindings::dom::UnrootedDom;
14use script_bindings::domstring::TracedDOMString;
15use script_bindings::error::{ErrorResult, Fallible};
16use script_bindings::reflector::reflect_dom_object;
17use servo_arc::Arc;
18use style::author_styles::AuthorStyles;
19use style::invalidation::element::restyle_hints::RestyleHint;
20use style::shared_lock::SharedRwLockReadGuard;
21use style::stylesheets::Stylesheet;
22use style::stylist::{CascadeData, Stylist};
23use stylo_atoms::Atom;
24
25use crate::conversions::Convert;
26use crate::css::stylesheet_set::StylesheetSetRef;
27use crate::dom::bindings::codegen::Bindings::ElementBinding::GetHTMLOptions;
28use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::HTMLSlotElement_Binding::HTMLSlotElementMethods;
29use crate::dom::bindings::codegen::Bindings::SanitizerBinding::{
30 SetHTMLOptions, SetHTMLUnsafeOptions,
31};
32use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
33use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
34 ShadowRootMode, SlotAssignmentMode,
35};
36use crate::dom::bindings::codegen::UnionTypes::{
37 TrustedHTMLOrNullIsEmptyString, TrustedHTMLOrString,
38};
39use crate::dom::bindings::inheritance::Castable;
40use crate::dom::bindings::num::Finite;
41use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
42use crate::dom::bindings::str::DOMString;
43use crate::dom::css::cssstylesheet::CSSStyleSheet;
44use crate::dom::css::stylesheetlist::{StyleSheetList, StyleSheetListOwner};
45use crate::dom::customelementregistry::CustomElementRegistry;
46use crate::dom::document::Document;
47use crate::dom::documentfragment::DocumentFragment;
48use crate::dom::documentorshadowroot::{
49 DocumentOrShadowRoot, ServoStylesheetInDocument, StylesheetSource,
50};
51use crate::dom::element::Element;
52use crate::dom::html::htmlslotelement::HTMLSlotElement;
53use crate::dom::htmldetailselement::DetailsNameGroups;
54use crate::dom::iterators::ShadowIncluding;
55use crate::dom::node::virtualmethods::{VirtualMethods, vtable_for};
56use crate::dom::node::{
57 BindContext, IsShadowTree, Node, NodeDamage, NodeFlags, NodeTraits, UnbindContext,
58 VecPreOrderInsertionHelper,
59};
60use crate::dom::sanitizer::Sanitizer;
61use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
62use crate::dom::types::EventTarget;
63use crate::dom::window::Window;
64
65#[derive(JSTraceable, MallocSizeOf, PartialEq)]
67pub(crate) enum IsUserAgentWidget {
68 No,
69 Yes,
70}
71
72#[dom_struct]
74pub(crate) struct ShadowRoot {
75 document_fragment: DocumentFragment,
77 document_or_shadow_root: DocumentOrShadowRoot,
78 document: Dom<Document>,
79 #[custom_trace]
81 author_styles: DomRefCell<AuthorStyles<ServoStylesheetInDocument>>,
82 stylesheet_list: MutNullableDom<StyleSheetList>,
83 window: Dom<Window>,
84
85 mode: ShadowRootMode,
87
88 slot_assignment_mode: SlotAssignmentMode,
90
91 clonable: bool,
93
94 available_to_element_internals: Cell<bool>,
96
97 slots: DomRefCell<HashMap<TracedDOMString, Vec<Dom<HTMLSlotElement>>>>,
98
99 is_user_agent_widget: bool,
100
101 declarative: Cell<bool>,
103
104 serializable: Cell<bool>,
106
107 delegates_focus: Cell<bool>,
109
110 adopted_stylesheets: DomRefCell<Vec<Dom<CSSStyleSheet>>>,
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, Some(host));
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 author_styles: DomRefCell::new(AuthorStyles::new()),
140 stylesheet_list: MutNullableDom::new(None),
141 window: Dom::from_ref(document.window()),
142 mode,
143 slot_assignment_mode,
144 clonable,
145 available_to_element_internals: Cell::new(false),
146 slots: Default::default(),
147 is_user_agent_widget: is_user_agent_widget == IsUserAgentWidget::Yes,
148 declarative: Cell::new(false),
149 serializable: Cell::new(false),
150 delegates_focus: Cell::new(false),
151 adopted_stylesheets: Default::default(),
152 details_name_groups: Default::default(),
153 }
154 }
155
156 pub(crate) fn new(
157 cx: &mut JSContext,
158 host: &Element,
159 document: &Document,
160 mode: ShadowRootMode,
161 slot_assignment_mode: SlotAssignmentMode,
162 clonable: bool,
163 is_user_agent_widget: IsUserAgentWidget,
164 ) -> DomRoot<ShadowRoot> {
165 reflect_dom_object(
166 cx,
167 Box::new(ShadowRoot::new_inherited(
168 host,
169 document,
170 mode,
171 slot_assignment_mode,
172 clonable,
173 is_user_agent_widget,
174 )),
175 document.window(),
176 )
177 }
178
179 pub(crate) fn host_unrooted<'a>(&self, no_gc: &'a NoGC) -> UnrootedDom<'a, Element> {
180 self.upcast::<DocumentFragment>()
181 .host_unrooted(no_gc)
182 .expect("ShadowRoot always has an element as host")
183 }
184
185 pub(crate) fn owner_doc(&self) -> &Document {
186 &self.document
187 }
188
189 pub(crate) fn stylesheet_count(&self) -> usize {
190 self.author_styles.borrow().stylesheets.len()
191 }
192
193 pub(crate) fn stylesheet_at(
194 &self,
195 cx: &mut JSContext,
196 index: usize,
197 ) -> Option<DomRoot<CSSStyleSheet>> {
198 let stylesheets = &self.author_styles.borrow().stylesheets;
199
200 stylesheets
201 .get(index)
202 .and_then(|s| s.owner.get_cssom_object(cx))
203 }
204
205 #[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn add_owned_stylesheet(
212 &self,
213 no_gc: &NoGC,
214 owner_node: &Element,
215 sheet: Arc<Stylesheet>,
216 ) {
217 let stylesheets = &mut self.author_styles.borrow_mut().stylesheets;
218
219 let insertion_point = stylesheets
221 .iter()
222 .find(|sheet_in_shadow| {
223 match &sheet_in_shadow.owner {
224 StylesheetSource::Element(other_node) => owner_node
225 .upcast::<Node>()
226 .is_before(no_gc, other_node.upcast()),
227 StylesheetSource::Constructed(_) => true,
230 }
231 })
232 .cloned();
233
234 DocumentOrShadowRoot::add_stylesheet(
235 StylesheetSource::Element(Dom::from_ref(owner_node)),
236 StylesheetSetRef::Author(stylesheets),
237 sheet,
238 insertion_point,
239 self.document.style_shared_author_lock(),
240 );
241 }
242
243 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
245 pub(crate) fn append_constructed_stylesheet(&self, cssom_stylesheet: &CSSStyleSheet) {
246 debug_assert!(cssom_stylesheet.is_constructed());
247
248 let stylesheets = &mut self.author_styles.borrow_mut().stylesheets;
249 let sheet = cssom_stylesheet.style_stylesheet().clone();
250
251 let insertion_point = stylesheets.iter().last().cloned();
252
253 DocumentOrShadowRoot::add_stylesheet(
254 StylesheetSource::Constructed(Dom::from_ref(cssom_stylesheet)),
255 StylesheetSetRef::Author(stylesheets),
256 sheet,
257 insertion_point,
258 self.document.style_shared_author_lock(),
259 );
260 }
261
262 #[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn remove_stylesheet(&self, owner: StylesheetSource, s: &Arc<Stylesheet>) {
265 DocumentOrShadowRoot::remove_stylesheet(
266 owner,
267 s,
268 StylesheetSetRef::Author(&mut self.author_styles.borrow_mut().stylesheets),
269 )
270 }
271
272 pub(crate) fn invalidate_stylesheets(&self, no_gc: &NoGC) {
273 self.document.invalidate_shadow_roots_stylesheets();
274 self.author_styles.borrow_mut().stylesheets.force_dirty();
275 self.Host().upcast::<Node>().dirty(no_gc, NodeDamage::Style);
277
278 let mut restyle = self.document.ensure_pending_restyle(&self.Host());
281 restyle.hint.insert(RestyleHint::restyle_subtree());
282 }
283
284 pub(crate) fn unregister_element_id(&self, id: &Atom) {
287 self.document_fragment.id_map().remove(id);
288 }
289
290 pub(crate) fn register_element_id(&self, element: &Element, id: &Atom) {
292 self.document_fragment.id_map().add(id, element)
293 }
294
295 pub(crate) fn register_slot(&self, slot: &HTMLSlotElement) {
296 debug!("Registering slot with name={:?}", slot.Name().str());
297
298 let mut slots = self.slots.borrow_mut();
299
300 let slots_with_the_same_name = slots.entry(slot.Name().traced()).or_default();
301
302 slots_with_the_same_name.insert_pre_order(slot, self.upcast::<Node>());
304 }
305
306 pub(crate) fn unregister_slot(&self, name: DOMString, slot: &HTMLSlotElement) {
307 debug!("Unregistering slot with name={:?}", name.str());
308
309 let mut slots = self.slots.borrow_mut();
310 let Entry::Occupied(mut entry) = slots.entry(name.traced()) else {
311 panic!("slot is not registered");
312 };
313 entry.get_mut().retain(|s| slot != &**s);
314 }
315
316 pub(crate) fn slot_for_name(&self, name: &DOMString) -> Option<DomRoot<HTMLSlotElement>> {
318 self.slots
319 .borrow()
320 .get(name)
321 .and_then(|slots| slots.first())
322 .map(|slot| slot.as_rooted())
323 }
324
325 pub(crate) fn has_slot_descendants(&self) -> bool {
326 !self.slots.borrow().is_empty()
327 }
328
329 pub(crate) fn set_available_to_element_internals(&self, value: bool) {
330 self.available_to_element_internals.set(value);
331 }
332
333 pub(crate) fn is_available_to_element_internals(&self) -> bool {
335 self.available_to_element_internals.get()
336 }
337
338 pub(crate) fn is_user_agent_widget(&self) -> bool {
339 self.is_user_agent_widget
340 }
341
342 pub(crate) fn set_declarative(&self, declarative: bool) {
343 self.declarative.set(declarative);
344 }
345
346 pub(crate) fn is_declarative(&self) -> bool {
347 self.declarative.get()
348 }
349
350 pub(crate) fn shadow_root_mode(&self) -> ShadowRootMode {
351 self.mode
352 }
353
354 pub(crate) fn set_serializable(&self, serializable: bool) {
355 self.serializable.set(serializable);
356 }
357
358 pub(crate) fn set_delegates_focus(&self, delegates_focus: bool) {
359 self.delegates_focus.set(delegates_focus);
360 }
361
362 pub(crate) fn details_name_groups<'a: 'b, 'b>(
363 &'a self,
364 no_gc: &'b NoGC,
365 ) -> RefMut<'b, DetailsNameGroups> {
366 RefMut::map(
367 self.details_name_groups.safe_borrow_mut(no_gc),
368 |details_name_groups| details_name_groups.get_or_insert_default(),
369 )
370 }
371
372 pub(crate) fn custom_element_registry(&self) -> Option<DomRoot<CustomElementRegistry>> {
373 self.document_or_shadow_root.custom_element_registry()
374 }
375
376 pub(crate) fn set_custom_element_registry(&self, registry: Option<&CustomElementRegistry>) {
377 self.document_or_shadow_root
378 .set_custom_element_registry(registry);
379 }
380}
381
382impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {
383 fn GetActiveElement(&self) -> Option<DomRoot<Element>> {
385 self.document_or_shadow_root.active_element(self.upcast())
386 }
387
388 fn GetCustomElementRegistry(&self) -> Option<DomRoot<CustomElementRegistry>> {
390 self.custom_element_registry()
391 }
392
393 fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<DomRoot<Element>> {
395 match self.document_or_shadow_root.element_from_point(
398 self.upcast(),
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 fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<DomRoot<Element>> {
414 let mut elements = Vec::new();
417 for e in self
418 .document_or_shadow_root
419 .elements_from_point(
420 self.upcast(),
421 x,
422 y,
423 None,
424 self.document.has_browsing_context(),
425 )
426 .iter()
427 {
428 let retargeted_node = e.upcast::<EventTarget>().retarget(self.upcast());
429 if let Some(element) = retargeted_node.downcast::<Element>().map(DomRoot::from_ref) {
430 elements.push(element);
431 }
432 }
433 elements
434 }
435
436 fn Mode(&self) -> ShadowRootMode {
438 self.mode
439 }
440
441 fn DelegatesFocus(&self) -> bool {
443 self.delegates_focus.get()
444 }
445
446 fn Clonable(&self) -> bool {
448 self.clonable
449 }
450
451 fn Serializable(&self) -> bool {
453 self.serializable.get()
454 }
455
456 fn Host(&self) -> DomRoot<Element> {
458 self.upcast::<DocumentFragment>()
459 .host()
460 .expect("ShadowRoot always has an element as host")
461 }
462
463 fn StyleSheets(&self, cx: &mut JSContext) -> DomRoot<StyleSheetList> {
465 self.stylesheet_list.or_init(|| {
466 StyleSheetList::new(
467 cx,
468 &self.window,
469 StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)),
470 )
471 })
472 }
473
474 fn GetHTML(&self, cx: &mut JSContext, options: &GetHTMLOptions) -> DOMString {
476 self.upcast::<Node>().html_serialize(
479 cx,
480 TraversalScope::ChildrenOnly(None),
481 options.serializableShadowRoots,
482 options.shadowRoots.clone(),
483 )
484 }
485
486 fn GetInnerHTML(&self, cx: &mut JSContext) -> Fallible<TrustedHTMLOrNullIsEmptyString> {
488 self.upcast::<Node>()
491 .fragment_serialization_algorithm(cx, true)
492 .map(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString)
493 }
494
495 fn SetInnerHTML(
497 &self,
498 cx: &mut JSContext,
499 value: TrustedHTMLOrNullIsEmptyString,
500 ) -> ErrorResult {
501 let value = TrustedHTML::get_trusted_type_compliant_string(
504 cx,
505 &self.owner_global(),
506 value.convert(),
507 "ShadowRoot innerHTML",
508 )?;
509
510 let context = self.Host();
512
513 let frag = context.parse_fragment(value, cx)?;
519
520 Node::replace_all(cx, Some(frag.upcast()), self.upcast());
522 Ok(())
523 }
524
525 fn SlotAssignment(&self) -> SlotAssignmentMode {
527 self.slot_assignment_mode
528 }
529
530 fn SetHTMLUnsafe(
532 &self,
533 cx: &mut JSContext,
534 value: TrustedHTMLOrString,
535 options: &SetHTMLUnsafeOptions,
536 ) -> ErrorResult {
537 let compliant_html = TrustedHTML::get_trusted_type_compliant_string(
541 cx,
542 &self.owner_global(),
543 value,
544 "ShadowRoot setHTMLUnsafe",
545 )?;
546
547 Sanitizer::set_and_filter_html(
550 cx,
551 self.upcast(),
552 &self.Host(),
553 compliant_html,
554 options,
555 false,
556 )?;
557
558 Ok(())
559 }
560
561 fn SetHTML(
563 &self,
564 cx: &mut JSContext,
565 html: DOMString,
566 options: &SetHTMLOptions,
567 ) -> ErrorResult {
568 let target = self.upcast::<Node>();
573 let context_element = self.Host();
574 Sanitizer::set_and_filter_html(cx, target, &context_element, html, options, true)
575 }
576
577 event_handler!(slotchange, GetOnslotchange, SetOnslotchange);
579
580 fn OnSetAdoptedStyleSheets(
582 &self,
583 cx: &mut JSContext,
584 value: DomRoot<CSSStyleSheet>,
585 index: u32,
586 ) -> ErrorResult {
587 let result = DocumentOrShadowRoot::on_set_adopted_stylesheets(
588 self.adopted_stylesheets.safe_borrow_mut(cx).as_mut(),
589 &value,
590 index,
591 &StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)),
592 );
593
594 if result.is_ok() && self.author_styles.borrow().stylesheets.dirty() {
595 self.invalidate_stylesheets(cx.no_gc());
596 }
597
598 result
599 }
600
601 fn OnDeleteAdoptedStyleSheets(
603 &self,
604 cx: &mut JSContext,
605 _value: DomRoot<CSSStyleSheet>,
606 index: u32,
607 ) -> ErrorResult {
608 let result = DocumentOrShadowRoot::on_delete_adopted_stylesheets(
609 self.adopted_stylesheets.safe_borrow_mut(cx).as_mut(),
610 index,
611 &StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)),
612 );
613
614 if result.is_ok() && self.author_styles.borrow().stylesheets.dirty() {
615 self.invalidate_stylesheets(cx.no_gc());
616 }
617
618 result
619 }
620
621 fn GetFullscreenElement(&self) -> Option<DomRoot<Element>> {
623 DocumentOrShadowRoot::get_fullscreen_element(
624 self.upcast::<Node>(),
625 self.document.fullscreen_element(),
626 )
627 }
628}
629
630impl VirtualMethods for ShadowRoot {
631 fn super_type(&self) -> Option<&dyn VirtualMethods> {
632 Some(self.upcast::<DocumentFragment>() as &dyn VirtualMethods)
633 }
634
635 fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) {
636 if let Some(s) = self.super_type() {
637 s.bind_to_tree(cx, context);
638 }
639
640 if context.tree_connected {
643 self.owner_document_unrooted(cx.no_gc())
644 .register_shadow_root(self);
645 }
646
647 let shadow_root = self.upcast::<Node>();
648
649 shadow_root.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected);
650
651 let inner_context = BindContext::new(shadow_root, IsShadowTree::Yes);
652
653 for node in shadow_root.traverse_preorder(ShadowIncluding::No).skip(1) {
655 node.set_flag(NodeFlags::IS_CONNECTED, inner_context.tree_connected);
656
657 debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS));
659 vtable_for(&node).bind_to_tree(cx, &inner_context);
660 }
661 }
662
663 fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) {
664 if let Some(s) = self.super_type() {
665 s.unbind_from_tree(cx, context);
666 }
667
668 if context.tree_connected {
669 self.owner_document_unrooted(cx.no_gc())
670 .unregister_shadow_root(self);
671 }
672 }
673}
674
675impl<'dom> LayoutDom<'dom, ShadowRoot> {
676 #[inline]
677 pub(crate) fn get_host_for_layout(self) -> LayoutDom<'dom, Element> {
678 self.upcast::<DocumentFragment>()
679 .shadowroot_host_for_layout()
680 }
681
682 #[inline]
683 #[expect(unsafe_code)]
684 pub(crate) fn get_style_data_for_layout(self) -> &'dom CascadeData {
685 fn is_sync<T: Sync>() {}
686 let _ = is_sync::<CascadeData>;
687 unsafe { &self.unsafe_get().author_styles.borrow_for_layout().data }
688 }
689
690 #[inline]
691 pub(crate) fn is_user_agent_widget(&self) -> bool {
692 self.unsafe_get().is_user_agent_widget()
693 }
694
695 #[inline]
698 #[expect(unsafe_code)]
699 pub(crate) unsafe fn flush_stylesheets_for_layout(
700 self,
701 stylist: &mut Stylist,
702 guard: &SharedRwLockReadGuard,
703 ) {
704 unsafe {
705 debug_assert!(self.upcast::<Node>().get_flag(NodeFlags::IS_CONNECTED));
706 };
707 let author_styles = unsafe { self.unsafe_get().author_styles.borrow_mut_for_layout() };
708 if author_styles.stylesheets.dirty() {
709 author_styles.flush(stylist, guard);
710 }
711 }
712}
713
714#[cfg(feature = "devtools")]
715impl Convert<devtools_traits::ShadowRootMode> for ShadowRootMode {
716 fn convert(self) -> devtools_traits::ShadowRootMode {
717 match self {
718 ShadowRootMode::Open => devtools_traits::ShadowRootMode::Open,
719 ShadowRootMode::Closed => devtools_traits::ShadowRootMode::Closed,
720 }
721 }
722}