diff options
author | Martin Robinson <mrobinson@igalia.com> | 2025-03-29 13:41:04 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-03-29 12:41:04 +0000 |
commit | b5c8164e9982ebd6212ad21910826ce8b2631930 (patch) | |
tree | 91359e4206965cc592f53bf8ee86f030c0e67fe0 | |
parent | c30ad5a30e10b9aabe5842624db24b9846c3d5d4 (diff) | |
download | servo-b5c8164e9982ebd6212ad21910826ce8b2631930.tar.gz servo-b5c8164e9982ebd6212ad21910826ce8b2631930.zip |
layout: Simplify and generalize the usage of pseudo-elements (#36202)
- Remove the last remaining Servo-specific PseudoElement enum from
layout. This was made to select `::before` and `::after` (both eager
pseudo-elements), but now `traverse_pseudo_element` is called
`traverse_eager_pseudo_element` and should work on any eager pseudo
element.
- Expose a single way of getting psuedo-element variants of
ThreadSafeLayoutElement in the Layout DOM, which returns `None` when
the pseudo-element doesn't apply (not defined for eager
pseudo-elements or when trying to get `<details>` related
pseudo-elements on elements that they don't apply to).
- Ensure that NodeAndStyleInfo always refers to a node. This is done by
making sure that anonymous boxes are all associated with their
originating node.
These changes are prepatory work for implementation of the `::marker`
pseudo-element as well as ensuring that all anonymous boxes can be
cached into the box tree eventually.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
-rw-r--r-- | components/layout_2020/construct_modern.rs | 24 | ||||
-rw-r--r-- | components/layout_2020/dom.rs | 30 | ||||
-rw-r--r-- | components/layout_2020/dom_traversal.rs | 189 | ||||
-rw-r--r-- | components/layout_2020/flow/construct.rs | 90 | ||||
-rw-r--r-- | components/layout_2020/flow/mod.rs | 7 | ||||
-rw-r--r-- | components/layout_2020/fragment_tree/base_fragment.rs | 4 | ||||
-rw-r--r-- | components/layout_2020/lists.rs | 32 | ||||
-rw-r--r-- | components/layout_2020/query.rs | 24 | ||||
-rw-r--r-- | components/layout_2020/table/construct.rs | 86 | ||||
-rw-r--r-- | components/script/layout_dom/element.rs | 33 | ||||
-rw-r--r-- | components/script/layout_dom/node.rs | 14 | ||||
-rw-r--r-- | components/shared/script_layout/wrapper_traits.rs | 75 | ||||
-rw-r--r-- | resources/servo.css | 2 | ||||
-rw-r--r-- | tests/wpt/meta/css/cssom-view/elementFromPoint-list-001.html.ini | 15 | ||||
-rw-r--r-- | tests/wpt/meta/intersection-observer/intersection-ratio-with-fractional-bounds-2.html.ini | 3 |
15 files changed, 276 insertions, 352 deletions
diff --git a/components/layout_2020/construct_modern.rs b/components/layout_2020/construct_modern.rs index e66082234ee..22f179d146c 100644 --- a/components/layout_2020/construct_modern.rs +++ b/components/layout_2020/construct_modern.rs @@ -5,8 +5,10 @@ //! Layout construction code that is shared between modern layout modes (Flexbox and CSS Grid) use std::borrow::Cow; +use std::sync::LazyLock; use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use style::selector_parser::PseudoElement; use crate::PropagatedBoxTreeData; use crate::context::LayoutContext; @@ -136,21 +138,11 @@ where pub(crate) fn finish(mut self) -> Vec<ModernItem<'dom>> { self.wrap_any_text_in_anonymous_block_container(); - let anonymous_style = if self.has_text_runs { - Some( - self.context - .shared_context() - .stylist - .style_for_anonymous::<Node::ConcreteElement>( - &self.context.shared_context().guards, - &style::selector_parser::PseudoElement::ServoAnonymousBox, - &self.info.style, - ), - ) - } else { - None - }; - + let anonymous_info = LazyLock::new(|| { + self.info + .pseudo(self.context, PseudoElement::ServoAnonymousBox) + .expect("Should always be able to construct info for anonymous boxes.") + }); let mut children: Vec<ModernItem> = std::mem::take(&mut self.jobs) .into_par_iter() .filter_map(|job| match job { @@ -173,7 +165,7 @@ where let block_formatting_context = BlockFormattingContext::from_block_container( BlockContainer::InlineFormattingContext(inline_formatting_context), ); - let info = &self.info.new_anonymous(anonymous_style.clone().unwrap()); + let info: &NodeAndStyleInfo<_> = &*anonymous_info; let formatting_context = IndependentFormattingContext { base: LayoutBoxBase::new(info.into(), info.style.clone()), contents: IndependentFormattingContextContents::NonReplaced( diff --git a/components/layout_2020/dom.rs b/components/layout_2020/dom.rs index b0323a4e56b..f8b3d6f9c29 100644 --- a/components/layout_2020/dom.rs +++ b/components/layout_2020/dom.rs @@ -17,10 +17,10 @@ use script_layout_interface::{ }; use servo_arc::Arc as ServoArc; use style::properties::ComputedValues; +use style::selector_parser::PseudoElement; use crate::cell::ArcRefCell; use crate::context::LayoutContext; -use crate::dom_traversal::WhichPseudoElement; use crate::flexbox::FlexLevelBox; use crate::flow::BlockLevelBox; use crate::flow::inline::InlineItem; @@ -108,8 +108,8 @@ pub(crate) trait NodeExt<'dom>: 'dom + LayoutNode<'dom> { fn layout_data_mut(self) -> AtomicRefMut<'dom, InnerDOMLayoutData>; fn layout_data(self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>>; fn element_box_slot(&self) -> BoxSlot<'dom>; - fn pseudo_element_box_slot(&self, which: WhichPseudoElement) -> BoxSlot<'dom>; - fn unset_pseudo_element_box(self, which: WhichPseudoElement); + fn pseudo_element_box_slot(&self, which: PseudoElement) -> BoxSlot<'dom>; + fn unset_pseudo_element_box(self, which: PseudoElement); /// Remove boxes for the element itself, and its `:before` and `:after` if any. fn unset_all_boxes(self); @@ -217,20 +217,28 @@ where BoxSlot::new(self.layout_data_mut().self_box.clone()) } - fn pseudo_element_box_slot(&self, which: WhichPseudoElement) -> BoxSlot<'dom> { + fn pseudo_element_box_slot(&self, pseudo_element_type: PseudoElement) -> BoxSlot<'dom> { let data = self.layout_data_mut(); - let cell = match which { - WhichPseudoElement::Before => &data.pseudo_before_box, - WhichPseudoElement::After => &data.pseudo_after_box, + let cell = match pseudo_element_type { + PseudoElement::Before => &data.pseudo_before_box, + PseudoElement::After => &data.pseudo_after_box, + _ => unreachable!( + "Asked for box slot for unsupported pseudo-element: {:?}", + pseudo_element_type + ), }; BoxSlot::new(cell.clone()) } - fn unset_pseudo_element_box(self, which: WhichPseudoElement) { + fn unset_pseudo_element_box(self, pseudo_element_type: PseudoElement) { let data = self.layout_data_mut(); - let cell = match which { - WhichPseudoElement::Before => &data.pseudo_before_box, - WhichPseudoElement::After => &data.pseudo_after_box, + let cell = match pseudo_element_type { + PseudoElement::Before => &data.pseudo_before_box, + PseudoElement::After => &data.pseudo_after_box, + _ => unreachable!( + "Asked for box slot for unsupported pseudo-element: {:?}", + pseudo_element_type + ), }; *cell.borrow_mut() = None; } diff --git a/components/layout_2020/dom_traversal.rs b/components/layout_2020/dom_traversal.rs index f7fed5c9154..33e500bfe87 100644 --- a/components/layout_2020/dom_traversal.rs +++ b/components/layout_2020/dom_traversal.rs @@ -6,7 +6,6 @@ use std::borrow::Cow; use std::iter::FusedIterator; use html5ever::{LocalName, local_name}; -use log::warn; use script_layout_interface::wrapper_traits::{ThreadSafeLayoutElement, ThreadSafeLayoutNode}; use script_layout_interface::{LayoutElementType, LayoutNodeType}; use selectors::Element as SelectorsElement; @@ -24,29 +23,23 @@ use crate::quotes::quotes_for_lang; use crate::replaced::ReplacedContents; use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside, DisplayOutside}; -#[derive(Clone, Copy, Debug)] -pub(crate) enum WhichPseudoElement { - Before, - After, -} - /// A data structure used to pass and store related layout information together to /// avoid having to repeat the same arguments in argument lists. #[derive(Clone)] pub(crate) struct NodeAndStyleInfo<Node> { - pub node: Option<Node>, - pub pseudo_element_type: Option<WhichPseudoElement>, + pub node: Node, + pub pseudo_element_type: Option<PseudoElement>, pub style: ServoArc<ComputedValues>, } impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo<Node> { fn new_with_pseudo( node: Node, - pseudo_element_type: WhichPseudoElement, + pseudo_element_type: PseudoElement, style: ServoArc<ComputedValues>, ) -> Self { Self { - node: Some(node), + node, pseudo_element_type: Some(pseudo_element_type), style, } @@ -54,34 +47,32 @@ impl<'dom, Node: NodeExt<'dom>> NodeAndStyleInfo<Node> { pub(crate) fn new(node: Node, style: ServoArc<ComputedValues>) -> Self { Self { - node: Some(node), + node, pseudo_element_type: None, style, } } pub(crate) fn is_single_line_text_input(&self) -> bool { - self.node.is_some_and(|node| { - node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLInputElement) - }) + self.node.type_id() == LayoutNodeType::Element(LayoutElementType::HTMLInputElement) } -} -impl<Node: Clone> NodeAndStyleInfo<Node> { - pub(crate) fn new_anonymous(&self, style: ServoArc<ComputedValues>) -> Self { - Self { - node: None, - pseudo_element_type: self.pseudo_element_type, - style, - } - } - - pub(crate) fn new_replacing_style(&self, style: ServoArc<ComputedValues>) -> Self { - Self { - node: self.node.clone(), - pseudo_element_type: self.pseudo_element_type, + pub(crate) fn pseudo( + &self, + context: &LayoutContext, + pseudo_element_type: PseudoElement, + ) -> Option<Self> { + let style = self + .node + .to_threadsafe() + .as_element()? + .with_pseudo(pseudo_element_type)? + .style(context.shared_context()); + Some(NodeAndStyleInfo { + node: self.node, + pseudo_element_type: Some(pseudo_element_type), style, - } + }) } } @@ -90,19 +81,26 @@ where Node: NodeExt<'dom>, { fn from(info: &NodeAndStyleInfo<Node>) -> Self { - let node = match info.node { - Some(node) => node, - None => return Self::anonymous(), - }; - - let pseudo = info.pseudo_element_type.map(|pseudo| match pseudo { - WhichPseudoElement::Before => PseudoElement::Before, - WhichPseudoElement::After => PseudoElement::After, - }); - + let node = info.node; + let pseudo = info.pseudo_element_type; let threadsafe_node = node.to_threadsafe(); let mut flags = FragmentFlags::empty(); + // Anonymous boxes should not have a tag, because they should not take part in hit testing. + // + // TODO(mrobinson): It seems that anonymous boxes should take part in hit testing in some + // cases, but currently this means that the order of hit test results isn't as expected for + // some WPT tests. This needs more investigation. + if matches!( + pseudo, + Some(PseudoElement::ServoAnonymousBox) | + Some(PseudoElement::ServoAnonymousTable) | + Some(PseudoElement::ServoAnonymousTableCell) | + Some(PseudoElement::ServoAnonymousTableRow) + ) { + return Self::anonymous(); + } + if let Some(element) = threadsafe_node.as_html_element() { if element.is_body_element_of_html_element_root() { flags.insert(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT); @@ -184,7 +182,7 @@ fn traverse_children_of<'dom, Node>( ) where Node: NodeExt<'dom>, { - traverse_pseudo_element(WhichPseudoElement::Before, parent_element, context, handler); + traverse_eager_pseudo_element(PseudoElement::Before, parent_element, context, handler); let is_text_input_element = matches!( parent_element.type_id(), @@ -223,7 +221,7 @@ fn traverse_children_of<'dom, Node>( } } - traverse_pseudo_element(WhichPseudoElement::After, parent_element, context, handler); + traverse_eager_pseudo_element(PseudoElement::After, parent_element, context, handler); } fn traverse_element<'dom, Node>( @@ -268,33 +266,46 @@ fn traverse_element<'dom, Node>( } } -fn traverse_pseudo_element<'dom, Node>( - which: WhichPseudoElement, - element: Node, +fn traverse_eager_pseudo_element<'dom, Node>( + pseudo_element_type: PseudoElement, + node: Node, context: &LayoutContext, handler: &mut impl TraversalHandler<'dom, Node>, ) where Node: NodeExt<'dom>, { - if let Some(style) = pseudo_element_style(which, element, context) { - let info = NodeAndStyleInfo::new_with_pseudo(element, which, style); - match Display::from(info.style.get_box().display) { - Display::None => element.unset_pseudo_element_box(which), - Display::Contents => { - let items = generate_pseudo_element_content(&info.style, element, context); - let box_slot = element.pseudo_element_box_slot(which); - box_slot.set(LayoutBox::DisplayContents); - traverse_pseudo_element_contents(&info, context, handler, items); - }, - Display::GeneratingBox(display) => { - let items = generate_pseudo_element_content(&info.style, element, context); - let box_slot = element.pseudo_element_box_slot(which); - let contents = NonReplacedContents::OfPseudoElement(items).into(); - handler.handle_element(&info, display, contents, box_slot); - }, - } - } else { - element.unset_pseudo_element_box(which) + assert!(pseudo_element_type.is_eager()); + + // First clear any old contents from the node. + node.unset_pseudo_element_box(pseudo_element_type); + + let Some(element) = node.to_threadsafe().as_element() else { + return; + }; + let Some(pseudo_element) = element.with_pseudo(pseudo_element_type) else { + return; + }; + + let style = pseudo_element.style(context.shared_context()); + if style.ineffective_content_property() { + return; + } + + let info = NodeAndStyleInfo::new_with_pseudo(node, pseudo_element_type, style); + match Display::from(info.style.get_box().display) { + Display::None => {}, + Display::Contents => { + let items = generate_pseudo_element_content(&info.style, node, context); + let box_slot = node.pseudo_element_box_slot(pseudo_element_type); + box_slot.set(LayoutBox::DisplayContents); + traverse_pseudo_element_contents(&info, context, handler, items); + }, + Display::GeneratingBox(display) => { + let items = generate_pseudo_element_content(&info.style, node, context); + let box_slot = node.pseudo_element_box_slot(pseudo_element_type); + let contents = NonReplacedContents::OfPseudoElement(items).into(); + handler.handle_element(&info, display, contents, box_slot); + }, } } @@ -306,20 +317,14 @@ fn traverse_pseudo_element_contents<'dom, Node>( ) where Node: NodeExt<'dom>, { - let mut anonymous_style = None; + let mut anonymous_info = None; for item in items { match item { PseudoElementContentItem::Text(text) => handler.handle_text(info, text.into()), PseudoElementContentItem::Replaced(contents) => { - let item_style = anonymous_style.get_or_insert_with(|| { - context - .shared_context() - .stylist - .style_for_anonymous::<Node::ConcreteElement>( - &context.shared_context().guards, - &PseudoElement::ServoAnonymousBox, - &info.style, - ) + let anonymous_info = anonymous_info.get_or_insert_with(|| { + info.pseudo(context, PseudoElement::ServoAnonymousBox) + .unwrap_or_else(|| info.clone()) }); let display_inline = DisplayGeneratingBox::OutsideInside { outside: DisplayOutside::Inline, @@ -329,12 +334,11 @@ fn traverse_pseudo_element_contents<'dom, Node>( }; // `display` is not inherited, so we get the initial value debug_assert!( - Display::from(item_style.get_box().display) == + Display::from(anonymous_info.style.get_box().display) == Display::GeneratingBox(display_inline) ); - let info = info.new_replacing_style(item_style.clone()); handler.handle_element( - &info, + anonymous_info, display_inline, Contents::Replaced(contents), // We don’t keep pointers to boxes generated by contents of pseudo-elements @@ -380,16 +384,9 @@ impl NonReplacedContents { ) where Node: NodeExt<'dom>, { - let node = match info.node { - Some(node) => node, - None => { - warn!("Tried to traverse an anonymous node!"); - return; - }, - }; match self { NonReplacedContents::OfElement | NonReplacedContents::OfTextControl => { - traverse_children_of(node, context, handler) + traverse_children_of(info.node, context, handler) }, NonReplacedContents::OfPseudoElement(items) => { traverse_pseudo_element_contents(info, context, handler, items) @@ -398,28 +395,6 @@ impl NonReplacedContents { } } -fn pseudo_element_style<'dom, Node>( - which: WhichPseudoElement, - element: Node, - context: &LayoutContext, -) -> Option<ServoArc<ComputedValues>> -where - Node: NodeExt<'dom>, -{ - match which { - WhichPseudoElement::After => element.to_threadsafe().get_pseudo(PseudoElement::After), - WhichPseudoElement::Before => element.to_threadsafe().get_pseudo(PseudoElement::Before), - } - .and_then(|pseudo_element| { - let style = pseudo_element.style(context.shared_context()); - if style.ineffective_content_property() { - None - } else { - Some(style) - } - }) -} - fn get_quote_from_pair<I, S>(item: &ContentItem<I>, opening: &S, closing: &S) -> String where S: ToString + ?Sized, diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs index 0fcd1ce16f6..ef73ee3d007 100644 --- a/components/layout_2020/flow/construct.rs +++ b/components/layout_2020/flow/construct.rs @@ -83,6 +83,7 @@ enum BlockLevelCreator { contents: Contents, }, OutsideMarker { + list_item_style: Arc<ComputedValues>, contents: Vec<PseudoElementContentItem>, }, AnonymousTable { @@ -141,9 +142,9 @@ pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> { inline_formatting_context_builder: InlineFormattingContextBuilder, - /// The style of the anonymous block boxes pushed to the list of block-level - /// boxes, if any (see `end_ongoing_inline_formatting_context`). - anonymous_style: Option<Arc<ComputedValues>>, + /// The [`NodeAndStyleInfo`] to use for anonymous block boxes pushed to the list of + /// block-level boxes, lazily initialized (see `end_ongoing_inline_formatting_context`). + anonymous_box_info: Option<NodeAndStyleInfo<Node>>, /// A collection of content that is being added to an anonymous table. This is /// composed of any sequence of internal table elements or table captions that @@ -165,14 +166,16 @@ impl BlockContainer { let mut builder = BlockContainerBuilder::new(context, info, propagated_data); if is_list_item { - if let Some(marker_contents) = crate::lists::make_marker(context, info) { - match info.style.clone_list_style_position() { + if let Some((marker_info, marker_contents)) = crate::lists::make_marker(context, info) { + match marker_info.style.clone_list_style_position() { ListStylePosition::Inside => { - builder.handle_list_item_marker_inside(info, marker_contents) - }, - ListStylePosition::Outside => { - builder.handle_list_item_marker_outside(info, marker_contents) + builder.handle_list_item_marker_inside(&marker_info, marker_contents) }, + ListStylePosition::Outside => builder.handle_list_item_marker_outside( + &marker_info, + marker_contents, + info.style.clone(), + ), } } } @@ -197,7 +200,7 @@ where block_level_boxes: Vec::new(), propagated_data: propagated_data.union(&info.style), have_already_seen_first_line_for_text_indent: false, - anonymous_style: None, + anonymous_box_info: None, anonymous_table_content: Vec::new(), inline_formatting_context_builder: InlineFormattingContextBuilder::new(), } @@ -277,16 +280,16 @@ where _ => None, }; - let ifc = Table::construct_anonymous(self.context, self.info, contents, propagated_data); + let (table_info, ifc) = + Table::construct_anonymous(self.context, self.info, contents, propagated_data); if inline_table { self.inline_formatting_context_builder.push_atomic(ifc); } else { - let anonymous_info = self.info.new_anonymous(ifc.style().clone()); let table_block = ArcRefCell::new(BlockLevelBox::Independent(ifc)); self.end_ongoing_inline_formatting_context(); self.block_level_boxes.push(BlockLevelJob { - info: anonymous_info, + info: table_info, box_slot: BoxSlot::dummy(), kind: BlockLevelCreator::AnonymousTable { table_block }, propagated_data, @@ -384,17 +387,8 @@ where info: &NodeAndStyleInfo<Node>, contents: Vec<crate::dom_traversal::PseudoElementContentItem>, ) { - let marker_style = self - .context - .shared_context() - .stylist - .style_for_anonymous::<Node::ConcreteElement>( - &self.context.shared_context().guards, - &PseudoElement::ServoLegacyText, // FIMXE: use `PseudoElement::Marker` when we add it - &info.style, - ); self.handle_inline_level_element( - &info.new_replacing_style(marker_style), + info, DisplayInside::Flow { is_list_item: false, }, @@ -407,11 +401,15 @@ where &mut self, info: &NodeAndStyleInfo<Node>, contents: Vec<crate::dom_traversal::PseudoElementContentItem>, + list_item_style: Arc<ComputedValues>, ) { self.block_level_boxes.push(BlockLevelJob { info: info.clone(), box_slot: BoxSlot::dummy(), - kind: BlockLevelCreator::OutsideMarker { contents }, + kind: BlockLevelCreator::OutsideMarker { + contents, + list_item_style, + }, propagated_data: self.propagated_data.without_text_decorations(), }); } @@ -448,11 +446,13 @@ where .start_inline_box(InlineBox::new(info)); if is_list_item { - if let Some(marker_contents) = crate::lists::make_marker(self.context, info) { + if let Some((marker_info, marker_contents)) = + crate::lists::make_marker(self.context, info) + { // Ignore `list-style-position` here: // “If the list item is an inline box: this value is equivalent to `inside`.” // https://drafts.csswg.org/css-lists/#list-style-position-outside - self.handle_list_item_marker_inside(info, marker_contents) + self.handle_list_item_marker_inside(&marker_info, marker_contents) } } @@ -616,20 +616,16 @@ where &mut self, inline_formatting_context: InlineFormattingContext, ) { - let block_container_style = &self.info.style; let layout_context = self.context; - let anonymous_style = self.anonymous_style.get_or_insert_with(|| { - layout_context - .shared_context() - .stylist - .style_for_anonymous::<Node::ConcreteElement>( - &layout_context.shared_context().guards, - &PseudoElement::ServoAnonymousBox, - block_container_style, - ) - }); + let info = self + .anonymous_box_info + .get_or_insert_with(|| { + self.info + .pseudo(layout_context, PseudoElement::ServoAnonymousBox) + .expect("Should never fail to create anonymous box") + }) + .clone(); - let info = self.info.new_anonymous(anonymous_style.clone()); self.block_level_boxes.push(BlockLevelJob { info, // FIXME(nox): We should be storing this somewhere. @@ -696,27 +692,23 @@ where contents, self.propagated_data, ))), - BlockLevelCreator::OutsideMarker { contents } => { - let marker_style = context - .shared_context() - .stylist - .style_for_anonymous::<Node::ConcreteElement>( - &context.shared_context().guards, - &PseudoElement::ServoLegacyText, // FIMXE: use `PseudoElement::Marker` when we add it - &info.style, - ); + BlockLevelCreator::OutsideMarker { + contents, + list_item_style, + } => { let contents = NonReplacedContents::OfPseudoElement(contents); let block_container = BlockContainer::construct( context, - &info.new_replacing_style(marker_style.clone()), + info, contents, self.propagated_data.without_text_decorations(), false, /* is_list_item */ ); ArcRefCell::new(BlockLevelBox::OutsideMarker(OutsideMarker { - marker_style, + marker_style: info.style.clone(), base: LayoutBoxBase::new(info.into(), info.style.clone()), block_container, + list_item_style, })) }, BlockLevelCreator::AnonymousTable { table_block } => table_block, diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs index 8f38e0d4b23..1aee5748c60 100644 --- a/components/layout_2020/flow/mod.rs +++ b/components/layout_2020/flow/mod.rs @@ -227,15 +227,12 @@ pub(crate) struct CollapsibleWithParentStartMargin(bool); #[derive(Debug)] pub(crate) struct OutsideMarker { pub marker_style: Arc<ComputedValues>, + pub list_item_style: Arc<ComputedValues>, pub base: LayoutBoxBase, pub block_container: BlockContainer, } impl OutsideMarker { - fn list_item_style(&self) -> &ComputedValues { - &self.base.style - } - fn inline_content_sizes( &self, layout_context: &LayoutContext, @@ -313,7 +310,7 @@ impl OutsideMarker { // TODO: This should use the LayoutStyle of the list item, not the default one. Currently // they are the same, but this could change in the future. let pbm_of_list_item = - LayoutStyle::Default(self.list_item_style()).padding_border_margin(containing_block); + LayoutStyle::Default(&self.list_item_style).padding_border_margin(containing_block); let content_rect = LogicalRect { start_corner: LogicalVec2 { inline: -max_inline_size - diff --git a/components/layout_2020/fragment_tree/base_fragment.rs b/components/layout_2020/fragment_tree/base_fragment.rs index 2b23e42100d..8e529f40ba6 100644 --- a/components/layout_2020/fragment_tree/base_fragment.rs +++ b/components/layout_2020/fragment_tree/base_fragment.rs @@ -13,8 +13,8 @@ use style::selector_parser::PseudoElement; #[derive(Clone, Debug)] pub(crate) struct BaseFragment { /// A tag which identifies the DOM node and pseudo element of this - /// Fragment's content. If this fragment isn't related to any DOM - /// node at all, the tag will be None. + /// Fragment's content. If this fragment is for an anonymous box, + /// the tag will be None. pub tag: Option<Tag>, /// Flags which various information about this fragment used during diff --git a/components/layout_2020/lists.rs b/components/layout_2020/lists.rs index ee8db260c8c..ee60a82ef01 100644 --- a/components/layout_2020/lists.rs +++ b/components/layout_2020/lists.rs @@ -2,7 +2,6 @@ * 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 log::warn; use style::properties::longhands::list_style_type::computed_value::T as ListStyleType; use style::properties::style_structs; use style::values::computed::Image; @@ -16,24 +15,25 @@ use crate::replaced::ReplacedContents; pub(crate) fn make_marker<'dom, Node>( context: &LayoutContext, info: &NodeAndStyleInfo<Node>, -) -> Option<Vec<PseudoElementContentItem>> +) -> Option<(NodeAndStyleInfo<Node>, Vec<PseudoElementContentItem>)> where Node: NodeExt<'dom>, { - let style = info.style.get_list(); - let node = match info.node { - Some(node) => node, - None => { - warn!("Tried to make a marker for an anonymous node!"); - return None; - }, - }; + // TODO: use `PseudoElement::Marker` when we add it. + let marker_info = info.pseudo( + context, + style::selector_parser::PseudoElement::ServoLegacyText, + )?; + let style = &marker_info.style; + let list_style = style.get_list(); // https://drafts.csswg.org/css-lists/#marker-image - let marker_image = || match &style.list_style_image { + let marker_image = || match &list_style.list_style_image { Image::Url(url) => Some(vec![ PseudoElementContentItem::Replaced(ReplacedContents::from_image_url( - node, context, url, + marker_info.node, + context, + url, )?), PseudoElementContentItem::Text(" ".into()), ]), @@ -44,11 +44,13 @@ where Image::PaintWorklet(..) | Image::None => None, }; - marker_image().or_else(|| { + let content = marker_image().or_else(|| { Some(vec![PseudoElementContentItem::Text( - marker_string(style)?.into(), + marker_string(list_style)?.into(), )]) - }) + })?; + + Some((marker_info, content)) } /// <https://drafts.csswg.org/css-lists/#marker-string> diff --git a/components/layout_2020/query.rs b/components/layout_2020/query.rs index 7c67641792e..108a4e0540c 100644 --- a/components/layout_2020/query.rs +++ b/components/layout_2020/query.rs @@ -116,19 +116,19 @@ pub fn process_resolved_style_request<'dom>( // We call process_resolved_style_request after performing a whole-document // traversal, so in the common case, the element is styled. let layout_element = node.to_threadsafe().as_element().unwrap(); - let layout_element = pseudo.map_or_else( - || Some(layout_element), - |pseudo_element| layout_element.get_pseudo(pseudo_element), - ); - - let layout_element = match layout_element { - None => { - // The pseudo doesn't exist, return nothing. Chrome seems to query - // the element itself in this case, Firefox uses the resolved value. - // https://www.w3.org/Bugs/Public/show_bug.cgi?id=29006 - return String::new(); + let layout_element = match pseudo { + Some(pseudo_element_type) => { + match layout_element.with_pseudo(*pseudo_element_type) { + Some(layout_element) => layout_element, + None => { + // The pseudo doesn't exist, return nothing. Chrome seems to query + // the element itself in this case, Firefox uses the resolved value. + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=29006 + return String::new(); + }, + } }, - Some(layout_element) => layout_element, + None => layout_element, }; let style = &*layout_element.resolved_style(); diff --git a/components/layout_2020/table/construct.rs b/components/layout_2020/table/construct.rs index 9e18aef04d2..6a7047aeb75 100644 --- a/components/layout_2020/table/construct.rs +++ b/components/layout_2020/table/construct.rs @@ -95,26 +95,16 @@ impl Table { parent_info: &NodeAndStyleInfo<Node>, contents: Vec<AnonymousTableContent<'dom, Node>>, propagated_data: PropagatedBoxTreeData, - ) -> IndependentFormattingContext + ) -> (NodeAndStyleInfo<Node>, IndependentFormattingContext) where Node: crate::dom::NodeExt<'dom>, { - let grid_and_wrapper_style = context - .shared_context() - .stylist - .style_for_anonymous::<Node::ConcreteElement>( - &context.shared_context().guards, - &PseudoElement::ServoAnonymousTable, - &parent_info.style, - ); - let anonymous_info = parent_info.new_anonymous(grid_and_wrapper_style.clone()); - - let mut table_builder = TableBuilderTraversal::new( - context, - &anonymous_info, - grid_and_wrapper_style.clone(), - propagated_data, - ); + let table_info = parent_info + .pseudo(context, PseudoElement::ServoAnonymousTable) + .expect("Should never fail to create anonymous table info."); + let table_style = table_info.style.clone(); + let mut table_builder = + TableBuilderTraversal::new(context, &table_info, table_style.clone(), propagated_data); for content in contents { match content { @@ -137,12 +127,14 @@ impl Table { let mut table = table_builder.finish(); table.anonymous = true; - IndependentFormattingContext { - base: LayoutBoxBase::new((&anonymous_info).into(), grid_and_wrapper_style), + let ifc = IndependentFormattingContext { + base: LayoutBoxBase::new((&table_info).into(), table_style), contents: IndependentFormattingContextContents::NonReplaced( IndependentNonReplacedContents::Table(table), ), - } + }; + + (table_info, ifc) } /// Push a new slot into the last row of this table. @@ -686,17 +678,10 @@ where } let row_content = std::mem::take(&mut self.current_anonymous_row_content); - let context = self.context; - let anonymous_style = self - .context - .shared_context() - .stylist - .style_for_anonymous::<Node::ConcreteElement>( - &context.shared_context().guards, - &PseudoElement::ServoAnonymousTableRow, - &self.info.style, - ); - let anonymous_info = self.info.new_anonymous(anonymous_style.clone()); + let anonymous_info = self + .info + .pseudo(self.context, PseudoElement::ServoAnonymousTableRow) + .expect("Should never fail to create anonymous row info."); let mut row_builder = TableRowBuilder::new(self, &anonymous_info, self.current_propagated_data); @@ -718,9 +703,10 @@ where row_builder.finish(); + let style = anonymous_info.style.clone(); self.push_table_row(TableTrack { base_fragment_info: (&anonymous_info).into(), - style: anonymous_style, + style, group_index: self.current_row_group_index, is_anonymous: true, }); @@ -958,16 +944,10 @@ where } let context = self.table_traversal.context; - let anonymous_style = context - .shared_context() - .stylist - .style_for_anonymous::<Node::ConcreteElement>( - &context.shared_context().guards, - &PseudoElement::ServoAnonymousTableCell, - &self.info.style, - ); - let anonymous_info = self.info.new_anonymous(anonymous_style); - + let anonymous_info = self + .info + .pseudo(context, PseudoElement::ServoAnonymousTableCell) + .expect("Should never fail to create anonymous table cell info"); let propagated_data = self.propagated_data.disallowing_percentage_table_columns(); let mut builder = BlockContainerBuilder::new(context, &anonymous_info, propagated_data); @@ -1025,12 +1005,14 @@ where // 65534 and `colspan` to 1000, so we also enforce the same limits // when dealing with arbitrary DOM elements (perhaps created via // script). - let (rowspan, colspan) = info.node.map_or((1, 1), |node| { - let node = node.to_threadsafe(); + let (rowspan, colspan) = if info.pseudo_element_type.is_none() { + let node = info.node.to_threadsafe(); let rowspan = node.get_rowspan().unwrap_or(1).min(65534) as usize; let colspan = node.get_colspan().unwrap_or(1).min(1000) as usize; (rowspan, colspan) - }); + } else { + (1, 1) + }; let propagated_data = self.propagated_data.disallowing_percentage_table_columns(); @@ -1139,10 +1121,16 @@ fn add_column<'dom, Node>( ) where Node: NodeExt<'dom>, { - let span = column_info - .node - .and_then(|node| node.to_threadsafe().get_span()) - .map_or(1, |span| span.min(1000) as usize); + let span = if column_info.pseudo_element_type.is_none() { + column_info + .node + .to_threadsafe() + .get_span() + .unwrap_or(1) + .min(1000) as usize + } else { + 1 + }; collection.extend( repeat(TableTrack { diff --git a/components/script/layout_dom/element.rs b/components/script/layout_dom/element.rs index 7237ec68d37..6a267f16077 100644 --- a/components/script/layout_dom/element.rs +++ b/components/script/layout_dom/element.rs @@ -14,6 +14,7 @@ use script_layout_interface::wrapper_traits::{ LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode, }; use script_layout_interface::{LayoutNodeType, StyleData}; +use selectors::Element as _; use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; use selectors::bloom::{BLOOM_HASH_MASK, BloomFilter}; use selectors::matching::{ElementSelectorFlags, MatchingContext, VisitedHandlingMode}; @@ -805,11 +806,35 @@ impl<'dom> ThreadSafeLayoutElement<'dom> for ServoThreadSafeLayoutElement<'dom> self.pseudo } - fn with_pseudo(&self, pseudo: PseudoElement) -> Self { - ServoThreadSafeLayoutElement { - element: self.element, - pseudo: Some(pseudo), + fn with_pseudo(&self, pseudo_element_type: PseudoElement) -> Option<Self> { + if pseudo_element_type.is_eager() && + self.style_data() + .styles + .pseudos + .get(&pseudo_element_type) + .is_none() + { + return None; + } + + if pseudo_element_type == PseudoElement::DetailsSummary && + (!self.has_local_name(&local_name!("details")) || !self.has_namespace(&ns!(html))) + { + return None; } + + if pseudo_element_type == PseudoElement::DetailsContent && + (!self.has_local_name(&local_name!("details")) || + !self.has_namespace(&ns!(html)) || + self.get_attr(&ns!(), &local_name!("open")).is_none()) + { + return None; + } + + Some(ServoThreadSafeLayoutElement { + element: self.element, + pseudo: Some(pseudo_element_type), + }) } fn type_id(&self) -> Option<LayoutNodeType> { diff --git a/components/script/layout_dom/node.rs b/components/script/layout_dom/node.rs index 83c1b8dd57a..213070181ef 100644 --- a/components/script/layout_dom/node.rs +++ b/components/script/layout_dom/node.rs @@ -436,8 +436,8 @@ impl<'dom> ServoThreadSafeLayoutNodeChildrenIterator<'dom> { pub fn new(parent: ServoThreadSafeLayoutNode<'dom>) -> Self { let first_child = match parent.pseudo_element() { None => parent - .get_pseudo(PseudoElement::Before) - .or_else(|| parent.get_details_summary_pseudo()) + .with_pseudo(PseudoElement::Before) + .or_else(|| parent.with_pseudo(PseudoElement::DetailsSummary)) .or_else(|| unsafe { parent.dangerous_first_child() }), Some(PseudoElement::DetailsContent) | Some(PseudoElement::DetailsSummary) => unsafe { parent.dangerous_first_child() @@ -503,18 +503,18 @@ impl<'dom> Iterator for ServoThreadSafeLayoutNodeChildrenIterator<'dom> { self.current_node = match node.pseudo_element() { Some(PseudoElement::Before) => self .parent_node - .get_details_summary_pseudo() + .with_pseudo(PseudoElement::DetailsSummary) .or_else(|| unsafe { self.parent_node.dangerous_first_child() }) - .or_else(|| self.parent_node.get_pseudo(PseudoElement::After)), + .or_else(|| self.parent_node.with_pseudo(PseudoElement::After)), Some(PseudoElement::DetailsSummary) => { - self.parent_node.get_details_content_pseudo() + self.parent_node.with_pseudo(PseudoElement::DetailsContent) }, Some(PseudoElement::DetailsContent) => { - self.parent_node.get_pseudo(PseudoElement::After) + self.parent_node.with_pseudo(PseudoElement::After) }, Some(PseudoElement::After) => None, None | Some(_) => unsafe { node.dangerous_next_sibling() } - .or_else(|| self.parent_node.get_pseudo(PseudoElement::After)), + .or_else(|| self.parent_node.with_pseudo(PseudoElement::After)), }; } node diff --git a/components/shared/script_layout/wrapper_traits.rs b/components/shared/script_layout/wrapper_traits.rs index 5effbfdf40b..f64ec94a777 100644 --- a/components/shared/script_layout/wrapper_traits.rs +++ b/components/shared/script_layout/wrapper_traits.rs @@ -11,7 +11,7 @@ use std::sync::Arc as StdArc; use atomic_refcell::AtomicRef; use base::id::{BrowsingContextId, PipelineId}; use fonts_traits::ByteIndex; -use html5ever::{LocalName, Namespace, local_name, namespace_url, ns}; +use html5ever::{LocalName, Namespace}; use pixels::{Image, ImageMetadata}; use range::Range; use servo_arc::Arc; @@ -157,25 +157,6 @@ pub trait ThreadSafeLayoutNode<'dom>: Clone + Copy + Debug + NodeInfo + PartialE /// the parent until all the children have been processed. fn parent_style(&self) -> Arc<ComputedValues>; - fn get_pseudo(&self, pseudo_element: PseudoElement) -> Option<Self> { - self.as_element() - .and_then(|element| element.get_pseudo(pseudo_element)) - .as_ref() - .map(ThreadSafeLayoutElement::as_node) - } - - fn get_details_summary_pseudo(&self) -> Option<Self> { - self.as_element() - .and_then(|el| el.get_details_summary_pseudo()) - .map(|el| el.as_node()) - } - - fn get_details_content_pseudo(&self) -> Option<Self> { - self.as_element() - .and_then(|el| el.get_details_content_pseudo()) - .map(|el| el.as_node()) - } - fn debug_id(self) -> usize; /// Returns an iterator over this node's children. @@ -266,6 +247,13 @@ pub trait ThreadSafeLayoutNode<'dom>: Clone + Copy + Debug + NodeInfo + PartialE fn fragment_type(&self) -> FragmentType { self.pseudo_element().into() } + + fn with_pseudo(&self, pseudo_element_type: PseudoElement) -> Option<Self> { + self.as_element() + .and_then(|element| element.with_pseudo(pseudo_element_type)) + .as_ref() + .map(ThreadSafeLayoutElement::as_node) + } } pub trait ThreadSafeLayoutElement<'dom>: @@ -283,7 +271,16 @@ pub trait ThreadSafeLayoutElement<'dom>: /// Creates a new `ThreadSafeLayoutElement` for the same `LayoutElement` /// with a different pseudo-element type. - fn with_pseudo(&self, pseudo: PseudoElement) -> Self; + /// + /// Returns `None` if this pseudo doesn't apply to the given element for one of + /// the following reasons: + /// + /// 1. `pseudo` is eager and is not defined in the stylesheet. In this case, there + /// is not reason to process the pseudo element at all. + /// 2. `pseudo` is for `::servo-details-summary` or `::servo-details-content` and + /// it doesn't apply to this element, either because it isn't a details or is + /// in the wrong state. + fn with_pseudo(&self, pseudo: PseudoElement) -> Option<Self>; /// Returns the type ID of this node. /// Returns `None` if this is a pseudo-element; otherwise, returns `Some`. @@ -309,42 +306,6 @@ pub trait ThreadSafeLayoutElement<'dom>: fn pseudo_element(&self) -> Option<PseudoElement>; - #[inline] - fn get_pseudo(&self, pseudo_element: PseudoElement) -> Option<Self> { - if self - .style_data() - .styles - .pseudos - .get(&pseudo_element) - .is_some() - { - Some(self.with_pseudo(pseudo_element)) - } else { - None - } - } - - #[inline] - fn get_details_summary_pseudo(&self) -> Option<Self> { - if self.has_local_name(&local_name!("details")) && self.has_namespace(&ns!(html)) { - Some(self.with_pseudo(PseudoElement::DetailsSummary)) - } else { - None - } - } - - #[inline] - fn get_details_content_pseudo(&self) -> Option<Self> { - if self.has_local_name(&local_name!("details")) && - self.has_namespace(&ns!(html)) && - self.get_attr(&ns!(), &local_name!("open")).is_some() - { - Some(self.with_pseudo(PseudoElement::DetailsContent)) - } else { - None - } - } - /// Returns the style results for the given node. If CSS selector matching /// has not yet been performed, fails. /// diff --git a/resources/servo.css b/resources/servo.css index cea5caedc69..fdabeb30d0c 100644 --- a/resources/servo.css +++ b/resources/servo.css @@ -202,6 +202,8 @@ svg > * { *|*::-servo-legacy-text { text-overflow: inherit; overflow: inherit; + padding: unset; + margin: unset; } *|*::-servo-legacy-table-wrapper { diff --git a/tests/wpt/meta/css/cssom-view/elementFromPoint-list-001.html.ini b/tests/wpt/meta/css/cssom-view/elementFromPoint-list-001.html.ini deleted file mode 100644 index 1f892861935..00000000000 --- a/tests/wpt/meta/css/cssom-view/elementFromPoint-list-001.html.ini +++ /dev/null @@ -1,15 +0,0 @@ -[elementFromPoint-list-001.html] - [<li>Image Outside 2</li>] - expected: FAIL - - [<li>Outside 2</li>] - expected: FAIL - - [<li>Image Outside 1</li>] - expected: FAIL - - [<li>Outside 3</li>] - expected: FAIL - - [<li>Outside 1</li>] - expected: FAIL diff --git a/tests/wpt/meta/intersection-observer/intersection-ratio-with-fractional-bounds-2.html.ini b/tests/wpt/meta/intersection-observer/intersection-ratio-with-fractional-bounds-2.html.ini deleted file mode 100644 index 57199041d19..00000000000 --- a/tests/wpt/meta/intersection-observer/intersection-ratio-with-fractional-bounds-2.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[intersection-ratio-with-fractional-bounds-2.html] - [IntersectionObserver ratio with fractional bounds] - expected: FAIL |