mirror of
https://github.com/servo/servo.git
synced 2025-09-27 23:30:08 +01:00
layout: Support storing layout data for two-level nested pseudo-elements (#38678)
Add basic support for storing layout data for pseudo-elements nested to up to two levels. This removes the last unstored layout result and fixes a double-borrow issue. This change does not add properly parsing nor styling of these element types, but does prepare for those changes which must come from stylo. Testing: This fixes a intermittent panic in `tests/wpt/tests/css/css-lists/nested-marker-styling.html` Fixes: #38177. Closes: #38183. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
43da933247
commit
99ce81cf64
10 changed files with 273 additions and 177 deletions
|
@ -34,7 +34,7 @@ use crate::taffy::TaffyItemBox;
|
|||
#[derive(MallocSizeOf)]
|
||||
pub struct PseudoLayoutData {
|
||||
pseudo: PseudoElement,
|
||||
box_slot: ArcRefCell<Option<LayoutBox>>,
|
||||
data: ArcRefCell<InnerDOMLayoutData>,
|
||||
}
|
||||
|
||||
/// The data that is stored in each DOM node that is used by layout.
|
||||
|
@ -45,21 +45,64 @@ pub struct InnerDOMLayoutData {
|
|||
}
|
||||
|
||||
impl InnerDOMLayoutData {
|
||||
pub(crate) fn for_pseudo(
|
||||
fn pseudo_layout_data(
|
||||
&self,
|
||||
pseudo_element: Option<PseudoElement>,
|
||||
) -> Option<AtomicRef<Option<LayoutBox>>> {
|
||||
let Some(pseudo_element) = pseudo_element else {
|
||||
return Some(self.self_box.borrow());
|
||||
};
|
||||
|
||||
pseudo_element: PseudoElement,
|
||||
) -> Option<ArcRefCell<InnerDOMLayoutData>> {
|
||||
for pseudo_layout_data in self.pseudo_boxes.iter() {
|
||||
if pseudo_element == pseudo_layout_data.pseudo {
|
||||
return Some(pseudo_layout_data.box_slot.borrow());
|
||||
return Some(pseudo_layout_data.data.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn create_pseudo_layout_data(
|
||||
&mut self,
|
||||
pseudo_element: PseudoElement,
|
||||
) -> ArcRefCell<InnerDOMLayoutData> {
|
||||
let data: ArcRefCell<InnerDOMLayoutData> = Default::default();
|
||||
self.pseudo_boxes.push(PseudoLayoutData {
|
||||
pseudo: pseudo_element,
|
||||
data: data.clone(),
|
||||
});
|
||||
data
|
||||
}
|
||||
|
||||
fn fragments(&self) -> Vec<Fragment> {
|
||||
self.self_box
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(LayoutBox::fragments)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn repair_style(&self, node: &ServoThreadSafeLayoutNode, context: &SharedStyleContext) {
|
||||
if let Some(layout_object) = &*self.self_box.borrow() {
|
||||
layout_object.repair_style(context, node, &node.style(context));
|
||||
}
|
||||
|
||||
for pseudo_layout_data in self.pseudo_boxes.iter() {
|
||||
let Some(node_with_pseudo) = node.with_pseudo(pseudo_layout_data.pseudo) else {
|
||||
continue;
|
||||
};
|
||||
pseudo_layout_data
|
||||
.data
|
||||
.borrow()
|
||||
.repair_style(&node_with_pseudo, context);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
fn clear_fragment_layout_cache(&self) {
|
||||
if let Some(data) = self.self_box.borrow().as_ref() {
|
||||
data.clear_fragment_layout_cache();
|
||||
}
|
||||
for pseudo_layout_data in self.pseudo_boxes.iter() {
|
||||
pseudo_layout_data
|
||||
.data
|
||||
.borrow()
|
||||
.clear_fragment_layout_cache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,14 +234,6 @@ impl From<ArcRefCell<Option<LayoutBox>>> for BoxSlot<'_> {
|
|||
|
||||
/// A mutable reference to a `LayoutBox` stored in a DOM element.
|
||||
impl BoxSlot<'_> {
|
||||
pub(crate) fn dummy() -> Self {
|
||||
let slot = None;
|
||||
Self {
|
||||
slot,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set(mut self, box_: LayoutBox) {
|
||||
if let Some(slot) = &mut self.slot {
|
||||
*slot.borrow_mut() = Some(box_);
|
||||
|
@ -233,10 +268,9 @@ pub(crate) trait NodeExt<'dom> {
|
|||
fn as_svg(&self) -> Option<SVGElementData>;
|
||||
fn as_typeless_object_with_data_attribute(&self) -> Option<String>;
|
||||
|
||||
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, pseudo_element: PseudoElement) -> BoxSlot<'dom>;
|
||||
fn ensure_inner_layout_data(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData>;
|
||||
fn inner_layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>>;
|
||||
fn box_slot(&self) -> BoxSlot<'dom>;
|
||||
|
||||
/// Remove boxes for the element itself, and all of its pseudo-element boxes.
|
||||
fn unset_all_boxes(&self);
|
||||
|
@ -327,11 +361,11 @@ impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
|||
.map(|string| string.to_owned())
|
||||
}
|
||||
|
||||
fn layout_data_mut(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData> {
|
||||
if ThreadSafeLayoutNode::layout_data(self).is_none() {
|
||||
fn ensure_inner_layout_data(&self) -> AtomicRefMut<'dom, InnerDOMLayoutData> {
|
||||
if self.layout_data().is_none() {
|
||||
self.initialize_layout_data::<DOMLayoutData>();
|
||||
}
|
||||
ThreadSafeLayoutNode::layout_data(self)
|
||||
self.layout_data()
|
||||
.unwrap()
|
||||
.as_any()
|
||||
.downcast_ref::<DOMLayoutData>()
|
||||
|
@ -340,8 +374,8 @@ impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
|||
.borrow_mut()
|
||||
}
|
||||
|
||||
fn layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>> {
|
||||
ThreadSafeLayoutNode::layout_data(self).map(|data| {
|
||||
fn inner_layout_data(&self) -> Option<AtomicRef<'dom, InnerDOMLayoutData>> {
|
||||
self.layout_data().map(|data| {
|
||||
data.as_any()
|
||||
.downcast_ref::<DOMLayoutData>()
|
||||
.unwrap()
|
||||
|
@ -350,22 +384,38 @@ impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
|||
})
|
||||
}
|
||||
|
||||
fn element_box_slot(&self) -> BoxSlot<'dom> {
|
||||
self.layout_data_mut().self_box.clone().into()
|
||||
}
|
||||
fn box_slot(&self) -> BoxSlot<'dom> {
|
||||
let pseudo_element_chain = self.pseudo_element_chain();
|
||||
let Some(primary) = pseudo_element_chain.primary else {
|
||||
return self.ensure_inner_layout_data().self_box.clone().into();
|
||||
};
|
||||
|
||||
fn pseudo_element_box_slot(&self, pseudo_element: PseudoElement) -> BoxSlot<'dom> {
|
||||
let mut layout_data = self.layout_data_mut();
|
||||
let box_slot = ArcRefCell::new(None);
|
||||
layout_data.pseudo_boxes.push(PseudoLayoutData {
|
||||
pseudo: pseudo_element,
|
||||
box_slot: box_slot.clone(),
|
||||
});
|
||||
box_slot.into()
|
||||
let Some(secondary) = pseudo_element_chain.secondary else {
|
||||
let primary_layout_data = self
|
||||
.ensure_inner_layout_data()
|
||||
.create_pseudo_layout_data(primary);
|
||||
return primary_layout_data.borrow().self_box.clone().into();
|
||||
};
|
||||
|
||||
// It's *very* important that this not borrow the element's main
|
||||
// `InnerLayoutData`. Primary pseudo-elements are processed at the same recursion
|
||||
// level as the main data, so the `BoxSlot` is created sequentially with other
|
||||
// primary pseudo-elements and the element itself. The secondary pseudo-element is
|
||||
// one level deep, so could be happening in parallel with the primary
|
||||
// pseudo-elements or main element layout.
|
||||
let primary_layout_data = self
|
||||
.inner_layout_data()
|
||||
.expect("Should already have element InnerLayoutData here.")
|
||||
.pseudo_layout_data(primary)
|
||||
.expect("Should already have primary pseudo-element InnerLayoutData here");
|
||||
let secondary_layout_data = primary_layout_data
|
||||
.borrow_mut()
|
||||
.create_pseudo_layout_data(secondary);
|
||||
secondary_layout_data.borrow().self_box.clone().into()
|
||||
}
|
||||
|
||||
fn unset_all_boxes(&self) {
|
||||
let mut layout_data = self.layout_data_mut();
|
||||
let mut layout_data = self.ensure_inner_layout_data();
|
||||
*layout_data.self_box.borrow_mut() = None;
|
||||
layout_data.pseudo_boxes.clear();
|
||||
|
||||
|
@ -374,48 +424,31 @@ impl<'dom> NodeExt<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
|||
}
|
||||
|
||||
fn unset_all_pseudo_boxes(&self) {
|
||||
self.layout_data_mut().pseudo_boxes.clear();
|
||||
self.ensure_inner_layout_data().pseudo_boxes.clear();
|
||||
}
|
||||
|
||||
fn clear_fragment_layout_cache(&self) {
|
||||
let data = self.layout_data_mut();
|
||||
if let Some(data) = data.self_box.borrow_mut().as_ref() {
|
||||
data.clear_fragment_layout_cache();
|
||||
}
|
||||
|
||||
for pseudo_layout_data in data.pseudo_boxes.iter() {
|
||||
if let Some(layout_box) = pseudo_layout_data.box_slot.borrow().as_ref() {
|
||||
layout_box.clear_fragment_layout_cache();
|
||||
}
|
||||
if let Some(inner_layout_data) = self.inner_layout_data() {
|
||||
inner_layout_data.clear_fragment_layout_cache();
|
||||
}
|
||||
}
|
||||
|
||||
fn fragments_for_pseudo(&self, pseudo_element: Option<PseudoElement>) -> Vec<Fragment> {
|
||||
let Some(layout_data) = NodeExt::layout_data(self) else {
|
||||
let Some(layout_data) = self.inner_layout_data() else {
|
||||
return vec![];
|
||||
};
|
||||
let Some(layout_data) = layout_data.for_pseudo(pseudo_element) else {
|
||||
return vec![];
|
||||
};
|
||||
layout_data
|
||||
.as_ref()
|
||||
.map(LayoutBox::fragments)
|
||||
.unwrap_or_default()
|
||||
match pseudo_element {
|
||||
Some(pseudo_element) => layout_data
|
||||
.pseudo_layout_data(pseudo_element)
|
||||
.map(|pseudo_layout_data| pseudo_layout_data.borrow().fragments())
|
||||
.unwrap_or_default(),
|
||||
None => layout_data.fragments(),
|
||||
}
|
||||
}
|
||||
|
||||
fn repair_style(&self, context: &SharedStyleContext) {
|
||||
let data = self.layout_data_mut();
|
||||
if let Some(layout_object) = &*data.self_box.borrow() {
|
||||
let style = self.style(context);
|
||||
layout_object.repair_style(context, self, &style);
|
||||
}
|
||||
|
||||
for pseudo_layout_data in data.pseudo_boxes.iter() {
|
||||
if let Some(layout_box) = pseudo_layout_data.box_slot.borrow().as_ref() {
|
||||
if let Some(node) = self.with_pseudo(pseudo_layout_data.pseudo) {
|
||||
layout_box.repair_style(context, self, &node.style(context));
|
||||
}
|
||||
}
|
||||
if let Some(layout_data) = self.inner_layout_data() {
|
||||
layout_data.repair_style(self, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@ use std::borrow::Cow;
|
|||
|
||||
use fonts::ByteIndex;
|
||||
use html5ever::{LocalName, local_name};
|
||||
use layout_api::wrapper_traits::{ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||||
use layout_api::wrapper_traits::{
|
||||
PseudoElementChain, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
|
||||
};
|
||||
use layout_api::{LayoutDamage, LayoutElementType, LayoutNodeType};
|
||||
use range::Range;
|
||||
use script::layout_dom::ServoThreadSafeLayoutNode;
|
||||
|
@ -48,8 +50,8 @@ impl<'dom> NodeAndStyleInfo<'dom> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pseudo_element(&self) -> Option<PseudoElement> {
|
||||
self.node.pseudo_element()
|
||||
pub(crate) fn pseudo_element_chain(&self) -> PseudoElementChain {
|
||||
self.node.pseudo_element_chain()
|
||||
}
|
||||
|
||||
pub(crate) fn with_pseudo_element(
|
||||
|
@ -74,7 +76,7 @@ impl<'dom> NodeAndStyleInfo<'dom> {
|
|||
impl<'dom> From<&NodeAndStyleInfo<'dom>> for BaseFragmentInfo {
|
||||
fn from(info: &NodeAndStyleInfo<'dom>) -> Self {
|
||||
let threadsafe_node = info.node;
|
||||
let pseudo = info.node.pseudo_element();
|
||||
let pseudo_element_chain = info.node.pseudo_element_chain();
|
||||
let mut flags = FragmentFlags::empty();
|
||||
|
||||
// Anonymous boxes should not have a tag, because they should not take part in hit testing.
|
||||
|
@ -83,7 +85,7 @@ impl<'dom> From<&NodeAndStyleInfo<'dom>> for BaseFragmentInfo {
|
|||
// 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,
|
||||
pseudo_element_chain.innermost(),
|
||||
Some(PseudoElement::ServoAnonymousBox) |
|
||||
Some(PseudoElement::ServoAnonymousTable) |
|
||||
Some(PseudoElement::ServoAnonymousTableCell) |
|
||||
|
@ -122,7 +124,10 @@ impl<'dom> From<&NodeAndStyleInfo<'dom>> for BaseFragmentInfo {
|
|||
};
|
||||
|
||||
Self {
|
||||
tag: Some(Tag::new_pseudo(threadsafe_node.opaque(), pseudo)),
|
||||
tag: Some(Tag::new_pseudo(
|
||||
threadsafe_node.opaque(),
|
||||
pseudo_element_chain,
|
||||
)),
|
||||
flags,
|
||||
}
|
||||
}
|
||||
|
@ -232,7 +237,7 @@ fn traverse_element<'dom>(
|
|||
} else {
|
||||
let shared_inline_styles: SharedInlineStyles = (&info).into();
|
||||
element
|
||||
.element_box_slot()
|
||||
.box_slot()
|
||||
.set(LayoutBox::DisplayContents(shared_inline_styles.clone()));
|
||||
|
||||
handler.enter_display_contents(shared_inline_styles);
|
||||
|
@ -254,7 +259,7 @@ fn traverse_element<'dom>(
|
|||
NonReplacedContents::OfElement.into()
|
||||
};
|
||||
let display = display.used_value_for_contents(&contents);
|
||||
let box_slot = element.element_box_slot();
|
||||
let box_slot = element.box_slot();
|
||||
handler.handle_element(&info, display, contents, box_slot);
|
||||
},
|
||||
}
|
||||
|
@ -282,9 +287,7 @@ fn traverse_eager_pseudo_element<'dom>(
|
|||
Display::None => {},
|
||||
Display::Contents => {
|
||||
let items = generate_pseudo_element_content(&pseudo_element_info, context);
|
||||
let box_slot = pseudo_element_info
|
||||
.node
|
||||
.pseudo_element_box_slot(pseudo_element_type);
|
||||
let box_slot = pseudo_element_info.node.box_slot();
|
||||
let shared_inline_styles: SharedInlineStyles = (&pseudo_element_info).into();
|
||||
box_slot.set(LayoutBox::DisplayContents(shared_inline_styles.clone()));
|
||||
|
||||
|
@ -294,9 +297,7 @@ fn traverse_eager_pseudo_element<'dom>(
|
|||
},
|
||||
Display::GeneratingBox(display) => {
|
||||
let items = generate_pseudo_element_content(&pseudo_element_info, context);
|
||||
let box_slot = pseudo_element_info
|
||||
.node
|
||||
.pseudo_element_box_slot(pseudo_element_type);
|
||||
let box_slot = pseudo_element_info.node.box_slot();
|
||||
let contents = NonReplacedContents::OfPseudoElement(items).into();
|
||||
handler.handle_element(&pseudo_element_info, display, contents, box_slot);
|
||||
},
|
||||
|
@ -333,8 +334,7 @@ fn traverse_pseudo_element_contents<'dom>(
|
|||
anonymous_info,
|
||||
display_inline,
|
||||
Contents::Replaced(contents),
|
||||
info.node
|
||||
.pseudo_element_box_slot(PseudoElement::ServoAnonymousBox),
|
||||
anonymous_info.node.box_slot(),
|
||||
)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -171,11 +171,10 @@ impl BlockContainer {
|
|||
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(&marker_info, info, marker_contents)
|
||||
builder.handle_list_item_marker_inside(&marker_info, marker_contents)
|
||||
},
|
||||
ListStylePosition::Outside => builder.handle_list_item_marker_outside(
|
||||
&marker_info,
|
||||
info,
|
||||
marker_contents,
|
||||
info.style.clone(),
|
||||
),
|
||||
|
@ -299,9 +298,7 @@ impl<'dom, 'style> BlockContainerBuilder<'dom, 'style> {
|
|||
self.push_block_level_job_for_inline_formatting_context(inline_formatting_context);
|
||||
}
|
||||
|
||||
let box_slot = table_info
|
||||
.node
|
||||
.pseudo_element_box_slot(PseudoElement::ServoAnonymousTable);
|
||||
let box_slot = table_info.node.box_slot();
|
||||
self.block_level_boxes.push(BlockLevelJob {
|
||||
info: table_info,
|
||||
box_slot,
|
||||
|
@ -408,19 +405,9 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
|
|||
fn handle_list_item_marker_inside(
|
||||
&mut self,
|
||||
marker_info: &NodeAndStyleInfo<'dom>,
|
||||
container_info: &NodeAndStyleInfo<'dom>,
|
||||
contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
|
||||
) {
|
||||
// TODO: We do not currently support saving box slots for ::marker pseudo-elements
|
||||
// that are part nested in ::before and ::after pseudo elements. For now, just
|
||||
// forget about them once they are built.
|
||||
let box_slot = match container_info.pseudo_element() {
|
||||
Some(_) => BoxSlot::dummy(),
|
||||
None => marker_info
|
||||
.node
|
||||
.pseudo_element_box_slot(PseudoElement::Marker),
|
||||
};
|
||||
|
||||
let box_slot = marker_info.node.box_slot();
|
||||
self.handle_inline_level_element(
|
||||
marker_info,
|
||||
DisplayInside::Flow {
|
||||
|
@ -434,20 +421,10 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
|
|||
fn handle_list_item_marker_outside(
|
||||
&mut self,
|
||||
marker_info: &NodeAndStyleInfo<'dom>,
|
||||
container_info: &NodeAndStyleInfo<'dom>,
|
||||
contents: Vec<crate::dom_traversal::PseudoElementContentItem>,
|
||||
list_item_style: Arc<ComputedValues>,
|
||||
) {
|
||||
// TODO: We do not currently support saving box slots for ::marker pseudo-elements
|
||||
// that are part nested in ::before and ::after pseudo elements. For now, just
|
||||
// forget about them once they are built.
|
||||
let box_slot = match container_info.pseudo_element() {
|
||||
Some(_) => BoxSlot::dummy(),
|
||||
None => marker_info
|
||||
.node
|
||||
.pseudo_element_box_slot(PseudoElement::Marker),
|
||||
};
|
||||
|
||||
let box_slot = marker_info.node.box_slot();
|
||||
self.block_level_boxes.push(BlockLevelJob {
|
||||
info: marker_info.clone(),
|
||||
box_slot,
|
||||
|
@ -511,7 +488,7 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
|
|||
// 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(&marker_info, info, marker_contents)
|
||||
self.handle_list_item_marker_inside(&marker_info, marker_contents)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -682,7 +659,7 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
|
|||
inline_formatting_context: InlineFormattingContext,
|
||||
) {
|
||||
let layout_context = self.context;
|
||||
let info = self
|
||||
let anonymous_info = self
|
||||
.anonymous_box_info
|
||||
.get_or_insert_with(|| {
|
||||
self.info
|
||||
|
@ -691,12 +668,9 @@ impl<'dom> BlockContainerBuilder<'dom, '_> {
|
|||
})
|
||||
.clone();
|
||||
|
||||
let box_slot = self
|
||||
.info
|
||||
.node
|
||||
.pseudo_element_box_slot(PseudoElement::ServoAnonymousBox);
|
||||
let box_slot = anonymous_info.node.box_slot();
|
||||
self.block_level_boxes.push(BlockLevelJob {
|
||||
info,
|
||||
info: anonymous_info,
|
||||
box_slot,
|
||||
kind: BlockLevelCreator::SameFormattingContextBlock(
|
||||
IntermediateBlockContainer::InlineFormattingContext(
|
||||
|
|
|
@ -190,7 +190,7 @@ fn construct_for_root_element(
|
|||
|
||||
let root_box = ArcRefCell::new(root_box);
|
||||
root_element
|
||||
.element_box_slot()
|
||||
.box_slot()
|
||||
.set(LayoutBox::BlockLevel(root_box.clone()));
|
||||
vec![root_box]
|
||||
}
|
||||
|
@ -301,7 +301,7 @@ impl<'dom> IncrementalBoxTreeUpdate<'dom> {
|
|||
return None;
|
||||
}
|
||||
|
||||
let layout_data = NodeExt::layout_data(&potential_thread_safe_dirty_root_node)?;
|
||||
let layout_data = NodeExt::inner_layout_data(&potential_thread_safe_dirty_root_node)?;
|
||||
if !layout_data.pseudo_boxes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
|
||||
use bitflags::bitflags;
|
||||
use layout_api::combine_id_with_fragment_type;
|
||||
use layout_api::wrapper_traits::PseudoElementChain;
|
||||
use malloc_size_of::malloc_size_of_is_0;
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use style::dom::OpaqueNode;
|
||||
use style::selector_parser::PseudoElement;
|
||||
|
||||
/// This data structure stores fields that are common to all non-base
|
||||
/// Fragment types and should generally be the first member of all
|
||||
|
@ -114,23 +114,29 @@ malloc_size_of_is_0!(FragmentFlags);
|
|||
#[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq)]
|
||||
pub(crate) struct Tag {
|
||||
pub(crate) node: OpaqueNode,
|
||||
pub(crate) pseudo: Option<PseudoElement>,
|
||||
pub(crate) pseudo_element_chain: PseudoElementChain,
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
/// Create a new Tag for a non-pseudo element. This is mainly used for
|
||||
/// matching existing tags, since it does not accept an `info` argument.
|
||||
pub(crate) fn new(node: OpaqueNode) -> Self {
|
||||
Tag { node, pseudo: None }
|
||||
Tag {
|
||||
node,
|
||||
pseudo_element_chain: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new Tag for a pseudo element. This is mainly used for
|
||||
/// matching existing tags, since it does not accept an `info` argument.
|
||||
pub(crate) fn new_pseudo(node: OpaqueNode, pseudo: Option<PseudoElement>) -> Self {
|
||||
Tag { node, pseudo }
|
||||
pub(crate) fn new_pseudo(node: OpaqueNode, pseudo_element_chain: PseudoElementChain) -> Self {
|
||||
Tag {
|
||||
node,
|
||||
pseudo_element_chain,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_display_list_fragment_id(self) -> u64 {
|
||||
combine_id_with_fragment_type(self.node.id(), self.pseudo.into())
|
||||
combine_id_with_fragment_type(self.node.id(), self.pseudo_element_chain.primary.into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,8 +71,15 @@ impl FragmentTree {
|
|||
|
||||
fragment_tree.find(|fragment, _level, containing_block| {
|
||||
if let Some(tag) = fragment.tag() {
|
||||
invalid_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
|
||||
invalid_image_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo));
|
||||
// TODO: Support animations on nested pseudo-elements.
|
||||
invalid_animating_nodes.remove(&AnimationSetKey::new(
|
||||
tag.node,
|
||||
tag.pseudo_element_chain.primary,
|
||||
));
|
||||
invalid_image_animating_nodes.remove(&AnimationSetKey::new(
|
||||
tag.node,
|
||||
tag.pseudo_element_chain.primary,
|
||||
));
|
||||
}
|
||||
|
||||
fragment.set_containing_block(containing_block);
|
||||
|
|
|
@ -720,9 +720,9 @@ impl<'style, 'dom> TableBuilderTraversal<'style, 'dom> {
|
|||
});
|
||||
self.push_table_row(table_row.clone());
|
||||
|
||||
self.info
|
||||
anonymous_info
|
||||
.node
|
||||
.pseudo_element_box_slot(PseudoElement::ServoAnonymousTableRow)
|
||||
.box_slot()
|
||||
.set(LayoutBox::TableLevelBox(TableLevelBox::Track(table_row)))
|
||||
}
|
||||
|
||||
|
@ -989,9 +989,9 @@ impl<'style, 'builder, 'dom, 'a> TableRowBuilder<'style, 'builder, 'dom, 'a> {
|
|||
.builder
|
||||
.add_cell(new_table_cell.clone());
|
||||
|
||||
self.info
|
||||
anonymous_info
|
||||
.node
|
||||
.pseudo_element_box_slot(PseudoElement::ServoAnonymousTableCell)
|
||||
.box_slot()
|
||||
.set(LayoutBox::TableLevelBox(TableLevelBox::Cell(
|
||||
new_table_cell,
|
||||
)));
|
||||
|
@ -1027,7 +1027,7 @@ impl<'dom> TraversalHandler<'dom> for TableRowBuilder<'_, '_, 'dom, '_> {
|
|||
let cell = old_cell.unwrap_or_else(|| {
|
||||
// This value will already have filtered out rowspan=0
|
||||
// in quirks mode, so we don't have to worry about that.
|
||||
let (rowspan, colspan) = if info.pseudo_element().is_none() {
|
||||
let (rowspan, colspan) = if info.pseudo_element_chain().is_empty() {
|
||||
let rowspan = info.node.get_rowspan().unwrap_or(1) as usize;
|
||||
let colspan = info.node.get_colspan().unwrap_or(1) as usize;
|
||||
|
||||
|
@ -1148,7 +1148,7 @@ fn add_column(
|
|||
is_anonymous: bool,
|
||||
old_column: Option<ArcRefCell<TableTrack>>,
|
||||
) -> ArcRefCell<TableTrack> {
|
||||
let span = if column_info.pseudo_element().is_none() {
|
||||
let span = if column_info.pseudo_element_chain().is_empty() {
|
||||
column_info.node.get_span().unwrap_or(1)
|
||||
} else {
|
||||
1
|
||||
|
|
|
@ -10,7 +10,9 @@ use atomic_refcell::{AtomicRef, AtomicRefMut};
|
|||
use embedder_traits::UntrustedNodeAddress;
|
||||
use html5ever::{LocalName, Namespace, local_name, ns};
|
||||
use js::jsapi::JSObject;
|
||||
use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode};
|
||||
use layout_api::wrapper_traits::{
|
||||
LayoutNode, PseudoElementChain, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
|
||||
};
|
||||
use layout_api::{LayoutDamage, LayoutNodeType, StyleData};
|
||||
use selectors::Element as _;
|
||||
use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
|
||||
|
@ -967,11 +969,11 @@ impl<'dom> ::selectors::Element for ServoLayoutElement<'dom> {
|
|||
/// ever access safe properties and cannot race on elements.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ServoThreadSafeLayoutElement<'dom> {
|
||||
/// The wrapped [`ServoLayoutNode`].
|
||||
pub(super) element: ServoLayoutElement<'dom>,
|
||||
|
||||
/// The pseudo-element type for this element, or `None` if it is the non-pseudo
|
||||
/// version of the element.
|
||||
pub(super) pseudo: Option<PseudoElement>,
|
||||
/// The possibly nested [`PseudoElementChain`] for this node.
|
||||
pub(super) pseudo_element_chain: PseudoElementChain,
|
||||
}
|
||||
|
||||
impl<'dom> ServoThreadSafeLayoutElement<'dom> {
|
||||
|
@ -995,32 +997,32 @@ impl<'dom> ThreadSafeLayoutElement<'dom> for ServoThreadSafeLayoutElement<'dom>
|
|||
fn as_node(&self) -> ServoThreadSafeLayoutNode<'dom> {
|
||||
ServoThreadSafeLayoutNode {
|
||||
node: self.element.as_node(),
|
||||
pseudo: self.pseudo,
|
||||
pseudo_element_chain: self.pseudo_element_chain,
|
||||
}
|
||||
}
|
||||
|
||||
fn pseudo_element(&self) -> Option<PseudoElement> {
|
||||
self.pseudo
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain {
|
||||
self.pseudo_element_chain
|
||||
}
|
||||
|
||||
fn with_pseudo(&self, pseudo_element_type: PseudoElement) -> Option<Self> {
|
||||
if pseudo_element_type.is_eager() &&
|
||||
fn with_pseudo(&self, pseudo_element: PseudoElement) -> Option<Self> {
|
||||
if pseudo_element.is_eager() &&
|
||||
self.style_data()
|
||||
.styles
|
||||
.pseudos
|
||||
.get(&pseudo_element_type)
|
||||
.get(&pseudo_element)
|
||||
.is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if pseudo_element_type == PseudoElement::DetailsSummary &&
|
||||
if pseudo_element == PseudoElement::DetailsSummary &&
|
||||
(!self.has_local_name(&local_name!("details")) || !self.has_namespace(&ns!(html)))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if pseudo_element_type == PseudoElement::DetailsContent &&
|
||||
if pseudo_element == PseudoElement::DetailsContent &&
|
||||
(!self.has_local_name(&local_name!("details")) ||
|
||||
!self.has_namespace(&ns!(html)) ||
|
||||
self.get_attr(&ns!(), &local_name!("open")).is_none())
|
||||
|
@ -1028,9 +1030,16 @@ impl<'dom> ThreadSafeLayoutElement<'dom> for ServoThreadSafeLayoutElement<'dom>
|
|||
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::DetailsSummary);
|
||||
assert!(pseudo_element != PseudoElement::DetailsContent);
|
||||
}
|
||||
|
||||
Some(ServoThreadSafeLayoutElement {
|
||||
element: self.element,
|
||||
pseudo: Some(pseudo_element_type),
|
||||
pseudo_element_chain: self.pseudo_element_chain.with_pseudo(pseudo_element),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::iter::FusedIterator;
|
|||
use base::id::{BrowsingContextId, PipelineId};
|
||||
use fonts_traits::ByteIndex;
|
||||
use layout_api::wrapper_traits::{
|
||||
LayoutDataTrait, LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
|
||||
LayoutDataTrait, LayoutNode, PseudoElementChain, ThreadSafeLayoutElement, ThreadSafeLayoutNode,
|
||||
};
|
||||
use layout_api::{
|
||||
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType,
|
||||
|
@ -218,18 +218,20 @@ impl<'dom> LayoutNode<'dom> for ServoLayoutNode<'dom> {
|
|||
/// never access any other node apart from its parent.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ServoThreadSafeLayoutNode<'dom> {
|
||||
/// The wrapped `ServoLayoutNode`.
|
||||
/// The wrapped [`ServoLayoutNode`].
|
||||
pub(super) node: ServoLayoutNode<'dom>,
|
||||
|
||||
/// The pseudo-element type for this node, or `None` if it is the non-pseudo
|
||||
/// version of the element.
|
||||
pub(super) pseudo: Option<PseudoElement>,
|
||||
/// The possibly nested [`PseudoElementChain`] for this node.
|
||||
pub(super) pseudo_element_chain: PseudoElementChain,
|
||||
}
|
||||
|
||||
impl<'dom> ServoThreadSafeLayoutNode<'dom> {
|
||||
/// Creates a new `ServoThreadSafeLayoutNode` from the given `ServoLayoutNode`.
|
||||
pub fn new(node: ServoLayoutNode<'dom>) -> Self {
|
||||
ServoThreadSafeLayoutNode { node, pseudo: None }
|
||||
ServoThreadSafeLayoutNode {
|
||||
node,
|
||||
pseudo_element_chain: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the interior of this node as a `LayoutDom`. This is highly unsafe for layout to
|
||||
|
@ -267,7 +269,8 @@ impl<'dom> ServoThreadSafeLayoutNode<'dom> {
|
|||
// rendering it as a bare html element.
|
||||
pub fn is_single_line_text_input(&self) -> bool {
|
||||
self.type_id() == Some(LayoutNodeType::Element(LayoutElementType::HTMLInputElement)) ||
|
||||
(self.pseudo.is_none() && self.node.node.is_text_container_of_single_line_input())
|
||||
(self.pseudo_element_chain.is_empty() &&
|
||||
self.node.node.is_text_container_of_single_line_input())
|
||||
}
|
||||
|
||||
pub fn is_text_input(&self) -> bool {
|
||||
|
@ -316,12 +319,12 @@ impl<'dom> ThreadSafeLayoutNode<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
|||
unsafe { self.get_jsmanaged().opaque() }
|
||||
}
|
||||
|
||||
fn pseudo_element(&self) -> Option<PseudoElement> {
|
||||
self.pseudo
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain {
|
||||
self.pseudo_element_chain
|
||||
}
|
||||
|
||||
fn type_id(&self) -> Option<LayoutNodeType> {
|
||||
if self.pseudo.is_none() {
|
||||
if self.pseudo_element_chain.is_empty() {
|
||||
Some(self.node.type_id())
|
||||
} else {
|
||||
None
|
||||
|
@ -356,7 +359,7 @@ impl<'dom> ThreadSafeLayoutNode<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
|||
.as_element()
|
||||
.map(|el| ServoThreadSafeLayoutElement {
|
||||
element: el,
|
||||
pseudo: self.pseudo,
|
||||
pseudo_element_chain: self.pseudo_element_chain,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -460,6 +463,13 @@ impl<'dom> ThreadSafeLayoutNode<'dom> for ServoThreadSafeLayoutNode<'dom> {
|
|||
.get_rowspan()
|
||||
}
|
||||
}
|
||||
|
||||
fn with_pseudo_element_chain(&self, pseudo_element_chain: PseudoElementChain) -> Self {
|
||||
Self {
|
||||
node: self.node,
|
||||
pseudo_element_chain,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ServoThreadSafeLayoutNodeChildrenIterator<'dom> {
|
||||
|
|
|
@ -11,6 +11,7 @@ use atomic_refcell::AtomicRef;
|
|||
use base::id::{BrowsingContextId, PipelineId};
|
||||
use fonts_traits::ByteIndex;
|
||||
use html5ever::{LocalName, Namespace};
|
||||
use malloc_size_of_derive::MallocSizeOf;
|
||||
use net_traits::image_cache::Image;
|
||||
use pixels::ImageMetadata;
|
||||
use range::Range;
|
||||
|
@ -25,8 +26,8 @@ use style::selector_parser::{PseudoElement, PseudoElementCascadeType, SelectorIm
|
|||
use style::stylist::RuleInclusion;
|
||||
|
||||
use crate::{
|
||||
FragmentType, GenericLayoutData, GenericLayoutDataTrait, HTMLCanvasData, HTMLMediaData,
|
||||
LayoutNodeType, SVGElementData, StyleData,
|
||||
GenericLayoutData, GenericLayoutDataTrait, HTMLCanvasData, HTMLMediaData, LayoutNodeType,
|
||||
SVGElementData, StyleData,
|
||||
};
|
||||
|
||||
pub trait LayoutDataTrait: GenericLayoutDataTrait + Default + Send + Sync + 'static {}
|
||||
|
@ -232,11 +233,7 @@ pub trait ThreadSafeLayoutNode<'dom>: Clone + Copy + Debug + NodeInfo + PartialE
|
|||
fn get_colspan(&self) -> Option<u32>;
|
||||
fn get_rowspan(&self) -> Option<u32>;
|
||||
|
||||
fn pseudo_element(&self) -> Option<PseudoElement>;
|
||||
|
||||
fn fragment_type(&self) -> FragmentType {
|
||||
self.pseudo_element().into()
|
||||
}
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain;
|
||||
|
||||
fn with_pseudo(&self, pseudo_element_type: PseudoElement) -> Option<Self> {
|
||||
self.as_element()
|
||||
|
@ -244,6 +241,8 @@ pub trait ThreadSafeLayoutNode<'dom>: Clone + Copy + Debug + NodeInfo + PartialE
|
|||
.as_ref()
|
||||
.map(ThreadSafeLayoutElement::as_node)
|
||||
}
|
||||
|
||||
fn with_pseudo_element_chain(&self, pseudo_element_chain: PseudoElementChain) -> Self;
|
||||
}
|
||||
|
||||
pub trait ThreadSafeLayoutElement<'dom>:
|
||||
|
@ -294,7 +293,7 @@ pub trait ThreadSafeLayoutElement<'dom>:
|
|||
|
||||
fn style_data(&self) -> AtomicRef<ElementData>;
|
||||
|
||||
fn pseudo_element(&self) -> Option<PseudoElement>;
|
||||
fn pseudo_element_chain(&self) -> PseudoElementChain;
|
||||
|
||||
/// Returns the style results for the given node. If CSS selector matching
|
||||
/// has not yet been performed, fails.
|
||||
|
@ -302,27 +301,25 @@ pub trait ThreadSafeLayoutElement<'dom>:
|
|||
/// Unlike the version on TNode, this handles pseudo-elements.
|
||||
#[inline]
|
||||
fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> {
|
||||
let data = self.style_data();
|
||||
match self.pseudo_element() {
|
||||
None => data.styles.primary().clone(),
|
||||
Some(style_pseudo) => {
|
||||
let get_style_for_pseudo_element =
|
||||
|base_style: &Arc<ComputedValues>, pseudo_element: PseudoElement| {
|
||||
// Precompute non-eagerly-cascaded pseudo-element styles if not
|
||||
// cached before.
|
||||
match style_pseudo.cascade_type() {
|
||||
match pseudo_element.cascade_type() {
|
||||
// Already computed during the cascade.
|
||||
PseudoElementCascadeType::Eager => self
|
||||
.style_data()
|
||||
.styles
|
||||
.pseudos
|
||||
.get(&style_pseudo)
|
||||
.get(&pseudo_element)
|
||||
.unwrap()
|
||||
.clone(),
|
||||
PseudoElementCascadeType::Precomputed => context
|
||||
.stylist
|
||||
.precomputed_values_for_pseudo::<Self::ConcreteElement>(
|
||||
&context.guards,
|
||||
&style_pseudo,
|
||||
Some(data.styles.primary()),
|
||||
&pseudo_element,
|
||||
Some(base_style),
|
||||
),
|
||||
PseudoElementCascadeType::Lazy => {
|
||||
context
|
||||
|
@ -330,16 +327,30 @@ pub trait ThreadSafeLayoutElement<'dom>:
|
|||
.lazily_compute_pseudo_element_style(
|
||||
&context.guards,
|
||||
self.unsafe_get(),
|
||||
&style_pseudo,
|
||||
&pseudo_element,
|
||||
RuleInclusion::All,
|
||||
data.styles.primary(),
|
||||
base_style,
|
||||
/* is_probe = */ false,
|
||||
/* matching_func = */ None,
|
||||
)
|
||||
.unwrap()
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let data = self.style_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(element_style, pseudo_element),
|
||||
None => return element_style.clone(),
|
||||
};
|
||||
match pseudo_element_chain.secondary {
|
||||
Some(pseudo_element) => {
|
||||
get_style_for_pseudo_element(&primary_pseudo_style, pseudo_element)
|
||||
},
|
||||
None => primary_pseudo_style,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -361,3 +372,49 @@ pub trait ThreadSafeLayoutElement<'dom>:
|
|||
/// As in that case, since this is an immutable borrow, we do not violate thread safety.
|
||||
fn is_root(&self) -> bool;
|
||||
}
|
||||
|
||||
/// A chain of pseudo-elements up to two levels deep. This is used to represent cases
|
||||
/// where a pseudo-element has its own child pseudo element (for instance
|
||||
/// `.div::after::marker`). If both [`Self::primary`] and [`Self::secondary`] are `None`,
|
||||
/// then this chain represents the element itself. Not all combinations of pseudo-elements
|
||||
/// are possible and we may not be able to calculate a style for all
|
||||
/// [`PseudoElementChain`]s.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Hash, MallocSizeOf, PartialEq)]
|
||||
pub struct PseudoElementChain {
|
||||
pub primary: Option<PseudoElement>,
|
||||
pub secondary: Option<PseudoElement>,
|
||||
}
|
||||
|
||||
impl PseudoElementChain {
|
||||
pub fn unnested(pseudo_element: PseudoElement) -> Self {
|
||||
Self {
|
||||
primary: Some(pseudo_element),
|
||||
secondary: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn innermost(&self) -> Option<PseudoElement> {
|
||||
self.secondary.or(self.primary)
|
||||
}
|
||||
|
||||
/// Return a possibly nested [`PseudoElementChain`]. Currently only `::before` and
|
||||
/// `::after` only support nesting. If the primary [`PseudoElement`] on the chain is
|
||||
/// not `::before` or `::after` a single element chain is returned for the given
|
||||
/// [`PseudoElement`].
|
||||
pub fn with_pseudo(&self, pseudo_element: PseudoElement) -> Self {
|
||||
match self.primary {
|
||||
Some(primary) if primary.is_before_or_after() => Self {
|
||||
primary: self.primary,
|
||||
secondary: Some(pseudo_element),
|
||||
},
|
||||
_ => {
|
||||
assert!(self.secondary.is_none());
|
||||
Self::unnested(pseudo_element)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.primary.is_none()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue