/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/script/dom/shadowroot.rs
715 строк
26 KB
Narfinger
script: Cleanup custom node dirty inclusive ancestor walk (#46981)
04 авг 2026, 20:35
Не верифицирован
04 авг 2026, 20:35
21faece
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::Cell; use std::collections::HashMap; use std::collections::hash_map::Entry; use dom_struct::dom_struct; use html5ever::serialize::TraversalScope; use js::context::{JSContext, NoGC}; use js::rust::{HandleValue, MutableHandleValue}; use script_bindings::cell::{DomRefCell, RefMut}; use script_bindings::dom::UnrootedDom; use script_bindings::error::{ErrorResult, Fallible}; use script_bindings::reflector::reflect_dom_object_with_cx; use servo_arc::Arc; use style::author_styles::AuthorStyles; use style::invalidation::element::restyle_hints::RestyleHint; use style::shared_lock::SharedRwLockReadGuard; use style::stylesheets::Stylesheet; use style::stylist::{CascadeData, Stylist}; use stylo_atoms::Atom; use crate::conversions::Convert; use crate::dom::bindings::codegen::Bindings::ElementBinding::GetHTMLOptions; use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::HTMLSlotElement_Binding::HTMLSlotElementMethods; use crate::dom::bindings::codegen::Bindings::SanitizerBinding::{ SetHTMLOptions, SetHTMLUnsafeOptions, }; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRoot_Binding::ShadowRootMethods; use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{ ShadowRootMode, SlotAssignmentMode, }; use crate::dom::bindings::codegen::UnionTypes::{ TrustedHTMLOrNullIsEmptyString, TrustedHTMLOrString, }; use crate::dom::bindings::frozenarray::CachedFrozenArray; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::num::Finite; use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::css::cssstylesheet::CSSStyleSheet; use crate::dom::css::stylesheetlist::{StyleSheetList, StyleSheetListOwner}; use crate::dom::customelementregistry::CustomElementRegistry; use crate::dom::document::Document; use crate::dom::documentfragment::DocumentFragment; use crate::dom::documentorshadowroot::{ DocumentOrShadowRoot, ServoStylesheetInDocument, StylesheetSource, }; use crate::dom::element::Element; use crate::dom::html::htmlslotelement::HTMLSlotElement; use crate::dom::htmldetailselement::DetailsNameGroups; use crate::dom::iterators::ShadowIncluding; use crate::dom::node::virtualmethods::{VirtualMethods, vtable_for}; use crate::dom::node::{ BindContext, IsShadowTree, Node, NodeDamage, NodeFlags, NodeTraits, UnbindContext, VecPreOrderInsertionHelper, }; use crate::dom::sanitizer::Sanitizer; use crate::dom::trustedtypes::trustedhtml::TrustedHTML; use crate::dom::types::EventTarget; use crate::dom::window::Window; use crate::stylesheet_set::StylesheetSetRef; /// Whether a shadow root hosts an User Agent widget. #[derive(JSTraceable, MallocSizeOf, PartialEq)] pub(crate) enum IsUserAgentWidget { No, Yes, } /// <https://dom.spec.whatwg.org/#interface-shadowroot> #[dom_struct] pub(crate) struct ShadowRoot { /// The [`DocumentFragment`] that this [`ShadowRoot`] inherits from. document_fragment: DocumentFragment, document_or_shadow_root: DocumentOrShadowRoot, document: Dom<Document>, /// List of author styles associated with nodes in this shadow tree. #[custom_trace] author_styles: DomRefCell<AuthorStyles<ServoStylesheetInDocument>>, stylesheet_list: MutNullableDom<StyleSheetList>, window: Dom<Window>, /// <https://dom.spec.whatwg.org/#dom-shadowroot-mode> mode: ShadowRootMode, /// <https://dom.spec.whatwg.org/#dom-shadowroot-slotassignment> slot_assignment_mode: SlotAssignmentMode, /// <https://dom.spec.whatwg.org/#dom-shadowroot-clonable> clonable: bool, /// <https://dom.spec.whatwg.org/#shadowroot-available-to-element-internals> available_to_element_internals: Cell<bool>, slots: DomRefCell<HashMap<DOMString, Vec<Dom<HTMLSlotElement>>>>, is_user_agent_widget: bool, /// <https://dom.spec.whatwg.org/#shadowroot-declarative> declarative: Cell<bool>, /// <https://dom.spec.whatwg.org/#shadowroot-serializable> serializable: Cell<bool>, /// <https://dom.spec.whatwg.org/#shadowroot-delegates-focus> delegates_focus: Cell<bool>, /// The constructed stylesheet that is adopted by this [ShadowRoot]. /// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets> adopted_stylesheets: DomRefCell<Vec<Dom<CSSStyleSheet>>>, /// Cached frozen array of [`Self::adopted_stylesheets`] #[ignore_malloc_size_of = "mozjs"] adopted_stylesheets_frozen_types: CachedFrozenArray, details_name_groups: DomRefCell<Option<DetailsNameGroups>>, } impl ShadowRoot { #[cfg_attr(crown, expect(crown::unrooted_must_root))] fn new_inherited( host: &Element, document: &Document, mode: ShadowRootMode, slot_assignment_mode: SlotAssignmentMode, clonable: bool, is_user_agent_widget: IsUserAgentWidget, ) -> ShadowRoot { let document_fragment = DocumentFragment::new_inherited(document, Some(host)); let node = document_fragment.upcast::<Node>(); node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, true); node.set_flag( NodeFlags::IS_CONNECTED, host.upcast::<Node>().is_connected(), ); ShadowRoot { document_fragment, document_or_shadow_root: DocumentOrShadowRoot::new(document.window()), document: Dom::from_ref(document), author_styles: DomRefCell::new(AuthorStyles::new()), stylesheet_list: MutNullableDom::new(None), window: Dom::from_ref(document.window()), mode, slot_assignment_mode, clonable, available_to_element_internals: Cell::new(false), slots: Default::default(), is_user_agent_widget: is_user_agent_widget == IsUserAgentWidget::Yes, declarative: Cell::new(false), serializable: Cell::new(false), delegates_focus: Cell::new(false), adopted_stylesheets: Default::default(), adopted_stylesheets_frozen_types: CachedFrozenArray::new(), details_name_groups: Default::default(), } } pub(crate) fn new( cx: &mut JSContext, host: &Element, document: &Document, mode: ShadowRootMode, slot_assignment_mode: SlotAssignmentMode, clonable: bool, is_user_agent_widget: IsUserAgentWidget, ) -> DomRoot<ShadowRoot> { reflect_dom_object_with_cx( Box::new(ShadowRoot::new_inherited( host, document, mode, slot_assignment_mode, clonable, is_user_agent_widget, )), document.window(), cx, ) } pub(crate) fn host_unrooted<'a>(&self, no_gc: &'a NoGC) -> UnrootedDom<'a, Element> { self.upcast::<DocumentFragment>() .host_unrooted(no_gc) .expect("ShadowRoot always has an element as host") } pub(crate) fn owner_doc(&self) -> &Document { &self.document } pub(crate) fn stylesheet_count(&self) -> usize { self.author_styles.borrow().stylesheets.len() } pub(crate) fn stylesheet_at( &self, cx: &mut JSContext, index: usize, ) -> Option<DomRoot<CSSStyleSheet>> { let stylesheets = &self.author_styles.borrow().stylesheets; stylesheets .get(index) .and_then(|s| s.owner.get_cssom_object(cx)) } /// Add a stylesheet owned by `owner_node` to the list of shadow root sheets, in the /// correct tree position. Additionally, ensure that owned stylesheet is inserted before /// any constructed stylesheet. /// /// <https://drafts.csswg.org/cssom/#documentorshadowroot-final-css-style-sheets> #[cfg_attr(crown, expect(crown::unrooted_must_root))] // Owner needs to be rooted already necessarily. pub(crate) fn add_owned_stylesheet(&self, owner_node: &Element, sheet: Arc<Stylesheet>) { let stylesheets = &mut self.author_styles.borrow_mut().stylesheets; // FIXME(stevennovaryo): This is almost identical with the one in Document::add_stylesheet. let insertion_point = stylesheets .iter() .find(|sheet_in_shadow| { match &sheet_in_shadow.owner { StylesheetSource::Element(other_node) => { owner_node.upcast::<Node>().is_before(other_node.upcast()) }, // Non-constructed stylesheet should be ordered before the // constructed ones. StylesheetSource::Constructed(_) => true, } }) .cloned(); DocumentOrShadowRoot::add_stylesheet( StylesheetSource::Element(Dom::from_ref(owner_node)), StylesheetSetRef::Author(stylesheets), sheet, insertion_point, self.document.style_shared_author_lock(), ); } /// Append a constructed stylesheet to the back of shadow root stylesheet set. #[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn append_constructed_stylesheet(&self, cssom_stylesheet: &CSSStyleSheet) { debug_assert!(cssom_stylesheet.is_constructed()); let stylesheets = &mut self.author_styles.borrow_mut().stylesheets; let sheet = cssom_stylesheet.style_stylesheet().clone(); let insertion_point = stylesheets.iter().last().cloned(); DocumentOrShadowRoot::add_stylesheet( StylesheetSource::Constructed(Dom::from_ref(cssom_stylesheet)), StylesheetSetRef::Author(stylesheets), sheet, insertion_point, self.document.style_shared_author_lock(), ); } /// Remove a stylesheet owned by `owner` from the list of shadow root sheets. #[cfg_attr(crown, expect(crown::unrooted_must_root))] // Owner needs to be rooted already necessarily. pub(crate) fn remove_stylesheet(&self, owner: StylesheetSource, s: &Arc<Stylesheet>) { DocumentOrShadowRoot::remove_stylesheet( owner, s, StylesheetSetRef::Author(&mut self.author_styles.borrow_mut().stylesheets), ) } pub(crate) fn invalidate_stylesheets(&self, no_gc: &NoGC) { self.document.invalidate_shadow_roots_stylesheets(); self.author_styles.borrow_mut().stylesheets.force_dirty(); // Mark the host element dirty so a reflow will be performed. self.Host().upcast::<Node>().dirty(no_gc, NodeDamage::Style); // Also mark the host element with `RestyleHint::restyle_subtree` so a reflow // can traverse into the shadow tree. let mut restyle = self.document.ensure_pending_restyle(&self.Host()); restyle.hint.insert(RestyleHint::restyle_subtree()); } /// Remove any existing association between the provided id and any elements /// in this shadow tree. pub(crate) fn unregister_element_id(&self, id: &Atom) { self.document_fragment.id_map().remove(id); } /// Associate an element present in this shadow tree with the provided id. pub(crate) fn register_element_id(&self, element: &Element, id: &Atom) { self.document_fragment.id_map().add(id, element) } pub(crate) fn register_slot(&self, slot: &HTMLSlotElement) { debug!("Registering slot with name={:?}", slot.Name().str()); let mut slots = self.slots.borrow_mut(); let slots_with_the_same_name = slots.entry(slot.Name()).or_default(); // Insert the slot before the first element that comes after it in tree order slots_with_the_same_name.insert_pre_order(slot, self.upcast::<Node>()); } pub(crate) fn unregister_slot(&self, name: DOMString, slot: &HTMLSlotElement) { debug!("Unregistering slot with name={:?}", name.str()); let mut slots = self.slots.borrow_mut(); let Entry::Occupied(mut entry) = slots.entry(name) else { panic!("slot is not registered"); }; entry.get_mut().retain(|s| slot != &**s); } /// Find the first slot with the given name among this root's descendants in tree order pub(crate) fn slot_for_name(&self, name: &DOMString) -> Option<DomRoot<HTMLSlotElement>> { self.slots .borrow() .get(name) .and_then(|slots| slots.first()) .map(|slot| slot.as_rooted()) } pub(crate) fn has_slot_descendants(&self) -> bool { !self.slots.borrow().is_empty() } pub(crate) fn set_available_to_element_internals(&self, value: bool) { self.available_to_element_internals.set(value); } /// <https://dom.spec.whatwg.org/#shadowroot-available-to-element-internals> pub(crate) fn is_available_to_element_internals(&self) -> bool { self.available_to_element_internals.get() } pub(crate) fn is_user_agent_widget(&self) -> bool { self.is_user_agent_widget } pub(crate) fn set_declarative(&self, declarative: bool) { self.declarative.set(declarative); } pub(crate) fn is_declarative(&self) -> bool { self.declarative.get() } pub(crate) fn shadow_root_mode(&self) -> ShadowRootMode { self.mode } pub(crate) fn set_serializable(&self, serializable: bool) { self.serializable.set(serializable); } pub(crate) fn set_delegates_focus(&self, delegates_focus: bool) { self.delegates_focus.set(delegates_focus); } pub(crate) fn details_name_groups(&self) -> RefMut<'_, DetailsNameGroups> { RefMut::map( self.details_name_groups.borrow_mut(), |details_name_groups| details_name_groups.get_or_insert_default(), ) } pub(crate) fn custom_element_registry(&self) -> Option<DomRoot<CustomElementRegistry>> { self.document_or_shadow_root.custom_element_registry() } pub(crate) fn set_custom_element_registry(&self, registry: &CustomElementRegistry) { self.document_or_shadow_root .set_custom_element_registry(Some(registry)); } } impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot { /// <https://html.spec.whatwg.org/multipage/#dom-document-activeelement> fn GetActiveElement(&self) -> Option<DomRoot<Element>> { self.document_or_shadow_root.active_element(self.upcast()) } /// <https://dom.spec.whatwg.org/#dom-documentorshadowroot-customelementregistry> fn GetCustomElementRegistry(&self) -> Option<DomRoot<CustomElementRegistry>> { self.custom_element_registry() } /// <https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint> fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<DomRoot<Element>> { // Return the result of running the retargeting algorithm with context object // and the original result as input. match self.document_or_shadow_root.element_from_point( self.upcast(), x, y, None, self.document.has_browsing_context(), ) { Some(e) => { let retargeted_node = e.upcast::<EventTarget>().retarget(self.upcast()); retargeted_node.downcast::<Element>().map(DomRoot::from_ref) }, None => None, } } /// <https://drafts.csswg.org/cssom-view/#dom-document-elementsfrompoint> fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<DomRoot<Element>> { // Return the result of running the retargeting algorithm with context object // and the original result as input let mut elements = Vec::new(); for e in self .document_or_shadow_root .elements_from_point( self.upcast(), x, y, None, self.document.has_browsing_context(), ) .iter() { let retargeted_node = e.upcast::<EventTarget>().retarget(self.upcast()); if let Some(element) = retargeted_node.downcast::<Element>().map(DomRoot::from_ref) { elements.push(element); } } elements } /// <https://dom.spec.whatwg.org/#dom-shadowroot-mode> fn Mode(&self) -> ShadowRootMode { self.mode } /// <https://dom.spec.whatwg.org/#dom-delegates-focus> fn DelegatesFocus(&self) -> bool { self.delegates_focus.get() } /// <https://dom.spec.whatwg.org/#dom-shadowroot-clonable> fn Clonable(&self) -> bool { self.clonable } /// <https://dom.spec.whatwg.org/#dom-serializable> fn Serializable(&self) -> bool { self.serializable.get() } /// <https://dom.spec.whatwg.org/#dom-shadowroot-host> fn Host(&self) -> DomRoot<Element> { self.upcast::<DocumentFragment>() .host() .expect("ShadowRoot always has an element as host") } /// <https://drafts.csswg.org/cssom/#dom-document-stylesheets> fn StyleSheets(&self, cx: &mut JSContext) -> DomRoot<StyleSheetList> { self.stylesheet_list.or_init(|| { StyleSheetList::new( cx, &self.window, StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)), ) }) } /// <https://html.spec.whatwg.org/multipage/#dom-shadowroot-gethtml> fn GetHTML(&self, cx: &mut JSContext, options: &GetHTMLOptions) -> DOMString { // > ShadowRoot's getHTML(options) method steps are to return the result of HTML fragment serialization // > algorithm with this, options["serializableShadowRoots"], and options["shadowRoots"]. self.upcast::<Node>().html_serialize( cx, TraversalScope::ChildrenOnly(None), options.serializableShadowRoots, options.shadowRoots.clone(), ) } /// <https://html.spec.whatwg.org/multipage/#dom-shadowroot-innerhtml> fn GetInnerHTML(&self, cx: &mut JSContext) -> Fallible<TrustedHTMLOrNullIsEmptyString> { // ShadowRoot's innerHTML getter steps are to return the result of running fragment serializing // algorithm steps with this and true. self.upcast::<Node>() .fragment_serialization_algorithm(cx, true) .map(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString) } /// <https://html.spec.whatwg.org/multipage/#dom-shadowroot-innerhtml> fn SetInnerHTML( &self, cx: &mut JSContext, value: TrustedHTMLOrNullIsEmptyString, ) -> ErrorResult { // Step 1. Let compliantString be the result of invoking the Get Trusted Type compliant string algorithm // with TrustedHTML, this's relevant global object, the given value, "ShadowRoot innerHTML", and "script". let value = TrustedHTML::get_trusted_type_compliant_string( cx, &self.owner_global(), value.convert(), "ShadowRoot innerHTML", )?; // Step 2. Let context be this's host. let context = self.Host(); // Step 3. Let fragment be the result of invoking the fragment parsing algorithm steps with context and // compliantString. // // NOTE: The spec doesn't strictly tell us to bail out here, but // we can't continue if parsing failed let frag = context.parse_fragment(value, cx)?; // Step 4. Replace all with fragment within this. Node::replace_all(cx, Some(frag.upcast()), self.upcast()); Ok(()) } /// <https://dom.spec.whatwg.org/#dom-shadowroot-slotassignment> fn SlotAssignment(&self) -> SlotAssignmentMode { self.slot_assignment_mode } /// <https://html.spec.whatwg.org/multipage/#dom-shadowroot-sethtmlunsafe> fn SetHTMLUnsafe( &self, cx: &mut JSContext, value: TrustedHTMLOrString, options: &SetHTMLUnsafeOptions, ) -> ErrorResult { // Step 1. Let compliantHTML be the result of invoking the // Get Trusted Type compliant string algorithm with TrustedHTML, // this's relevant global object, html, "ShadowRoot setHTMLUnsafe", and "script". let compliant_html = TrustedHTML::get_trusted_type_compliant_string( cx, &self.owner_global(), value, "ShadowRoot setHTMLUnsafe", )?; // Step 2. Set and filter HTML given this, this's shadow host, compliantHTML, options, and // false. Sanitizer::set_and_filter_html( cx, self.upcast(), &self.Host(), compliant_html, options, false, )?; Ok(()) } /// <https://wicg.github.io/sanitizer-api/#dom-shadowroot-sethtml> fn SetHTML( &self, cx: &mut JSContext, html: DOMString, options: &SetHTMLOptions, ) -> ErrorResult { // Step 1. Set and filter HTML using this (as target), this (as context element), html, // options, and true. // NOTE: The specification text is incorrect. We should use this's shadow host as context // element. let target = self.upcast::<Node>(); let context_element = self.Host(); Sanitizer::set_and_filter_html(cx, target, &context_element, html, options, true) } // https://dom.spec.whatwg.org/#dom-shadowroot-onslotchange event_handler!(slotchange, GetOnslotchange, SetOnslotchange); /// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets> fn AdoptedStyleSheets(&self, cx: &mut JSContext, retval: MutableHandleValue) { self.adopted_stylesheets_frozen_types.get_or_init( cx, || { self.adopted_stylesheets .borrow() .clone() .iter() .map(|sheet| sheet.as_rooted()) .collect() }, retval, ); } /// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets> fn SetAdoptedStyleSheets(&self, cx: &mut JSContext, val: HandleValue) -> ErrorResult { let result = DocumentOrShadowRoot::set_adopted_stylesheet_from_jsval( cx, &self.adopted_stylesheets, val, &StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)), ); if result.is_ok() { if self.author_styles.borrow().stylesheets.dirty() { self.invalidate_stylesheets(cx.no_gc()); } // Clear the FrozenArray cache. self.adopted_stylesheets_frozen_types.clear(); } result } /// <https://fullscreen.spec.whatwg.org/#dom-document-fullscreenelement> fn GetFullscreenElement(&self) -> Option<DomRoot<Element>> { DocumentOrShadowRoot::get_fullscreen_element( self.upcast::<Node>(), self.document.fullscreen_element(), ) } } impl VirtualMethods for ShadowRoot { fn super_type(&self) -> Option<&dyn VirtualMethods> { Some(self.upcast::<DocumentFragment>() as &dyn VirtualMethods) } fn bind_to_tree(&self, cx: &mut JSContext, context: &BindContext) { if let Some(s) = self.super_type() { s.bind_to_tree(cx, context); } // TODO(stevennovaryo): Handle adoptedStylesheet to deal with different // constructor document. if context.tree_connected { let document = self.owner_document(); document.register_shadow_root(self); } let shadow_root = self.upcast::<Node>(); shadow_root.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected); let inner_context = BindContext::new(shadow_root, IsShadowTree::Yes); // avoid iterate over the shadow root itself for node in shadow_root.traverse_preorder(ShadowIncluding::No).skip(1) { node.set_flag(NodeFlags::IS_CONNECTED, inner_context.tree_connected); // Out-of-document elements never have the descendants flag set debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS)); vtable_for(&node).bind_to_tree(cx, &inner_context); } } fn unbind_from_tree(&self, cx: &mut JSContext, context: &UnbindContext) { if let Some(s) = self.super_type() { s.unbind_from_tree(cx, context); } if context.tree_connected { let document = self.owner_document(); document.unregister_shadow_root(self); } } } impl<'dom> LayoutDom<'dom, ShadowRoot> { #[inline] pub(crate) fn get_host_for_layout(self) -> LayoutDom<'dom, Element> { self.upcast::<DocumentFragment>() .shadowroot_host_for_layout() } #[inline] #[expect(unsafe_code)] pub(crate) fn get_style_data_for_layout(self) -> &'dom CascadeData { fn is_sync<T: Sync>() {} let _ = is_sync::<CascadeData>; unsafe { &self.unsafe_get().author_styles.borrow_for_layout().data } } #[inline] pub(crate) fn is_user_agent_widget(&self) -> bool { self.unsafe_get().is_user_agent_widget() } // FIXME(nox): This uses the dreaded borrow_mut_for_layout so this should // probably be revisited. #[inline] #[expect(unsafe_code)] pub(crate) unsafe fn flush_stylesheets_for_layout( self, stylist: &mut Stylist, guard: &SharedRwLockReadGuard, ) { unsafe { debug_assert!(self.upcast::<Node>().get_flag(NodeFlags::IS_CONNECTED)); }; let author_styles = unsafe { self.unsafe_get().author_styles.borrow_mut_for_layout() }; if author_styles.stylesheets.dirty() { author_styles.flush(stylist, guard); } } } impl Convert<devtools_traits::ShadowRootMode> for ShadowRootMode { fn convert(self) -> devtools_traits::ShadowRootMode { match self { ShadowRootMode::Open => devtools_traits::ShadowRootMode::Open, ShadowRootMode::Closed => devtools_traits::ShadowRootMode::Closed, } } }