Files
servo/components/script/dom/shadowroot.rs
elomscansio aa7eca43b7 script: propagate VirtualMethods::unbind_from_tree with &mut JSContext (#44422)
Propagate `&mut JSContext` in `VirtualMethods::unbind_from_tree`

Testing: Successful build is enough
Fixes: #42837

---------

Signed-off-by: Emmanuel Paul Elom <elomemmanuel007@gmail.com>
2026-04-23 14:09:11 +00:00

688 lines
25 KiB
Rust

/* 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::rust::{HandleValue, MutableHandleValue};
use script_bindings::error::{ErrorResult, Fallible};
use script_bindings::script_runtime::JSContext;
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::cell::{DomRefCell, RefMut};
use crate::dom::bindings::codegen::Bindings::ElementBinding::GetHTMLOptions;
use crate::dom::bindings::codegen::Bindings::HTMLSlotElementBinding::HTMLSlotElement_Binding::HTMLSlotElementMethods;
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::reflector::reflect_dom_object;
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::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::node::{
BindContext, IsShadowTree, Node, NodeDamage, NodeFlags, NodeTraits, ShadowIncluding,
UnbindContext, VecPreOrderInsertionHelper,
};
use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
use crate::dom::types::EventTarget;
use crate::dom::virtualmethods::{VirtualMethods, vtable_for};
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
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 {
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(
host: &Element,
document: &Document,
mode: ShadowRootMode,
slot_assignment_mode: SlotAssignmentMode,
clonable: bool,
is_user_agent_widget: IsUserAgentWidget,
can_gc: CanGc,
) -> DomRoot<ShadowRoot> {
reflect_dom_object(
Box::new(ShadowRoot::new_inherited(
host,
document,
mode,
slot_assignment_mode,
clonable,
is_user_agent_widget,
)),
document.window(),
can_gc,
)
}
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, index: usize) -> Option<DomRoot<CSSStyleSheet>> {
let stylesheets = &self.author_styles.borrow().stylesheets;
stylesheets
.get(index)
.and_then(|s| s.owner.get_cssom_object())
}
/// 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();
if self.document.has_browsing_context() {
let document_context = self.window.web_font_context();
self.window
.layout_mut()
.load_web_fonts_from_stylesheet(&sheet, &document_context);
}
DocumentOrShadowRoot::add_stylesheet(
StylesheetSource::Element(Dom::from_ref(owner_node)),
StylesheetSetRef::Author(stylesheets),
sheet,
insertion_point,
self.document.style_shared_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();
if self.document.has_browsing_context() {
let document_context = self.window.web_font_context();
self.window
.layout_mut()
.load_web_fonts_from_stylesheet(&sheet, &document_context);
}
DocumentOrShadowRoot::add_stylesheet(
StylesheetSource::Constructed(Dom::from_ref(cssom_stylesheet)),
StylesheetSetRef::Author(stylesheets),
sheet,
insertion_point,
self.document.style_shared_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) {
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(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, to_unregister: &Element, id: Atom, _can_gc: CanGc) {
self.document_or_shadow_root.unregister_named_element(
self.document_fragment.id_map(),
to_unregister,
&id,
);
}
/// Associate an element present in this shadow tree with the provided id.
pub(crate) fn register_element_id(&self, element: &Element, id: Atom, _can_gc: CanGc) {
let root = self
.upcast::<Node>()
.inclusive_ancestors(ShadowIncluding::No)
.last()
.unwrap();
self.document_or_shadow_root.register_named_element(
self.document_fragment.id_map(),
element,
&id,
root,
);
}
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(),
)
}
}
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://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(
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(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) -> DomRoot<StyleSheetList> {
self.stylesheet_list.or_init(|| {
StyleSheetList::new(
&self.window,
StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)),
CanGc::deprecated_note(),
)
})
}
/// <https://html.spec.whatwg.org/multipage/#dom-shadowroot-gethtml>
fn GetHTML(&self, cx: &mut js::context::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 js::context::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 js::context::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 js::context::JSContext,
value: TrustedHTMLOrString,
) -> 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 value = TrustedHTML::get_trusted_type_compliant_string(
cx,
&self.owner_global(),
value,
"ShadowRoot setHTMLUnsafe",
)?;
// Step 2. Unsafely set HTMl given this, this's shadow host, and complaintHTML
let target = self.upcast::<Node>();
let context_element = self.Host();
Node::unsafely_set_html(target, &context_element, value, cx);
Ok(())
}
// https://dom.spec.whatwg.org/#dom-shadowroot-onslotchange
event_handler!(onslotchange, GetOnslotchange, SetOnslotchange);
/// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
fn AdoptedStyleSheets(&self, context: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
self.adopted_stylesheets_frozen_types.get_or_init(
|| {
self.adopted_stylesheets
.borrow()
.clone()
.iter()
.map(|sheet| sheet.as_rooted())
.collect()
},
context,
retval,
can_gc,
);
}
/// <https://drafts.csswg.org/cssom/#dom-documentorshadowroot-adoptedstylesheets>
fn SetAdoptedStyleSheets(
&self,
context: JSContext,
val: HandleValue,
can_gc: CanGc,
) -> ErrorResult {
let result = DocumentOrShadowRoot::set_adopted_stylesheet_from_jsval(
context,
self.adopted_stylesheets.borrow_mut().as_mut(),
val,
&StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)),
can_gc,
);
// If update is successful, clear the FrozenArray cache.
if result.is_ok() {
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 js::context::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 js::context::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,
}
}
}