script: Expose a LayoutNode::is_root_of_user_agent_widget method (#44197)

The main goal here is to have layout not depend on `ServoLayoutElement`
directly and instead use the layout DOM interface exposed by
`layout-api`. 

This change allows layout to determine if replaced content
is the root of a user agent widget without having to access
`ServoDangerousStyleShadowRoot` which isn't really safe to use in layout
code. `LayoutElement::shadow_root()` is now no longer exposed to layout.

Testing: This should not change behavior, so should be covered by
existing test.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson
2026-04-14 12:35:00 +02:00
committed by GitHub
parent 4ff29fecc8
commit 5d28862c7b
7 changed files with 45 additions and 37 deletions

View File

@@ -150,34 +150,33 @@ impl IndependentFormattingContext {
let non_replaced_contents = match contents {
Contents::Replaced(contents) => {
base_fragment_info.flags.insert(FragmentFlags::IS_REPLACED);
// Some replaced elements can have inner widgets, e.g. `<video controls>`.
let widget = Some(node_and_style_info.node)
.filter(|node| node.pseudo_element_chain().is_empty())
.and_then(|node| node.as_element())
.and_then(|element| element.shadow_root())
.is_some_and(|shadow_root| shadow_root.is_ua_widget())
.then(|| {
let widget_info = node_and_style_info
.with_pseudo_element(context, PseudoElement::ServoAnonymousBox)
.expect("Should always be able to construct info for anonymous boxes.");
// Use a block formatting context for the widget, since the display inside is always flow.
let widget_contents = IndependentFormattingContextContents::Flow(
BlockFormattingContext::construct(
context,
&widget_info,
NonReplacedContents::OfElement,
propagated_data,
false, /* is_list_item */
),
);
let widget_base =
LayoutBoxBase::new((&widget_info).into(), widget_info.style);
ArcRefCell::new(IndependentFormattingContext::new(
widget_base,
widget_contents,
let node = node_and_style_info.node;
let widget = (node.pseudo_element_chain().is_empty() &&
node.is_root_of_user_agent_widget())
.then(|| {
let widget_info = node_and_style_info
.with_pseudo_element(context, PseudoElement::ServoAnonymousBox)
.expect("Should always be able to construct info for anonymous boxes.");
// Use a block formatting context for the widget, since the display inside is always flow.
let widget_contents = IndependentFormattingContextContents::Flow(
BlockFormattingContext::construct(
context,
&widget_info,
NonReplacedContents::OfElement,
propagated_data,
))
});
false, /* is_list_item */
),
);
let widget_base = LayoutBoxBase::new((&widget_info).into(), widget_info.style);
ArcRefCell::new(IndependentFormattingContext::new(
widget_base,
widget_contents,
propagated_data,
))
});
return IndependentFormattingContextContents::Replaced(contents, widget);
},
Contents::Widget(non_replaced_contents) => {

View File

@@ -2368,6 +2368,14 @@ impl<'dom> LayoutDom<'dom, Node> {
pub(crate) fn is_in_ua_widget(&self) -> bool {
self.unsafe_get().is_in_ua_widget()
}
pub(crate) fn is_root_of_user_agent_widget(&self) -> bool {
self.downcast::<Element>().is_some_and(|element| {
element
.get_shadow_root_for_layout()
.is_some_and(|shadow_root| shadow_root.is_user_agent_widget())
})
}
}
//

View File

@@ -654,7 +654,7 @@ impl<'dom> LayoutDom<'dom, ShadowRoot> {
}
#[inline]
pub(crate) fn is_ua_widget(&self) -> bool {
pub(crate) fn is_user_agent_widget(&self) -> bool {
self.unsafe_get().is_user_agent_widget()
}

View File

@@ -54,11 +54,3 @@ impl<'dom> TShadowRoot for ServoDangerousStyleShadowRoot<'dom> {
Some(self.shadow_root.get_style_data_for_layout())
}
}
impl<'dom> ServoDangerousStyleShadowRoot<'dom> {
/// Whether or not this [`ServoDangerousStyleShadowRoot`] is the root
/// of a user agent widget.
pub fn is_ua_widget(&self) -> bool {
self.shadow_root.is_ua_widget()
}
}

View File

@@ -54,8 +54,10 @@ impl<'dom> ServoLayoutElement<'dom> {
self.element.is_html_element()
}
/// The shadow root this element is a host of.
pub fn shadow_root(&self) -> Option<ServoDangerousStyleShadowRoot<'dom>> {
/// The shadow root that this [`ServoLayoutElement`] is a host of, if it has one.
///
/// Note: This should *not* be exposed to layout as it allows access to an ancestor element.
pub(super) fn shadow_root(&self) -> Option<ServoDangerousStyleShadowRoot<'dom>> {
self.element.get_shadow_root_for_layout().map(Into::into)
}
}

View File

@@ -318,6 +318,10 @@ impl<'dom> LayoutNode<'dom> for ServoLayoutNode<'dom> {
fn is_single_line_text_input(&self) -> bool {
self.pseudo_element_chain.is_empty() && self.node.is_text_container_of_single_line_input()
}
fn is_root_of_user_agent_widget(&self) -> bool {
self.node.is_root_of_user_agent_widget()
}
}
impl NodeInfo for ServoLayoutNode<'_> {

View File

@@ -221,6 +221,9 @@ pub trait LayoutNode<'dom>: Copy + Debug + NodeInfo + Send + Sync {
/// <https://html.spec.whatwg.org/multipage/#the-input-element-as-a-text-entry-widget>
fn is_single_line_text_input(&self) -> bool;
/// Whether or not this [`LayoutNode`] is in a user agent widget shadow DOM.
fn is_root_of_user_agent_widget(&self) -> bool;
/// Set whether or not this node has an active pseudo-element style with a `content`
/// attribute that uses `attr`.
fn set_uses_content_attribute_with_attr(&self, _uses_content_attribute_with_attr: bool);