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::rust::{HandleValue, MutableHandleValue};
12use script_bindings::cell::{DomRefCell, RefMut};
13use script_bindings::error::{ErrorResult, Fallible};
14use script_bindings::reflector::reflect_dom_object;
15use script_bindings::script_runtime::JSContext;
16use servo_arc::Arc;
17use style::author_styles::AuthorStyles;
18use style::invalidation::element::restyle_hints::RestyleHint;
19use style::shared_lock::SharedRwLockReadGuard;
20use style::stylesheets::Stylesheet;
21use style::stylist::{CascadeData, Stylist};
22use stylo_atoms::Atom;
23
24use crate::conversions::Convert;
25use crate::dom::bindings::codegen::Bindings::ElementBinding::GetHTMLOptions;
26use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::HTMLSlotElement_Binding::HTMLSlotElementMethods;
27use crate::dom::bindings::codegen::Bindings::SanitizerBinding::{
28 SetHTMLOptions, SetHTMLUnsafeOptions,
29};
30use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods;
31use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
32 ShadowRootMode, SlotAssignmentMode,
33};
34use crate::dom::bindings::codegen::UnionTypes::{
35 TrustedHTMLOrNullIsEmptyString, TrustedHTMLOrString,
36};
37use crate::dom::bindings::frozenarray::CachedFrozenArray;
38use crate::dom::bindings::inheritance::Castable;
39use crate::dom::bindings::num::Finite;
40use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
41use crate::dom::bindings::str::DOMString;
42use crate::dom::css::cssstylesheet::CSSStyleSheet;
43use crate::dom::css::stylesheetlist::{StyleSheetList, StyleSheetListOwner};
44use crate::dom::document::Document;
45use crate::dom::documentfragment::DocumentFragment;
46use crate::dom::documentorshadowroot::{
47 DocumentOrShadowRoot, ServoStylesheetInDocument, StylesheetSource,
48};
49use crate::dom::element::Element;
50use crate::dom::html::htmlslotelement::HTMLSlotElement;
51use crate::dom::htmldetailselement::DetailsNameGroups;
52use crate::dom::iterators::ShadowIncluding;
53use crate::dom::node::{
54 BindContext, IsShadowTree, Node, NodeDamage, NodeFlags, NodeTraits, UnbindContext,
55 VecPreOrderInsertionHelper,
56};
57use crate::dom::sanitizer::Sanitizer;
58use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
59use crate::dom::types::EventTarget;
60use crate::dom::virtualmethods::{VirtualMethods, vtable_for};
61use crate::dom::window::Window;
62use crate::script_runtime::CanGc;
63use crate::stylesheet_set::StylesheetSetRef;
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<DOMString, 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 #[ignore_malloc_size_of = "mozjs"]
116 adopted_stylesheets_frozen_types: CachedFrozenArray,
117
118 details_name_groups: DomRefCell<Option<DetailsNameGroups>>,
119}
120
121impl ShadowRoot {
122 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
123 fn new_inherited(
124 host: &Element,
125 document: &Document,
126 mode: ShadowRootMode,
127 slot_assignment_mode: SlotAssignmentMode,
128 clonable: bool,
129 is_user_agent_widget: IsUserAgentWidget,
130 ) -> ShadowRoot {
131 let document_fragment = DocumentFragment::new_inherited(document, Some(host));
132 let node = document_fragment.upcast::<Node>();
133 node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, true);
134 node.set_flag(
135 NodeFlags::IS_CONNECTED,
136 host.upcast::<Node>().is_connected(),
137 );
138
139 ShadowRoot {
140 document_fragment,
141 document_or_shadow_root: DocumentOrShadowRoot::new(document.window()),
142 document: Dom::from_ref(document),
143 author_styles: DomRefCell::new(AuthorStyles::new()),
144 stylesheet_list: MutNullableDom::new(None),
145 window: Dom::from_ref(document.window()),
146 mode,
147 slot_assignment_mode,
148 clonable,
149 available_to_element_internals: Cell::new(false),
150 slots: Default::default(),
151 is_user_agent_widget: is_user_agent_widget == IsUserAgentWidget::Yes,
152 declarative: Cell::new(false),
153 serializable: Cell::new(false),
154 delegates_focus: Cell::new(false),
155 adopted_stylesheets: Default::default(),
156 adopted_stylesheets_frozen_types: CachedFrozenArray::new(),
157 details_name_groups: Default::default(),
158 }
159 }
160
161 pub(crate) fn new(
162 host: &Element,
163 document: &Document,
164 mode: ShadowRootMode,
165 slot_assignment_mode: SlotAssignmentMode,
166 clonable: bool,
167 is_user_agent_widget: IsUserAgentWidget,
168 can_gc: CanGc,
169 ) -> DomRoot<ShadowRoot> {
170 reflect_dom_object(
171 Box::new(ShadowRoot::new_inherited(
172 host,
173 document,
174 mode,
175 slot_assignment_mode,
176 clonable,
177 is_user_agent_widget,
178 )),
179 document.window(),
180 can_gc,
181 )
182 }
183
184 pub(crate) fn owner_doc(&self) -> &Document {
185 &self.document
186 }
187
188 pub(crate) fn stylesheet_count(&self) -> usize {
189 self.author_styles.borrow().stylesheets.len()
190 }
191
192 pub(crate) fn stylesheet_at(&self, index: usize) -> Option<DomRoot<CSSStyleSheet>> {
193 let stylesheets = &self.author_styles.borrow().stylesheets;
194
195 stylesheets
196 .get(index)
197 .and_then(|s| s.owner.get_cssom_object())
198 }
199
200 #[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn add_owned_stylesheet(
207 &self,
208 cx: &mut js::context::JSContext,
209 owner_node: &Element,
210 sheet: Arc<Stylesheet>,
211 ) {
212 let stylesheets = &mut self.author_styles.borrow_mut().stylesheets;
213
214 let insertion_point = stylesheets
216 .iter()
217 .find(|sheet_in_shadow| {
218 match &sheet_in_shadow.owner {
219 StylesheetSource::Element(other_node) => {
220 owner_node.upcast::<Node>().is_before(other_node.upcast())
221 },
222 StylesheetSource::Constructed(_) => true,
225 }
226 })
227 .cloned();
228
229 if self.document.has_browsing_context() {
230 self.document.load_web_fonts_from_stylesheet(cx, &sheet);
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_author_lock(),
239 );
240 }
241
242 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
244 pub(crate) fn append_constructed_stylesheet(
245 &self,
246 cx: &mut js::context::JSContext,
247 cssom_stylesheet: &CSSStyleSheet,
248 ) {
249 debug_assert!(cssom_stylesheet.is_constructed());
250
251 let stylesheets = &mut self.author_styles.borrow_mut().stylesheets;
252 let sheet = cssom_stylesheet.style_stylesheet().clone();
253
254 let insertion_point = stylesheets.iter().last().cloned();
255
256 if self.document.has_browsing_context() {
257 self.document.load_web_fonts_from_stylesheet(cx, &sheet);
258 }
259
260 DocumentOrShadowRoot::add_stylesheet(
261 StylesheetSource::Constructed(Dom::from_ref(cssom_stylesheet)),
262 StylesheetSetRef::Author(stylesheets),
263 sheet,
264 insertion_point,
265 self.document.style_shared_author_lock(),
266 );
267 }
268
269 #[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn remove_stylesheet(&self, owner: StylesheetSource, s: &Arc<Stylesheet>) {
272 DocumentOrShadowRoot::remove_stylesheet(
273 owner,
274 s,
275 StylesheetSetRef::Author(&mut self.author_styles.borrow_mut().stylesheets),
276 )
277 }
278
279 pub(crate) fn invalidate_stylesheets(&self) {
280 self.document.invalidate_shadow_roots_stylesheets();
281 self.author_styles.borrow_mut().stylesheets.force_dirty();
282 self.Host().upcast::<Node>().dirty(NodeDamage::Style);
284
285 let mut restyle = self.document.ensure_pending_restyle(&self.Host());
288 restyle.hint.insert(RestyleHint::restyle_subtree());
289 }
290
291 pub(crate) fn unregister_element_id(&self, id: &Atom) {
294 self.document_fragment.id_map().remove(id);
295 }
296
297 pub(crate) fn register_element_id(&self, element: &Element, id: &Atom, _can_gc: CanGc) {
299 self.document_fragment.id_map().add(id, element)
300 }
301
302 pub(crate) fn register_slot(&self, slot: &HTMLSlotElement) {
303 debug!("Registering slot with name={:?}", slot.Name().str());
304
305 let mut slots = self.slots.borrow_mut();
306
307 let slots_with_the_same_name = slots.entry(slot.Name()).or_default();
308
309 slots_with_the_same_name.insert_pre_order(slot, self.upcast::<Node>());
311 }
312
313 pub(crate) fn unregister_slot(&self, name: DOMString, slot: &HTMLSlotElement) {
314 debug!("Unregistering slot with name={:?}", name.str());
315
316 let mut slots = self.slots.borrow_mut();
317 let Entry::Occupied(mut entry) = slots.entry(name) else {
318 panic!("slot is not registered");
319 };
320 entry.get_mut().retain(|s| slot != &**s);
321 }
322
323 pub(crate) fn slot_for_name(&self, name: &DOMString) -> Option<DomRoot<HTMLSlotElement>> {
325 self.slots
326 .borrow()
327 .get(name)
328 .and_then(|slots| slots.first())
329 .map(|slot| slot.as_rooted())
330 }
331
332 pub(crate) fn has_slot_descendants(&self) -> bool {
333 !self.slots.borrow().is_empty()
334 }
335
336 pub(crate) fn set_available_to_element_internals(&self, value: bool) {
337 self.available_to_element_internals.set(value);
338 }
339
340 pub(crate) fn is_available_to_element_internals(&self) -> bool {
342 self.available_to_element_internals.get()
343 }
344
345 pub(crate) fn is_user_agent_widget(&self) -> bool {
346 self.is_user_agent_widget
347 }
348
349 pub(crate) fn set_declarative(&self, declarative: bool) {
350 self.declarative.set(declarative);
351 }
352
353 pub(crate) fn is_declarative(&self) -> bool {
354 self.declarative.get()
355 }
356
357 pub(crate) fn shadow_root_mode(&self) -> ShadowRootMode {
358 self.mode
359 }
360
361 pub(crate) fn set_serializable(&self, serializable: bool) {
362 self.serializable.set(serializable);
363 }
364
365 pub(crate) fn set_delegates_focus(&self, delegates_focus: bool) {
366 self.delegates_focus.set(delegates_focus);
367 }
368
369 pub(crate) fn details_name_groups(&self) -> RefMut<'_, DetailsNameGroups> {
370 RefMut::map(
371 self.details_name_groups.borrow_mut(),
372 |details_name_groups| details_name_groups.get_or_insert_default(),
373 )
374 }
375}
376
377impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {
378 fn GetActiveElement(&self) -> Option<DomRoot<Element>> {
380 self.document_or_shadow_root.active_element(self.upcast())
381 }
382
383 fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<DomRoot<Element>> {
385 match self.document_or_shadow_root.element_from_point(
388 x,
389 y,
390 None,
391 self.document.has_browsing_context(),
392 ) {
393 Some(e) => {
394 let retargeted_node = e.upcast::<EventTarget>().retarget(self.upcast());
395 retargeted_node.downcast::<Element>().map(DomRoot::from_ref)
396 },
397 None => None,
398 }
399 }
400
401 fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<DomRoot<Element>> {
403 let mut elements = Vec::new();
406 for e in self
407 .document_or_shadow_root
408 .elements_from_point(x, y, None, self.document.has_browsing_context())
409 .iter()
410 {
411 let retargeted_node = e.upcast::<EventTarget>().retarget(self.upcast());
412 if let Some(element) = retargeted_node.downcast::<Element>().map(DomRoot::from_ref) {
413 elements.push(element);
414 }
415 }
416 elements
417 }
418
419 fn Mode(&self) -> ShadowRootMode {
421 self.mode
422 }
423
424 fn DelegatesFocus(&self) -> bool {
426 self.delegates_focus.get()
427 }
428
429 fn Clonable(&self) -> bool {
431 self.clonable
432 }
433
434 fn Serializable(&self) -> bool {
436 self.serializable.get()
437 }
438
439 fn Host(&self) -> DomRoot<Element> {
441 self.upcast::<DocumentFragment>()
442 .host()
443 .expect("ShadowRoot always has an element as host")
444 }
445
446 fn StyleSheets(&self) -> DomRoot<StyleSheetList> {
448 self.stylesheet_list.or_init(|| {
449 StyleSheetList::new(
450 &self.window,
451 StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)),
452 CanGc::deprecated_note(),
453 )
454 })
455 }
456
457 fn GetHTML(&self, cx: &mut js::context::JSContext, options: &GetHTMLOptions) -> DOMString {
459 self.upcast::<Node>().html_serialize(
462 cx,
463 TraversalScope::ChildrenOnly(None),
464 options.serializableShadowRoots,
465 options.shadowRoots.clone(),
466 )
467 }
468
469 fn GetInnerHTML(
471 &self,
472 cx: &mut js::context::JSContext,
473 ) -> Fallible<TrustedHTMLOrNullIsEmptyString> {
474 self.upcast::<Node>()
477 .fragment_serialization_algorithm(cx, true)
478 .map(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString)
479 }
480
481 fn SetInnerHTML(
483 &self,
484 cx: &mut js::context::JSContext,
485 value: TrustedHTMLOrNullIsEmptyString,
486 ) -> ErrorResult {
487 let value = TrustedHTML::get_trusted_type_compliant_string(
490 cx,
491 &self.owner_global(),
492 value.convert(),
493 "ShadowRoot innerHTML",
494 )?;
495
496 let context = self.Host();
498
499 let frag = context.parse_fragment(value, cx)?;
505
506 Node::replace_all(cx, Some(frag.upcast()), self.upcast());
508 Ok(())
509 }
510
511 fn SlotAssignment(&self) -> SlotAssignmentMode {
513 self.slot_assignment_mode
514 }
515
516 fn SetHTMLUnsafe(
518 &self,
519 cx: &mut js::context::JSContext,
520 value: TrustedHTMLOrString,
521 options: &SetHTMLUnsafeOptions,
522 ) -> ErrorResult {
523 let compliant_html = TrustedHTML::get_trusted_type_compliant_string(
527 cx,
528 &self.owner_global(),
529 value,
530 "ShadowRoot setHTMLUnsafe",
531 )?;
532
533 Sanitizer::set_and_filter_html(
536 cx,
537 self.upcast(),
538 &self.Host(),
539 compliant_html,
540 options,
541 false,
542 )?;
543
544 Ok(())
545 }
546
547 fn SetHTML(
549 &self,
550 cx: &mut js::context::JSContext,
551 html: DOMString,
552 options: &SetHTMLOptions,
553 ) -> ErrorResult {
554 let target = self.upcast::<Node>();
559 let context_element = self.Host();
560 Sanitizer::set_and_filter_html(cx, target, &context_element, html, options, true)
561 }
562
563 event_handler!(slotchange, GetOnslotchange, SetOnslotchange);
565
566 fn AdoptedStyleSheets(&self, context: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
568 self.adopted_stylesheets_frozen_types.get_or_init(
569 || {
570 self.adopted_stylesheets
571 .borrow()
572 .clone()
573 .iter()
574 .map(|sheet| sheet.as_rooted())
575 .collect()
576 },
577 context,
578 retval,
579 can_gc,
580 );
581 }
582
583 fn SetAdoptedStyleSheets(
585 &self,
586 cx: &mut js::context::JSContext,
587 val: HandleValue,
588 ) -> ErrorResult {
589 let result = DocumentOrShadowRoot::set_adopted_stylesheet_from_jsval(
590 cx,
591 self.adopted_stylesheets.borrow_mut().as_mut(),
592 val,
593 &StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)),
594 );
595
596 if result.is_ok() {
598 self.adopted_stylesheets_frozen_types.clear();
599 }
600
601 result
602 }
603
604 fn GetFullscreenElement(&self) -> Option<DomRoot<Element>> {
606 DocumentOrShadowRoot::get_fullscreen_element(
607 self.upcast::<Node>(),
608 self.document.fullscreen_element(),
609 )
610 }
611}
612
613impl VirtualMethods for ShadowRoot {
614 fn super_type(&self) -> Option<&dyn VirtualMethods> {
615 Some(self.upcast::<DocumentFragment>() as &dyn VirtualMethods)
616 }
617
618 fn bind_to_tree(&self, cx: &mut js::context::JSContext, context: &BindContext) {
619 if let Some(s) = self.super_type() {
620 s.bind_to_tree(cx, context);
621 }
622
623 if context.tree_connected {
626 let document = self.owner_document();
627 document.register_shadow_root(self);
628 }
629
630 let shadow_root = self.upcast::<Node>();
631
632 shadow_root.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected);
633
634 let inner_context = BindContext::new(shadow_root, IsShadowTree::Yes);
635
636 for node in shadow_root.traverse_preorder(ShadowIncluding::No).skip(1) {
638 node.set_flag(NodeFlags::IS_CONNECTED, inner_context.tree_connected);
639
640 debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS));
642 vtable_for(&node).bind_to_tree(cx, &inner_context);
643 }
644 }
645
646 fn unbind_from_tree(&self, cx: &mut js::context::JSContext, context: &UnbindContext) {
647 if let Some(s) = self.super_type() {
648 s.unbind_from_tree(cx, context);
649 }
650
651 if context.tree_connected {
652 let document = self.owner_document();
653 document.unregister_shadow_root(self);
654 }
655 }
656}
657
658impl<'dom> LayoutDom<'dom, ShadowRoot> {
659 #[inline]
660 pub(crate) fn get_host_for_layout(self) -> LayoutDom<'dom, Element> {
661 self.upcast::<DocumentFragment>()
662 .shadowroot_host_for_layout()
663 }
664
665 #[inline]
666 #[expect(unsafe_code)]
667 pub(crate) fn get_style_data_for_layout(self) -> &'dom CascadeData {
668 fn is_sync<T: Sync>() {}
669 let _ = is_sync::<CascadeData>;
670 unsafe { &self.unsafe_get().author_styles.borrow_for_layout().data }
671 }
672
673 #[inline]
674 pub(crate) fn is_user_agent_widget(&self) -> bool {
675 self.unsafe_get().is_user_agent_widget()
676 }
677
678 #[inline]
681 #[expect(unsafe_code)]
682 pub(crate) unsafe fn flush_stylesheets_for_layout(
683 self,
684 stylist: &mut Stylist,
685 guard: &SharedRwLockReadGuard,
686 ) {
687 unsafe {
688 debug_assert!(self.upcast::<Node>().get_flag(NodeFlags::IS_CONNECTED));
689 };
690 let author_styles = unsafe { self.unsafe_get().author_styles.borrow_mut_for_layout() };
691 if author_styles.stylesheets.dirty() {
692 author_styles.flush(stylist, guard);
693 }
694 }
695}
696
697impl Convert<devtools_traits::ShadowRootMode> for ShadowRootMode {
698 fn convert(self) -> devtools_traits::ShadowRootMode {
699 match self {
700 ShadowRootMode::Open => devtools_traits::ShadowRootMode::Open,
701 ShadowRootMode::Closed => devtools_traits::ShadowRootMode::Closed,
702 }
703 }
704}