layout: Add initial support for the ::marker pseudo-element (#36317)

This change adds support for the `::marker` pseudo-element and ensure
that
markers are cached into the box tree. This is only initial support,
there are a few
things missing such as animations, transitions, and support the
`content` CSS
property.

Testing: There are WPT tests for this change.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-04-07 19:49:05 +02:00 committed by GitHub
parent a5c547259f
commit 2b63e60e8f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 282 additions and 276 deletions

View file

@ -34,6 +34,7 @@ pub struct InnerDOMLayoutData {
pub(super) self_box: ArcRefCell<Option<LayoutBox>>,
pub(super) pseudo_before_box: ArcRefCell<Option<LayoutBox>>,
pub(super) pseudo_after_box: ArcRefCell<Option<LayoutBox>>,
pub(super) pseudo_marker_box: ArcRefCell<Option<LayoutBox>>,
}
/// A box that is stored in one of the `DOMLayoutData` slots.
@ -222,6 +223,7 @@ where
let cell = match pseudo_element_type {
PseudoElement::Before => &data.pseudo_before_box,
PseudoElement::After => &data.pseudo_after_box,
PseudoElement::Marker => &data.pseudo_marker_box,
_ => unreachable!(
"Asked for box slot for unsupported pseudo-element: {:?}",
pseudo_element_type
@ -235,6 +237,7 @@ where
let cell = match pseudo_element_type {
PseudoElement::Before => &data.pseudo_before_box,
PseudoElement::After => &data.pseudo_after_box,
PseudoElement::Marker => &data.pseudo_marker_box,
_ => unreachable!(
"Asked for box slot for unsupported pseudo-element: {:?}",
pseudo_element_type
@ -248,6 +251,7 @@ where
*data.self_box.borrow_mut() = None;
*data.pseudo_before_box.borrow_mut() = None;
*data.pseudo_after_box.borrow_mut() = None;
*data.pseudo_marker_box.borrow_mut() = None;
// Stylo already takes care of removing all layout data
// for DOM descendants of elements with `display: none`.
}

View file

@ -236,6 +236,10 @@ fn traverse_element<'dom, Node>(
) where
Node: NodeExt<'dom>,
{
// Clear any existing pseudo-element box slot, because markers are not handled like
// `::before`` and `::after`. They are processed during box tree creation.
element.unset_pseudo_element_box(PseudoElement::Marker);
let replaced = ReplacedContents::for_element(element, context);
let style = element.style(context);
match Display::from(style.get_box().display) {

View file

@ -393,7 +393,7 @@ where
is_list_item: false,
},
NonReplacedContents::OfPseudoElement(contents).into(),
BoxSlot::dummy(),
info.node.pseudo_element_box_slot(PseudoElement::Marker),
);
}
@ -405,7 +405,7 @@ where
) {
self.block_level_boxes.push(BlockLevelJob {
info: info.clone(),
box_slot: BoxSlot::dummy(),
box_slot: info.node.pseudo_element_box_slot(PseudoElement::Marker),
kind: BlockLevelCreator::OutsideMarker {
contents,
list_item_style,

View file

@ -19,11 +19,7 @@ pub(crate) fn make_marker<'dom, Node>(
where
Node: NodeExt<'dom>,
{
// TODO: use `PseudoElement::Marker` when we add it.
let marker_info = info.pseudo(
context,
style::selector_parser::PseudoElement::ServoLegacyText,
)?;
let marker_info = info.pseudo(context, style::selector_parser::PseudoElement::Marker)?;
let style = &marker_info.style;
let list_style = style.get_list();

View file

@ -131,7 +131,7 @@ pub fn process_resolved_style_request<'dom>(
None => layout_element,
};
let style = &*layout_element.resolved_style();
let style = &*layout_element.style(context);
let longhand_id = match *property {
PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
Ok(longhand_id) => longhand_id,
@ -1122,7 +1122,7 @@ where
let element = node.as_element().unwrap();
let parent_style = if node.is_connected() {
if element.has_data() {
node.to_threadsafe().as_element().unwrap().resolved_style()
node.to_threadsafe().as_element().unwrap().style(context)
} else {
let mut tlc = ThreadLocalStyleContext::new();
let mut context = StyleContext {

View file

@ -1262,6 +1262,7 @@ impl WindowMethods<crate::DomTypeHolder> for Window {
Some(PseudoElement::After)
},
Some(ref pseudo) if pseudo == "::selection" => Some(PseudoElement::Selection),
Some(ref pseudo) if pseudo == "::marker" => Some(PseudoElement::Marker),
Some(ref pseudo) if pseudo.starts_with(':') => {
// Step 3.2: If type is failure, or is a ::slotted() or ::part()
// pseudo-element, let obj be null.

View file

@ -363,22 +363,6 @@ pub trait ThreadSafeLayoutElement<'dom>:
.clone()
}
/// Returns the already resolved style of the node.
///
/// This differs from `style(ctx)` in that if the pseudo-element has not yet
/// been computed it would panic.
///
/// This should be used just for querying layout, or when we know the
/// element style is precomputed, not from general layout itself.
#[inline]
fn resolved_style(&self) -> Arc<ComputedValues> {
let data = self.style_data();
match self.pseudo_element() {
None => data.styles.primary().clone(),
Some(pseudo_element) => data.styles.pseudos.get(&pseudo_element).unwrap().clone(),
}
}
fn is_shadow_host(&self) -> bool;
/// Returns whether this node is a body element of an html element root