Files
servo/components/script/layout_dom/servo_layout_element.rs
Martin Robinson 5d28862c7b 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>
2026-04-14 10:35:00 +00:00

257 lines
8.5 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/. */
#![expect(unsafe_code)]
#![deny(missing_docs)]
use std::fmt;
use std::hash::Hash;
use html5ever::{LocalName, Namespace, local_name, ns};
use layout_api::{
LayoutDataTrait, LayoutElement, LayoutNode, LayoutNodeType, PseudoElementChain, StyleData,
};
use servo_arc::Arc;
use style::attr::AttrValue;
use style::context::SharedStyleContext;
use style::data::{ElementDataMut, ElementDataRef};
use style::properties::ComputedValues;
use style::selector_parser::{PseudoElement, PseudoElementCascadeType};
use style::stylist::RuleInclusion;
use crate::dom::bindings::root::LayoutDom;
use crate::dom::element::Element;
use crate::dom::node::{Node, NodeFlags};
use crate::layout_dom::{
ServoDangerousStyleElement, ServoDangerousStyleShadowRoot, ServoLayoutNode,
};
impl fmt::Debug for LayoutDom<'_, Element> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<{}", self.local_name())?;
if let Some(id) = unsafe { (*self.id_attribute()).as_ref() } {
write!(f, " id={id}")?;
}
write!(f, "> ({:#x})", self.upcast::<Node>().opaque().0)
}
}
/// An implementation of [`LayoutElement`] for Servo's `script` crate.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ServoLayoutElement<'dom> {
/// The wrapped private DOM Element.
pub(super) element: LayoutDom<'dom, Element>,
/// The possibly nested [`PseudoElementChain`] for this element.
pub(super) pseudo_element_chain: PseudoElementChain,
}
unsafe impl Send for ServoLayoutElement<'_> {}
unsafe impl Sync for ServoLayoutElement<'_> {}
impl<'dom> ServoLayoutElement<'dom> {
pub(super) fn is_html_element(&self) -> bool {
self.element.is_html_element()
}
/// 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)
}
}
impl<'dom> From<LayoutDom<'dom, Element>> for ServoLayoutElement<'dom> {
fn from(element: LayoutDom<'dom, Element>) -> Self {
Self {
element,
pseudo_element_chain: Default::default(),
}
}
}
impl<'dom> LayoutElement<'dom> for ServoLayoutElement<'dom> {
type ConcreteLayoutNode = ServoLayoutNode<'dom>;
type ConcreteStyleElement = ServoDangerousStyleElement<'dom>;
fn with_pseudo(&self, pseudo_element: PseudoElement) -> Option<Self> {
if pseudo_element.is_eager() &&
self.element_data()
.styles
.pseudos
.get(&pseudo_element)
.is_none()
{
return None;
}
if pseudo_element == PseudoElement::DetailsContent &&
(self.element.local_name() != &local_name!("details") ||
self.element.namespace() != &ns!(html) ||
self.attribute(&ns!(), &local_name!("open")).is_none())
{
return None;
}
// These pseudo-element type cannot be nested.
if !self.pseudo_element_chain.is_empty() {
assert!(!pseudo_element.is_eager());
assert!(pseudo_element != PseudoElement::DetailsContent);
}
Some(Self {
element: self.element,
pseudo_element_chain: self.pseudo_element_chain.with_pseudo(pseudo_element),
})
}
fn pseudo_element_chain(&self) -> PseudoElementChain {
self.pseudo_element_chain
}
fn as_node(&self) -> ServoLayoutNode<'dom> {
ServoLayoutNode {
node: self.element.upcast(),
pseudo_element_chain: self.pseudo_element_chain,
}
}
fn initialize_style_and_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
if self.element.style_data().is_none() {
unsafe { self.element.initialize_style_data() };
}
let node = self.element.upcast::<Node>();
if node.layout_data().is_none() {
unsafe { node.initialize_layout_data(Box::<RequestedLayoutDataType>::default()) };
}
}
fn unset_snapshot_flags(&self) {
unsafe {
self.as_node()
.node
.set_flag(NodeFlags::HAS_SNAPSHOT | NodeFlags::HANDLED_SNAPSHOT, false);
}
}
fn set_has_snapshot(&self) {
unsafe {
self.as_node().node.set_flag(NodeFlags::HAS_SNAPSHOT, true);
}
}
fn style_data(self) -> Option<&'dom StyleData> {
self.element.style_data()
}
fn element_data(&self) -> ElementDataRef<'dom> {
self.style_data()
.expect("Unstyled layout node?")
.element_data
.borrow()
}
fn element_data_mut(&self) -> ElementDataMut<'dom> {
self.style_data()
.expect("Unstyled layout node?")
.element_data
.borrow_mut()
}
fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
let get_style_for_pseudo_element =
|data: &ElementDataRef<'_>,
base_style: &Arc<ComputedValues>,
pseudo_element: PseudoElement| {
// Precompute non-eagerly-cascaded pseudo-element styles if not
// cached before.
match pseudo_element.cascade_type() {
// Already computed during the cascade.
PseudoElementCascadeType::Eager => {
data.styles.pseudos.get(&pseudo_element).unwrap().clone()
},
PseudoElementCascadeType::Precomputed => context
.stylist
.precomputed_values_for_pseudo::<Self::ConcreteStyleElement>(
&context.guards,
&pseudo_element,
Some(base_style),
),
PseudoElementCascadeType::Lazy => {
context
.stylist
.lazily_compute_pseudo_element_style(
&context.guards,
unsafe { self.dangerous_style_element() },
&pseudo_element,
RuleInclusion::All,
base_style,
/* is_probe = */ false,
/* matching_func = */ None,
)
.unwrap()
},
}
};
let data = self.element_data();
let element_style = data.styles.primary();
let pseudo_element_chain = self.pseudo_element_chain();
let primary_pseudo_style = match pseudo_element_chain.primary {
Some(pseudo_element) => {
get_style_for_pseudo_element(&data, element_style, pseudo_element)
},
None => return element_style.clone(),
};
match pseudo_element_chain.secondary {
Some(pseudo_element) => {
get_style_for_pseudo_element(&data, &primary_pseudo_style, pseudo_element)
},
None => primary_pseudo_style,
}
}
fn type_id(&self) -> Option<LayoutNodeType> {
self.as_node().type_id()
}
unsafe fn dangerous_style_element(self) -> ServoDangerousStyleElement<'dom> {
self.element.into()
}
fn local_name(&self) -> &LocalName {
self.element.local_name()
}
fn attribute(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
self.element.get_attr_for_layout(namespace, name)
}
fn attribute_as_str<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str> {
self.element.get_attr_val_for_layout(namespace, name)
}
fn is_shadow_host(&self) -> bool {
self.element.get_shadow_root_for_layout().is_some()
}
fn is_body_element_of_html_element_root(&self) -> bool {
self.element.is_body_element_of_html_element_root()
}
fn is_html_element_in_html_document(&self) -> bool {
self.element.is_html_element() &&
self.element
.upcast::<Node>()
.owner_doc_for_layout()
.is_html_document_for_layout()
}
fn is_root(&self) -> bool {
self.element.is_root()
}
}