#![expect(unsafe_code)]
use std::borrow::Cow;
use std::fmt;
use std::iter::FusedIterator;
use layout_api::wrapper_traits::{
LayoutDataTrait, LayoutNode, PseudoElementChain, SharedSelection, ThreadSafeLayoutElement,
ThreadSafeLayoutNode,
};
use layout_api::{
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType,
SVGElementData, StyleData, TrustedNodeAddress,
};
use net_traits::image_cache::Image;
use pixels::ImageMetadata;
use script_bindings::error::Fallible;
use selectors::Element as _;
use servo_arc::Arc;
use servo_base::id::{BrowsingContextId, PipelineId};
use servo_url::ServoUrl;
use style;
use style::context::SharedStyleContext;
use style::dom::{NodeInfo, TElement, TNode, TShadowRoot};
use style::dom_apis::{MayUseInvalidation, SelectorQuery, query_selector};
use style::properties::ComputedValues;
use style::selector_parser::{PseudoElement, SelectorParser};
use style::stylesheets::UrlExtraData;
use url::Url;
use super::{
ServoLayoutDocument, ServoLayoutElement, ServoShadowRoot, ServoThreadSafeLayoutElement,
};
use crate::dom::bindings::error::Error;
use crate::dom::bindings::inheritance::NodeTypeId;
use crate::dom::bindings::root::LayoutDom;
use crate::dom::element::{Element, LayoutElementHelpers};
use crate::dom::node::{LayoutNodeHelpers, Node, NodeFlags, NodeTypeIdWrapper};
#[derive(Clone, Copy, PartialEq)]
#[repr(transparent)]
pub struct ServoLayoutNode<'dom> {
pub(super) node: LayoutDom<'dom, Node>,
}
unsafe impl Send for ServoLayoutNode<'_> {}
unsafe impl Sync for ServoLayoutNode<'_> {}
impl fmt::Debug for ServoLayoutNode<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(el) = self.as_element() {
el.fmt(f)
} else if self.is_text_node() {
write!(f, "<text node> ({:#x})", self.opaque().0)
} else {
write!(f, "<non-text node> ({:#x})", self.opaque().0)
}
}
}
impl<'dom> ServoLayoutNode<'dom> {
pub(crate) fn from_layout_dom(node: LayoutDom<'dom, Node>) -> Self {
ServoLayoutNode { node }
}
pub unsafe fn new(address: &TrustedNodeAddress) -> Self {
let node = unsafe { LayoutDom::from_trusted_node_address(*address) };
ServoLayoutNode::from_layout_dom(node)
}
pub(super) fn script_type_id(&self) -> NodeTypeId {
self.node.type_id_for_layout()
}
pub(crate) fn to_layout_dom(self) -> LayoutDom<'dom, Node> {
self.node
}
pub(crate) fn assigned_slot(self) -> Option<ServoLayoutElement<'dom>> {
self.node
.assigned_slot_for_layout()
.as_ref()
.map(LayoutDom::upcast)
.map(ServoLayoutElement::from_layout_dom)
}
pub(crate) fn scope_match_a_selectors_string<Query>(
self,
document_url: Arc<Url>,
selector: &str,
) -> Fallible<Query::Output>
where
Query: SelectorQuery<ServoLayoutElement<'dom>>,
Query::Output: Default,
{
let mut result = Query::Output::default();
let selector_or_error =
SelectorParser::parse_author_origin_no_namespace(selector, &UrlExtraData(document_url));
let Ok(selector_list) = selector_or_error else {
return Err(Error::Syntax(None));
};
query_selector::<ServoLayoutElement<'dom>, Query>(
self,
&selector_list,
&mut result,
MayUseInvalidation::No,
);
Ok(result)
}
}
impl style::dom::NodeInfo for ServoLayoutNode<'_> {
fn is_element(&self) -> bool {
self.node.is_element_for_layout()
}
fn is_text_node(&self) -> bool {
self.node.is_text_node_for_layout()
}
}
impl<'dom> style::dom::TNode for ServoLayoutNode<'dom> {
type ConcreteDocument = ServoLayoutDocument<'dom>;
type ConcreteElement = ServoLayoutElement<'dom>;
type ConcreteShadowRoot = ServoShadowRoot<'dom>;
fn parent_node(&self) -> Option<Self> {
self.node.parent_node_ref().map(Self::from_layout_dom)
}
fn first_child(&self) -> Option<Self> {
self.node.first_child_ref().map(Self::from_layout_dom)
}
fn last_child(&self) -> Option<Self> {
self.node.last_child_ref().map(Self::from_layout_dom)
}
fn prev_sibling(&self) -> Option<Self> {
self.node.prev_sibling_ref().map(Self::from_layout_dom)
}
fn next_sibling(&self) -> Option<Self> {
self.node.next_sibling_ref().map(Self::from_layout_dom)
}
fn owner_doc(&self) -> Self::ConcreteDocument {
ServoLayoutDocument::from_layout_dom(self.node.owner_doc_for_layout())
}
fn traversal_parent(&self) -> Option<ServoLayoutElement<'dom>> {
if let Some(assigned_slot) = self.assigned_slot() {
return Some(assigned_slot);
}
let parent = self.parent_node()?;
if let Some(shadow) = parent.as_shadow_root() {
return Some(shadow.host());
};
parent.as_element()
}
fn opaque(&self) -> style::dom::OpaqueNode {
self.to_layout_dom().opaque()
}
fn debug_id(self) -> usize {
self.opaque().0
}
fn as_element(&self) -> Option<ServoLayoutElement<'dom>> {
self.node
.downcast()
.map(ServoLayoutElement::from_layout_dom)
}
fn as_document(&self) -> Option<ServoLayoutDocument<'dom>> {
self.node
.downcast()
.map(ServoLayoutDocument::from_layout_dom)
}
fn as_shadow_root(&self) -> Option<ServoShadowRoot<'dom>> {
self.node.downcast().map(ServoShadowRoot::from_layout_dom)
}
fn is_in_document(&self) -> bool {
unsafe { self.node.get_flag(NodeFlags::IS_IN_A_DOCUMENT_TREE) }
}
}
impl<'dom> LayoutNode<'dom> for ServoLayoutNode<'dom> {
type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'dom>;
fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode {
ServoThreadSafeLayoutNode::new(*self)
}
fn type_id(&self) -> LayoutNodeType {
NodeTypeIdWrapper(self.script_type_id()).into()
}
unsafe fn initialize_style_and_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
let inner = self.to_layout_dom();
if inner.style_data().is_none() {
unsafe { inner.initialize_style_data() };
}
if inner.layout_data().is_none() {
unsafe { inner.initialize_layout_data(Box::<RequestedLayoutDataType>::default()) };
}
}
fn is_connected(&self) -> bool {
unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) }
}
fn style_data(&self) -> Option<&'dom StyleData> {
self.to_layout_dom().style_data()
}
fn layout_data(&self) -> Option<&'dom GenericLayoutData> {
self.to_layout_dom().layout_data()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ServoThreadSafeLayoutNode<'dom> {
pub(super) node: ServoLayoutNode<'dom>,
pub(super) pseudo_element_chain: PseudoElementChain,
}
impl<'dom> ServoThreadSafeLayoutNode<'dom> {
pub fn new(node: ServoLayoutNode<'dom>) -> Self {
ServoThreadSafeLayoutNode {
node,
pseudo_element_chain: Default::default(),
}
}
unsafe fn get_jsmanaged(&self) -> LayoutDom<'dom, Node> {
self.node.to_layout_dom()
}
unsafe fn dangerous_first_child(&self) -> Option<Self> {
let js_managed = unsafe { self.get_jsmanaged() };
js_managed
.first_child_ref()
.map(ServoLayoutNode::from_layout_dom)
.map(Self::new)
}
unsafe fn dangerous_next_sibling(&self) -> Option<Self> {
let js_managed = unsafe { self.get_jsmanaged() };
js_managed
.next_sibling_ref()
.map(ServoLayoutNode::from_layout_dom)
.map(Self::new)
}
pub fn is_single_line_text_input(&self) -> bool {
self.type_id() == Some(LayoutNodeType::Element(LayoutElementType::HTMLInputElement)) ||
(self.pseudo_element_chain.is_empty() &&
self.node.node.is_text_container_of_single_line_input())
}
pub fn selected_style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
let Some(element) = self.as_element() else {
debug_assert!(self.is_text_node());
return self.parent_style(context);
};
let style_data = &element.style_data().styles;
let get_selected_style = || {
if self.node.node.is_in_ua_widget() {
return Some(
element
.containing_shadow_host()?
.as_node()
.selected_style(context),
);
}
style_data.pseudos.get(&PseudoElement::Selection).cloned()
};
get_selected_style().unwrap_or_else(|| style_data.primary().clone())
}
}
impl style::dom::NodeInfo for ServoThreadSafeLayoutNode<'_> {
fn is_element(&self) -> bool {
self.node.is_element()
}
fn is_text_node(&self) -> bool {
self.node.is_text_node()
}
}
impl<'dom> ThreadSafeLayoutNode<'dom> for ServoThreadSafeLayoutNode<'dom> {
type ConcreteNode = ServoLayoutNode<'dom>;
type ConcreteThreadSafeLayoutElement = ServoThreadSafeLayoutElement<'dom>;
type ConcreteElement = ServoLayoutElement<'dom>;
type ChildrenIterator = ServoThreadSafeLayoutNodeChildrenIterator<'dom>;
fn opaque(&self) -> style::dom::OpaqueNode {
unsafe { self.get_jsmanaged().opaque() }
}
fn pseudo_element_chain(&self) -> PseudoElementChain {
self.pseudo_element_chain
}
fn type_id(&self) -> Option<LayoutNodeType> {
if self.pseudo_element_chain.is_empty() {
Some(self.node.type_id())
} else {
None
}
}
fn parent_style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
if let Some(chain) = self.pseudo_element_chain.without_innermost() {
let mut parent = *self;
parent.pseudo_element_chain = chain;
return parent.style(context);
}
let parent_element = self.node.traversal_parent().unwrap();
let parent_data = parent_element.borrow_data().unwrap();
parent_data.styles.primary().clone()
}
fn initialize_layout_data<RequestedLayoutDataType: LayoutDataTrait>(&self) {
let inner = self.node.to_layout_dom();
if inner.layout_data().is_none() {
unsafe {
inner.initialize_layout_data(Box::<RequestedLayoutDataType>::default());
}
}
}
fn debug_id(self) -> usize {
self.node.debug_id()
}
fn children(&self) -> style::dom::LayoutIterator<Self::ChildrenIterator> {
style::dom::LayoutIterator(ServoThreadSafeLayoutNodeChildrenIterator::new(*self))
}
fn as_element(&self) -> Option<ServoThreadSafeLayoutElement<'dom>> {
self.node
.as_element()
.map(|el| ServoThreadSafeLayoutElement {
element: el,
pseudo_element_chain: self.pseudo_element_chain,
})
}
fn as_html_element(&self) -> Option<ServoThreadSafeLayoutElement<'dom>> {
self.as_element()
.filter(|element| element.element.is_html_element())
}
fn style_data(&self) -> Option<&'dom StyleData> {
self.node.style_data()
}
fn layout_data(&self) -> Option<&'dom GenericLayoutData> {
self.node.layout_data()
}
fn unsafe_get(self) -> Self::ConcreteNode {
self.node
}
fn text_content(self) -> Cow<'dom, str> {
unsafe { self.get_jsmanaged().text_content() }
}
fn selection(&self) -> Option<SharedSelection> {
let this = unsafe { self.get_jsmanaged() };
this.selection()
}
fn image_url(&self) -> Option<ServoUrl> {
let this = unsafe { self.get_jsmanaged() };
this.image_url()
}
fn image_density(&self) -> Option<f64> {
let this = unsafe { self.get_jsmanaged() };
this.image_density()
}
fn showing_broken_image_icon(&self) -> bool {
let this = unsafe { self.get_jsmanaged() };
this.showing_broken_image_icon()
}
fn image_data(&self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
let this = unsafe { self.get_jsmanaged() };
this.image_data()
}
fn canvas_data(&self) -> Option<HTMLCanvasData> {
let this = unsafe { self.get_jsmanaged() };
this.canvas_data()
}
fn media_data(&self) -> Option<HTMLMediaData> {
let this = unsafe { self.get_jsmanaged() };
this.media_data()
}
fn svg_data(&self) -> Option<SVGElementData<'dom>> {
let this = unsafe { self.get_jsmanaged() };
this.svg_data()
}
fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId> {
let this = unsafe { self.get_jsmanaged() };
this.iframe_browsing_context_id()
}
fn iframe_pipeline_id(&self) -> Option<PipelineId> {
let this = unsafe { self.get_jsmanaged() };
this.iframe_pipeline_id()
}
fn get_span(&self) -> Option<u32> {
unsafe {
self.get_jsmanaged()
.downcast::<Element>()
.unwrap()
.get_span()
}
}
fn get_colspan(&self) -> Option<u32> {
unsafe {
self.get_jsmanaged()
.downcast::<Element>()
.unwrap()
.get_colspan()
}
}
fn get_rowspan(&self) -> Option<u32> {
unsafe {
self.get_jsmanaged()
.downcast::<Element>()
.unwrap()
.get_rowspan()
}
}
fn with_pseudo_element_chain(&self, pseudo_element_chain: PseudoElementChain) -> Self {
Self {
node: self.node,
pseudo_element_chain,
}
}
fn set_uses_content_attribute_with_attr(&self, uses_content_attribute_with_attr: bool) {
unsafe {
self.node.node.set_flag(
NodeFlags::USES_ATTR_IN_CONTENT_ATTRIBUTE,
uses_content_attribute_with_attr,
)
}
}
}
pub enum ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
Node(Option<ServoThreadSafeLayoutNode<'dom>>),
Slottables(<Vec<ServoLayoutNode<'dom>> as IntoIterator>::IntoIter),
}
impl<'dom> ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
#[expect(unsafe_code)]
fn new(
parent: ServoThreadSafeLayoutNode<'dom>,
) -> ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
if let Some(element) = parent.as_element() {
if let Some(shadow) = element.shadow_root() {
return Self::new(shadow.as_node().to_threadsafe());
};
let slotted_nodes = element.slotted_nodes();
if !slotted_nodes.is_empty() {
#[expect(clippy::unnecessary_to_owned)] return Self::Slottables(slotted_nodes.to_owned().into_iter());
}
}
Self::Node(unsafe { parent.dangerous_first_child() })
}
}
impl<'dom> Iterator for ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
type Item = ServoThreadSafeLayoutNode<'dom>;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Node(node) => {
let next_sibling = unsafe { (*node)?.dangerous_next_sibling() };
std::mem::replace(node, next_sibling)
},
Self::Slottables(slots) => slots.next().map(|node| node.to_threadsafe()),
}
}
}
impl FusedIterator for ServoThreadSafeLayoutNodeChildrenIterator<'_> {}