mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Add support for table fixups (#30868)
This adds support for fixing up tables so that internal table elements that are not properly parented in the DOM have the correct box tree structure according to the CSS Table specification [1]. Note that this only comes into play when building the DOM via script, as HTML 5 has its own table fixups that mean that the box tree construction fixups here are not necessary. There are no tests for this change. In general, it's hard to write tests against the shape of the box tree, because it depends on the DOM. We plan to test this via WPT tests once layout is complete. 1. https://drafts.csswg.org/css-tables/#table-internal-element Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
709d00583f
commit
81f5157522
7 changed files with 467 additions and 102 deletions
|
@ -2379,14 +2379,14 @@ impl Legalizer {
|
|||
FlowClass::TableRow => self.push_new_anonymous_flow::<E, _>(
|
||||
context,
|
||||
parent,
|
||||
&[PseudoElement::ServoLegacyAnonymousTableCell],
|
||||
&[PseudoElement::ServoAnonymousTableCell],
|
||||
SpecificFragmentInfo::TableCell,
|
||||
TableCellFlow::from_fragment,
|
||||
),
|
||||
FlowClass::Table | FlowClass::TableRowGroup => self.push_new_anonymous_flow::<E, _>(
|
||||
context,
|
||||
parent,
|
||||
&[PseudoElement::ServoLegacyAnonymousTableRow],
|
||||
&[PseudoElement::ServoAnonymousTableRow],
|
||||
SpecificFragmentInfo::TableRow,
|
||||
TableRowFlow::from_fragment,
|
||||
),
|
||||
|
|
|
@ -11,6 +11,7 @@ use style::computed_values::white_space::T as WhiteSpace;
|
|||
use style::properties::longhands::list_style_position::computed_value::T as ListStylePosition;
|
||||
use style::properties::ComputedValues;
|
||||
use style::selector_parser::PseudoElement;
|
||||
use style::str::char_is_whitespace;
|
||||
use style::values::specified::text::TextDecorationLine;
|
||||
|
||||
use crate::cell::ArcRefCell;
|
||||
|
@ -23,9 +24,10 @@ use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox};
|
|||
use crate::formatting_contexts::IndependentFormattingContext;
|
||||
use crate::positioned::AbsolutelyPositionedBox;
|
||||
use crate::style_ext::{ComputedValuesExt, DisplayGeneratingBox, DisplayInside, DisplayOutside};
|
||||
use crate::table::{AnonymousTableContent, Table};
|
||||
|
||||
impl BlockFormattingContext {
|
||||
pub fn construct<'dom, Node>(
|
||||
pub(crate) fn construct<'dom, Node>(
|
||||
context: &LayoutContext,
|
||||
info: &NodeAndStyleInfo<Node>,
|
||||
contents: NonReplacedContents,
|
||||
|
@ -35,15 +37,17 @@ impl BlockFormattingContext {
|
|||
where
|
||||
Node: NodeExt<'dom>,
|
||||
{
|
||||
let contents = BlockContainer::construct(
|
||||
Self::from_block_container(BlockContainer::construct(
|
||||
context,
|
||||
info,
|
||||
contents,
|
||||
propagated_text_decoration_line,
|
||||
is_list_item,
|
||||
);
|
||||
let contains_floats = contents.contains_floats();
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn from_block_container(contents: BlockContainer) -> Self {
|
||||
let contains_floats = contents.contains_floats();
|
||||
Self {
|
||||
contents,
|
||||
contains_floats,
|
||||
|
@ -96,6 +100,9 @@ enum BlockLevelCreator {
|
|||
display_inside: DisplayInside,
|
||||
contents: Contents,
|
||||
},
|
||||
AnonymousTable {
|
||||
table_block: ArcRefCell<BlockLevelBox>,
|
||||
},
|
||||
}
|
||||
|
||||
/// A block container that may still have to be constructed.
|
||||
|
@ -118,7 +125,7 @@ enum IntermediateBlockContainer {
|
|||
///
|
||||
/// This builder starts from the first child of a given DOM node
|
||||
/// and does a preorder traversal of all of its inclusive siblings.
|
||||
struct BlockContainerBuilder<'dom, 'style, Node> {
|
||||
pub(crate) struct BlockContainerBuilder<'dom, 'style, Node> {
|
||||
context: &'style LayoutContext<'style>,
|
||||
|
||||
/// This NodeAndStyleInfo contains the root node, the corresponding pseudo
|
||||
|
@ -167,6 +174,11 @@ struct BlockContainerBuilder<'dom, 'style, Node> {
|
|||
/// 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>>,
|
||||
|
||||
/// 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
|
||||
/// are found outside of a table.
|
||||
anonymous_table_content: Vec<AnonymousTableContent<'dom, Node>>,
|
||||
}
|
||||
|
||||
impl BlockContainer {
|
||||
|
@ -180,20 +192,8 @@ impl BlockContainer {
|
|||
where
|
||||
Node: NodeExt<'dom>,
|
||||
{
|
||||
let text_decoration_line =
|
||||
propagated_text_decoration_line | info.style.clone_text_decoration_line();
|
||||
let mut builder = BlockContainerBuilder {
|
||||
context,
|
||||
info,
|
||||
block_level_boxes: Vec::new(),
|
||||
ongoing_inline_formatting_context: InlineFormattingContext::new(
|
||||
text_decoration_line,
|
||||
/* has_first_formatted_line = */ true,
|
||||
/* ends_with_whitespace */ false,
|
||||
),
|
||||
ongoing_inline_boxes_stack: Vec::new(),
|
||||
anonymous_style: None,
|
||||
};
|
||||
let mut builder =
|
||||
BlockContainerBuilder::new(context, info, propagated_text_decoration_line);
|
||||
|
||||
if is_list_item {
|
||||
if let Some(marker_contents) = crate::lists::make_marker(context, info) {
|
||||
|
@ -214,27 +214,58 @@ impl BlockContainer {
|
|||
}
|
||||
|
||||
contents.traverse(context, info, &mut builder);
|
||||
builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert!(builder.ongoing_inline_boxes_stack.is_empty());
|
||||
impl<'dom, 'style, Node> BlockContainerBuilder<'dom, 'style, Node>
|
||||
where
|
||||
Node: NodeExt<'dom>,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
context: &'style LayoutContext,
|
||||
info: &'style NodeAndStyleInfo<Node>,
|
||||
propagated_text_decoration_line: TextDecorationLine,
|
||||
) -> Self {
|
||||
let text_decoration_line =
|
||||
propagated_text_decoration_line | info.style.clone_text_decoration_line();
|
||||
BlockContainerBuilder {
|
||||
context,
|
||||
info,
|
||||
block_level_boxes: Vec::new(),
|
||||
ongoing_inline_formatting_context: InlineFormattingContext::new(
|
||||
text_decoration_line,
|
||||
/* has_first_formatted_line = */ true,
|
||||
/* ends_with_whitespace */ false,
|
||||
),
|
||||
ongoing_inline_boxes_stack: Vec::new(),
|
||||
anonymous_style: None,
|
||||
anonymous_table_content: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
if !builder.ongoing_inline_formatting_context.is_empty() {
|
||||
if builder.block_level_boxes.is_empty() {
|
||||
pub(crate) fn finish(mut self) -> BlockContainer {
|
||||
debug_assert!(self.ongoing_inline_boxes_stack.is_empty());
|
||||
|
||||
self.finish_anonymous_table_if_needed();
|
||||
|
||||
if !self.ongoing_inline_formatting_context.is_empty() {
|
||||
if self.block_level_boxes.is_empty() {
|
||||
return BlockContainer::InlineFormattingContext(
|
||||
builder.ongoing_inline_formatting_context,
|
||||
self.ongoing_inline_formatting_context,
|
||||
);
|
||||
}
|
||||
builder.end_ongoing_inline_formatting_context();
|
||||
self.end_ongoing_inline_formatting_context();
|
||||
}
|
||||
|
||||
let block_level_boxes = if context.use_rayon {
|
||||
builder
|
||||
.block_level_boxes
|
||||
let context = self.context;
|
||||
let block_level_boxes = if self.context.use_rayon {
|
||||
self.block_level_boxes
|
||||
.into_par_iter()
|
||||
.map(|block_level_job| block_level_job.finish(context))
|
||||
.collect()
|
||||
} else {
|
||||
builder
|
||||
.block_level_boxes
|
||||
self.block_level_boxes
|
||||
.into_iter()
|
||||
.map(|block_level_job| block_level_job.finish(context))
|
||||
.collect()
|
||||
|
@ -242,6 +273,65 @@ impl BlockContainer {
|
|||
|
||||
BlockContainer::BlockLevelBoxes(block_level_boxes)
|
||||
}
|
||||
|
||||
fn finish_anonymous_table_if_needed(&mut self) {
|
||||
if self.anonymous_table_content.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Text decorations are not propagated to atomic inline-level descendants.
|
||||
// From https://drafts.csswg.org/css2/#lining-striking-props:
|
||||
// > Note that text decorations are not propagated to floating and absolutely
|
||||
// > positioned descendants, nor to the contents of atomic inline-level descendants
|
||||
// > such as inline blocks and inline tables.
|
||||
let inline_table = !self.ongoing_inline_boxes_stack.is_empty();
|
||||
let propagated_text_decoration_line = if inline_table {
|
||||
TextDecorationLine::NONE
|
||||
} else {
|
||||
self.ongoing_inline_formatting_context.text_decoration_line
|
||||
};
|
||||
|
||||
let contents: Vec<AnonymousTableContent<'dom, Node>> =
|
||||
self.anonymous_table_content.drain(..).collect();
|
||||
let last_text = match contents.last() {
|
||||
Some(AnonymousTableContent::Text(info, text)) => Some((info.clone(), text.clone())),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let ifc = Table::construct_anonymous(
|
||||
self.context,
|
||||
self.info,
|
||||
contents,
|
||||
propagated_text_decoration_line,
|
||||
);
|
||||
|
||||
if inline_table {
|
||||
self.ongoing_inline_formatting_context.ends_with_whitespace = false;
|
||||
self.current_inline_level_boxes()
|
||||
.push(ArcRefCell::new(InlineLevelBox::Atomic(ifc)));
|
||||
} else {
|
||||
let anonymous_info = self.info.new_replacing_style(ifc.style().clone());
|
||||
let table_block = ArcRefCell::new(BlockLevelBox::Independent(ifc));
|
||||
self.block_level_boxes.push(BlockLevelJob {
|
||||
info: anonymous_info,
|
||||
box_slot: BoxSlot::dummy(),
|
||||
kind: BlockLevelCreator::AnonymousTable { table_block },
|
||||
});
|
||||
}
|
||||
|
||||
// If the last element in the anonymous table content is whitespace, that
|
||||
// whitespace doesn't actually belong to the table. It should be processed outside
|
||||
// ie become a space between the anonymous table and the rest of the block
|
||||
// content. Anonymous tables are really only constructed around internal table
|
||||
// elements and the whitespace between them, so this trailing whitespace should
|
||||
// not be included.
|
||||
//
|
||||
// See https://drafts.csswg.org/css-tables/#fixup-algorithm sections "Remove
|
||||
// irrelevant boxes" and "Generate missing parents."
|
||||
if let Some((info, text)) = last_text {
|
||||
self.handle_text(&info, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'dom, Node> TraversalHandler<'dom, Node> for BlockContainerBuilder<'dom, '_, Node>
|
||||
|
@ -256,25 +346,37 @@ where
|
|||
box_slot: BoxSlot<'dom>,
|
||||
) {
|
||||
match display {
|
||||
DisplayGeneratingBox::OutsideInside { outside, inside } => match outside {
|
||||
DisplayOutside::Inline => box_slot.set(LayoutBox::InlineLevel(
|
||||
self.handle_inline_level_element(info, inside, contents),
|
||||
)),
|
||||
DisplayOutside::Block => {
|
||||
let box_style = info.style.get_box();
|
||||
// Floats and abspos cause blockification, so they only happen in this case.
|
||||
// https://drafts.csswg.org/css2/visuren.html#dis-pos-flo
|
||||
if box_style.position.is_absolutely_positioned() {
|
||||
self.handle_absolutely_positioned_element(info, inside, contents, box_slot)
|
||||
} else if box_style.float.is_floating() {
|
||||
self.handle_float_element(info, inside, contents, box_slot)
|
||||
} else {
|
||||
self.handle_block_level_element(info, inside, contents, box_slot)
|
||||
}
|
||||
},
|
||||
DisplayGeneratingBox::OutsideInside { outside, inside } => {
|
||||
self.finish_anonymous_table_if_needed();
|
||||
|
||||
match outside {
|
||||
DisplayOutside::Inline => box_slot.set(LayoutBox::InlineLevel(
|
||||
self.handle_inline_level_element(info, inside, contents),
|
||||
)),
|
||||
DisplayOutside::Block => {
|
||||
let box_style = info.style.get_box();
|
||||
// Floats and abspos cause blockification, so they only happen in this case.
|
||||
// https://drafts.csswg.org/css2/visuren.html#dis-pos-flo
|
||||
if box_style.position.is_absolutely_positioned() {
|
||||
self.handle_absolutely_positioned_element(
|
||||
info, inside, contents, box_slot,
|
||||
)
|
||||
} else if box_style.float.is_floating() {
|
||||
self.handle_float_element(info, inside, contents, box_slot)
|
||||
} else {
|
||||
self.handle_block_level_element(info, inside, contents, box_slot)
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
DisplayGeneratingBox::LayoutInternal(_) => {
|
||||
unreachable!("The result of blockification should never be layout-internal value.");
|
||||
self.anonymous_table_content
|
||||
.push(AnonymousTableContent::Element {
|
||||
info: info.clone(),
|
||||
display,
|
||||
contents,
|
||||
box_slot,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -284,6 +386,17 @@ where
|
|||
return;
|
||||
}
|
||||
|
||||
// If we are building an anonymous table ie this text directly followed internal
|
||||
// table elements that did not have a `<table>` ancestor, then we forward all
|
||||
// whitespace to the table builder.
|
||||
if !self.anonymous_table_content.is_empty() && input.chars().all(char_is_whitespace) {
|
||||
self.anonymous_table_content
|
||||
.push(AnonymousTableContent::Text(info.clone(), input));
|
||||
return;
|
||||
} else {
|
||||
self.finish_anonymous_table_if_needed();
|
||||
}
|
||||
|
||||
let (output, has_uncollapsible_content) = collapse_and_transform_whitespace(
|
||||
&input,
|
||||
info.style.get_inherited_text().white_space,
|
||||
|
@ -776,6 +889,7 @@ where
|
|||
display_inside,
|
||||
contents,
|
||||
))),
|
||||
BlockLevelCreator::AnonymousTable { table_block } => table_block,
|
||||
};
|
||||
self.box_slot
|
||||
.set(LayoutBox::BlockLevel(block_level_box.clone()));
|
||||
|
|
|
@ -38,6 +38,7 @@ pub mod float;
|
|||
pub mod inline;
|
||||
mod root;
|
||||
|
||||
pub(crate) use construct::BlockContainerBuilder;
|
||||
pub use root::{BoxTree, CanvasBackground};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
|
@ -7,13 +7,19 @@ use std::convert::{TryFrom, TryInto};
|
|||
|
||||
use log::warn;
|
||||
use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode;
|
||||
use style::selector_parser::PseudoElement;
|
||||
use style::str::char_is_whitespace;
|
||||
use style::values::specified::TextDecorationLine;
|
||||
|
||||
use super::{Table, TableSlot, TableSlotCell, TableSlotCoordinates, TableSlotOffset};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::dom::{BoxSlot, NodeExt};
|
||||
use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler};
|
||||
use crate::flow::BlockFormattingContext;
|
||||
use crate::flow::{BlockContainerBuilder, BlockFormattingContext};
|
||||
use crate::formatting_contexts::{
|
||||
IndependentFormattingContext, NonReplacedFormattingContext,
|
||||
NonReplacedFormattingContextContents,
|
||||
};
|
||||
use crate::style_ext::{DisplayGeneratingBox, DisplayLayoutInternal};
|
||||
|
||||
/// A reference to a slot and its coordinates in the table
|
||||
|
@ -33,6 +39,16 @@ impl<'a> ResolvedSlotAndLocation<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) enum AnonymousTableContent<'dom, Node> {
|
||||
Text(NodeAndStyleInfo<Node>, Cow<'dom, str>),
|
||||
Element {
|
||||
info: NodeAndStyleInfo<Node>,
|
||||
display: DisplayGeneratingBox,
|
||||
contents: Contents,
|
||||
box_slot: BoxSlot<'dom>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Table {
|
||||
pub(crate) fn construct<'dom>(
|
||||
context: &LayoutContext,
|
||||
|
@ -40,14 +56,60 @@ impl Table {
|
|||
contents: NonReplacedContents,
|
||||
propagated_text_decoration_line: TextDecorationLine,
|
||||
) -> Self {
|
||||
let mut traversal = TableBuilderTraversal {
|
||||
context,
|
||||
_info: info,
|
||||
propagated_text_decoration_line,
|
||||
builder: Default::default(),
|
||||
};
|
||||
let mut traversal =
|
||||
TableBuilderTraversal::new(context, info, propagated_text_decoration_line);
|
||||
contents.traverse(context, info, &mut traversal);
|
||||
traversal.builder.finish()
|
||||
traversal.finish()
|
||||
}
|
||||
|
||||
pub(crate) fn construct_anonymous<'dom, Node>(
|
||||
context: &LayoutContext,
|
||||
parent_info: &NodeAndStyleInfo<Node>,
|
||||
contents: Vec<AnonymousTableContent<'dom, Node>>,
|
||||
propagated_text_decoration_line: style::values::specified::TextDecorationLine,
|
||||
) -> IndependentFormattingContext
|
||||
where
|
||||
Node: crate::dom::NodeExt<'dom>,
|
||||
{
|
||||
let anonymous_style = context
|
||||
.shared_context()
|
||||
.stylist
|
||||
.style_for_anonymous::<Node::ConcreteElement>(
|
||||
&context.shared_context().guards,
|
||||
// TODO: This should be updated for Layout 2020 once we've determined
|
||||
// which styles should be inherited for tables.
|
||||
&PseudoElement::ServoLegacyAnonymousTable,
|
||||
&parent_info.style,
|
||||
);
|
||||
let anonymous_info = parent_info.new_replacing_style(anonymous_style.clone());
|
||||
|
||||
let mut table_builder =
|
||||
TableBuilderTraversal::new(context, &anonymous_info, propagated_text_decoration_line);
|
||||
|
||||
for content in contents {
|
||||
match content {
|
||||
AnonymousTableContent::Element {
|
||||
info,
|
||||
display,
|
||||
contents,
|
||||
box_slot,
|
||||
} => {
|
||||
table_builder.handle_element(&info, display, contents, box_slot);
|
||||
},
|
||||
AnonymousTableContent::Text(..) => {
|
||||
// This only happens if there was whitespace between our internal table elements.
|
||||
// We only collect that whitespace in case we need to re-emit trailing whitespace
|
||||
// after we've added our anonymous table.
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
IndependentFormattingContext::NonReplaced(NonReplacedFormattingContext {
|
||||
base_fragment_info: (&anonymous_info).into(),
|
||||
style: anonymous_style,
|
||||
content_sizes: None,
|
||||
contents: NonReplacedFormattingContextContents::Table(table_builder.finish()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Push a new slot into the last row of this table.
|
||||
|
@ -295,9 +357,9 @@ impl TableBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
struct TableBuilderTraversal<'a, Node> {
|
||||
context: &'a LayoutContext<'a>,
|
||||
_info: &'a NodeAndStyleInfo<Node>,
|
||||
pub(crate) struct TableBuilderTraversal<'style, 'dom, Node> {
|
||||
context: &'style LayoutContext<'style>,
|
||||
info: &'style NodeAndStyleInfo<Node>,
|
||||
|
||||
/// Propagated value for text-decoration-line, used to construct the block
|
||||
/// contents of table cells.
|
||||
|
@ -306,14 +368,83 @@ struct TableBuilderTraversal<'a, Node> {
|
|||
/// The [`TableBuilder`] for this [`TableBuilderTraversal`]. This is separated
|
||||
/// into another struct so that we can write unit tests against the builder.
|
||||
builder: TableBuilder,
|
||||
|
||||
current_anonymous_row_content: Vec<AnonymousTableContent<'dom, Node>>,
|
||||
}
|
||||
|
||||
impl<'a, 'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableBuilderTraversal<'a, Node>
|
||||
impl<'style, 'dom, Node> TableBuilderTraversal<'style, 'dom, Node>
|
||||
where
|
||||
Node: NodeExt<'dom>,
|
||||
{
|
||||
fn handle_text(&mut self, _info: &NodeAndStyleInfo<Node>, _text: Cow<'dom, str>) {
|
||||
// TODO: We should collect these contents into a new table cell.
|
||||
pub(crate) fn new(
|
||||
context: &'style LayoutContext<'style>,
|
||||
info: &'style NodeAndStyleInfo<Node>,
|
||||
propagated_text_decoration_line: TextDecorationLine,
|
||||
) -> Self {
|
||||
TableBuilderTraversal {
|
||||
context,
|
||||
info,
|
||||
propagated_text_decoration_line,
|
||||
builder: Default::default(),
|
||||
current_anonymous_row_content: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn finish(mut self) -> Table {
|
||||
self.finish_anonymous_row_if_needed();
|
||||
self.builder.finish()
|
||||
}
|
||||
|
||||
fn finish_anonymous_row_if_needed(&mut self) {
|
||||
if self.current_anonymous_row_content.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let row_content = std::mem::replace(&mut self.current_anonymous_row_content, Vec::new());
|
||||
let context = self.context;
|
||||
let anonymous_style = self
|
||||
.context
|
||||
.shared_context()
|
||||
.stylist
|
||||
.style_for_anonymous::<Node::ConcreteElement>(
|
||||
&context.shared_context().guards,
|
||||
&PseudoElement::ServoAnonymousTableCell,
|
||||
&self.info.style,
|
||||
);
|
||||
let anonymous_info = self.info.new_replacing_style(anonymous_style);
|
||||
let mut row_builder = TableRowBuilder::new(self, &anonymous_info);
|
||||
|
||||
for cell_content in row_content {
|
||||
match cell_content {
|
||||
AnonymousTableContent::Element {
|
||||
info,
|
||||
display,
|
||||
contents,
|
||||
box_slot,
|
||||
} => {
|
||||
row_builder.handle_element(&info, display, contents, box_slot);
|
||||
},
|
||||
AnonymousTableContent::Text(info, text) => {
|
||||
row_builder.handle_text(&info, text);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
row_builder.finish();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'style, 'dom, Node: 'dom> TraversalHandler<'dom, Node>
|
||||
for TableBuilderTraversal<'style, 'dom, Node>
|
||||
where
|
||||
Node: NodeExt<'dom>,
|
||||
{
|
||||
fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) {
|
||||
if text.chars().all(char_is_whitespace) {
|
||||
return;
|
||||
}
|
||||
self.current_anonymous_row_content
|
||||
.push(AnonymousTableContent::Text(info.clone(), text));
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#forming-a-table
|
||||
|
@ -326,7 +457,11 @@ where
|
|||
) {
|
||||
match display {
|
||||
DisplayGeneratingBox::LayoutInternal(internal) => match internal {
|
||||
DisplayLayoutInternal::TableRowGroup => {
|
||||
DisplayLayoutInternal::TableRowGroup |
|
||||
DisplayLayoutInternal::TableFooterGroup |
|
||||
DisplayLayoutInternal::TableHeaderGroup => {
|
||||
self.finish_anonymous_row_if_needed();
|
||||
|
||||
// TODO: Should we fixup `rowspan=0` to the actual resolved value and
|
||||
// any other rowspans that have been cut short?
|
||||
self.builder.incoming_rowspans.clear();
|
||||
|
@ -337,47 +472,147 @@ where
|
|||
);
|
||||
|
||||
// TODO: Handle style for row groups here.
|
||||
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot)
|
||||
},
|
||||
DisplayLayoutInternal::TableRow => {
|
||||
self.builder.start_row();
|
||||
self.finish_anonymous_row_if_needed();
|
||||
|
||||
let context = self.context;
|
||||
|
||||
let mut row_builder = TableRowBuilder::new(self, info);
|
||||
NonReplacedContents::try_from(contents).unwrap().traverse(
|
||||
self.context,
|
||||
context,
|
||||
info,
|
||||
&mut TableRowBuilder::new(self),
|
||||
&mut row_builder,
|
||||
);
|
||||
self.builder.end_row();
|
||||
row_builder.finish();
|
||||
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot)
|
||||
},
|
||||
_ => {
|
||||
// TODO: Handle other types of unparented table content, colgroups, and captions.
|
||||
DisplayLayoutInternal::TableCaption |
|
||||
DisplayLayoutInternal::TableColumn |
|
||||
DisplayLayoutInternal::TableColumnGroup => {
|
||||
// TODO: Handle these other types of table elements.
|
||||
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot)
|
||||
},
|
||||
DisplayLayoutInternal::TableCell => {
|
||||
self.current_anonymous_row_content
|
||||
.push(AnonymousTableContent::Element {
|
||||
info: info.clone(),
|
||||
display,
|
||||
contents,
|
||||
box_slot,
|
||||
});
|
||||
},
|
||||
},
|
||||
_ => {
|
||||
// TODO: Create an anonymous row and cell for other unwrapped content.
|
||||
self.current_anonymous_row_content
|
||||
.push(AnonymousTableContent::Element {
|
||||
info: info.clone(),
|
||||
display,
|
||||
contents,
|
||||
box_slot,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot)
|
||||
}
|
||||
}
|
||||
|
||||
struct TableRowBuilder<'a, 'builder, Node> {
|
||||
table_traversal: &'builder mut TableBuilderTraversal<'a, Node>,
|
||||
struct TableRowBuilder<'style, 'builder, 'dom, 'a, Node> {
|
||||
table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom, Node>,
|
||||
|
||||
/// The [`NodeAndStyleInfo`] of this table row, which we use to
|
||||
/// construct anonymous table cells.
|
||||
info: &'a NodeAndStyleInfo<Node>,
|
||||
|
||||
current_anonymous_cell_content: Vec<AnonymousTableContent<'dom, Node>>,
|
||||
}
|
||||
|
||||
impl<'a, 'builder, Node> TableRowBuilder<'a, 'builder, Node> {
|
||||
fn new(table_traversal: &'builder mut TableBuilderTraversal<'a, Node>) -> Self {
|
||||
TableRowBuilder { table_traversal }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'builder, 'dom, Node: 'dom> TraversalHandler<'dom, Node>
|
||||
for TableRowBuilder<'a, 'builder, Node>
|
||||
impl<'style, 'builder, 'dom, 'a, Node: 'dom> TableRowBuilder<'style, 'builder, 'dom, 'a, Node>
|
||||
where
|
||||
Node: NodeExt<'dom>,
|
||||
{
|
||||
fn handle_text(&mut self, _info: &NodeAndStyleInfo<Node>, _text: Cow<'dom, str>) {
|
||||
// TODO: We should collect these contents into a new table cell.
|
||||
fn new(
|
||||
table_traversal: &'builder mut TableBuilderTraversal<'style, 'dom, Node>,
|
||||
info: &'a NodeAndStyleInfo<Node>,
|
||||
) -> Self {
|
||||
table_traversal.builder.start_row();
|
||||
|
||||
TableRowBuilder {
|
||||
table_traversal,
|
||||
info,
|
||||
current_anonymous_cell_content: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(mut self) {
|
||||
self.finish_current_anonymous_cell_if_needed();
|
||||
self.table_traversal.builder.end_row();
|
||||
}
|
||||
|
||||
fn finish_current_anonymous_cell_if_needed(&mut self) {
|
||||
if self.current_anonymous_cell_content.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
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_replacing_style(anonymous_style);
|
||||
let mut builder = BlockContainerBuilder::new(
|
||||
context,
|
||||
&anonymous_info,
|
||||
self.table_traversal.propagated_text_decoration_line,
|
||||
);
|
||||
|
||||
for cell_content in self.current_anonymous_cell_content.drain(..) {
|
||||
match cell_content {
|
||||
AnonymousTableContent::Element {
|
||||
info,
|
||||
display,
|
||||
contents,
|
||||
box_slot,
|
||||
} => {
|
||||
builder.handle_element(&info, display, contents, box_slot);
|
||||
},
|
||||
AnonymousTableContent::Text(info, text) => {
|
||||
builder.handle_text(&info, text);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let block_container = builder.finish();
|
||||
self.table_traversal.builder.add_cell(TableSlotCell {
|
||||
contents: BlockFormattingContext::from_block_container(block_container),
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
id: 0, // This is just an id used for testing purposes.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'style, 'builder, 'dom, 'a, Node: 'dom> TraversalHandler<'dom, Node>
|
||||
for TableRowBuilder<'style, 'builder, 'dom, 'a, Node>
|
||||
where
|
||||
Node: NodeExt<'dom>,
|
||||
{
|
||||
fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) {
|
||||
if text.chars().all(char_is_whitespace) {
|
||||
return;
|
||||
}
|
||||
self.current_anonymous_cell_content
|
||||
.push(AnonymousTableContent::Text(info.clone(), text));
|
||||
}
|
||||
|
||||
/// https://html.spec.whatwg.org/multipage/#algorithm-for-processing-rows
|
||||
|
@ -417,23 +652,37 @@ where
|
|||
},
|
||||
};
|
||||
|
||||
self.finish_current_anonymous_cell_if_needed();
|
||||
self.table_traversal.builder.add_cell(TableSlotCell {
|
||||
contents,
|
||||
colspan,
|
||||
rowspan,
|
||||
id: 0, // This is just an id used for testing purposes.
|
||||
});
|
||||
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot)
|
||||
},
|
||||
_ => {
|
||||
// TODO: Properly handle other table-like elements in the middle of a row.
|
||||
//// TODO: Properly handle other table-like elements in the middle of a row.
|
||||
self.current_anonymous_cell_content
|
||||
.push(AnonymousTableContent::Element {
|
||||
info: info.clone(),
|
||||
display,
|
||||
contents,
|
||||
box_slot,
|
||||
});
|
||||
},
|
||||
},
|
||||
_ => {
|
||||
// TODO: We should collect these contents into a new table cell.
|
||||
self.current_anonymous_cell_content
|
||||
.push(AnonymousTableContent::Element {
|
||||
info: info.clone(),
|
||||
display,
|
||||
contents,
|
||||
box_slot,
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
mod construct;
|
||||
|
||||
pub(crate) use construct::AnonymousTableContent;
|
||||
pub use construct::TableBuilder;
|
||||
use euclid::{Point2D, UnknownUnit, Vector2D};
|
||||
use serde::Serialize;
|
||||
|
|
|
@ -54,13 +54,13 @@ pub enum PseudoElement {
|
|||
DetailsSummary,
|
||||
DetailsContent,
|
||||
ServoAnonymousBox,
|
||||
ServoAnonymousTableCell,
|
||||
ServoAnonymousTableRow,
|
||||
ServoLegacyText,
|
||||
ServoLegacyInputText,
|
||||
ServoLegacyTableWrapper,
|
||||
ServoLegacyAnonymousTableWrapper,
|
||||
ServoLegacyAnonymousTable,
|
||||
ServoLegacyAnonymousTableRow,
|
||||
ServoLegacyAnonymousTableCell,
|
||||
ServoLegacyAnonymousBlock,
|
||||
ServoLegacyInlineBlockWrapper,
|
||||
ServoLegacyInlineAbsolute,
|
||||
|
@ -82,13 +82,13 @@ impl ToCss for PseudoElement {
|
|||
DetailsSummary => "::-servo-details-summary",
|
||||
DetailsContent => "::-servo-details-content",
|
||||
ServoAnonymousBox => "::-servo-anonymous-box",
|
||||
ServoAnonymousTableCell => "::-servo-anonymous-table-cell",
|
||||
ServoAnonymousTableRow => "::-servo-anonymous-table-row",
|
||||
ServoLegacyText => "::-servo-legacy-text",
|
||||
ServoLegacyInputText => "::-servo-legacy-input-text",
|
||||
ServoLegacyTableWrapper => "::-servo-legacy-table-wrapper",
|
||||
ServoLegacyAnonymousTableWrapper => "::-servo-legacy-anonymous-table-wrapper",
|
||||
ServoLegacyAnonymousTable => "::-servo-legacy-anonymous-table",
|
||||
ServoLegacyAnonymousTableRow => "::-servo-legacy-anonymous-table-row",
|
||||
ServoLegacyAnonymousTableCell => "::-servo-legacy-anonymous-table-cell",
|
||||
ServoLegacyAnonymousBlock => "::-servo-legacy-anonymous-block",
|
||||
ServoLegacyInlineBlockWrapper => "::-servo-legacy-inline-block-wrapper",
|
||||
ServoLegacyInlineAbsolute => "::-servo-legacy-inline-absolute",
|
||||
|
@ -229,13 +229,13 @@ impl PseudoElement {
|
|||
PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy,
|
||||
PseudoElement::DetailsContent |
|
||||
PseudoElement::ServoAnonymousBox |
|
||||
PseudoElement::ServoAnonymousTableCell |
|
||||
PseudoElement::ServoAnonymousTableRow |
|
||||
PseudoElement::ServoLegacyText |
|
||||
PseudoElement::ServoLegacyInputText |
|
||||
PseudoElement::ServoLegacyTableWrapper |
|
||||
PseudoElement::ServoLegacyAnonymousTableWrapper |
|
||||
PseudoElement::ServoLegacyAnonymousTable |
|
||||
PseudoElement::ServoLegacyAnonymousTableRow |
|
||||
PseudoElement::ServoLegacyAnonymousTableCell |
|
||||
PseudoElement::ServoLegacyAnonymousBlock |
|
||||
PseudoElement::ServoLegacyInlineBlockWrapper |
|
||||
PseudoElement::ServoLegacyInlineAbsolute => PseudoElementCascadeType::Precomputed,
|
||||
|
@ -554,17 +554,17 @@ impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
|
|||
}
|
||||
ServoLegacyAnonymousTable
|
||||
},
|
||||
"-servo-legacy-anonymous-table-row" => {
|
||||
"-servo-anonymous-table-row" => {
|
||||
if !self.in_user_agent_stylesheet() {
|
||||
return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
|
||||
}
|
||||
ServoLegacyAnonymousTableRow
|
||||
ServoAnonymousTableRow
|
||||
},
|
||||
"-servo-legacy-anonymous-table-cell" => {
|
||||
"-servo-anonymous-table-cell" => {
|
||||
if !self.in_user_agent_stylesheet() {
|
||||
return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
|
||||
}
|
||||
ServoLegacyAnonymousTableCell
|
||||
ServoAnonymousTableCell
|
||||
},
|
||||
"-servo-legacy-anonymous-block" => {
|
||||
if !self.in_user_agent_stylesheet() {
|
||||
|
|
|
@ -230,7 +230,7 @@ svg > * {
|
|||
overflow: visible;
|
||||
}
|
||||
|
||||
*|*::-servo-legacy-anonymous-table-row {
|
||||
*|*::-servo-anonymous-table-row {
|
||||
display: table-row;
|
||||
position: static;
|
||||
border: none;
|
||||
|
@ -238,7 +238,7 @@ svg > * {
|
|||
overflow: visible;
|
||||
}
|
||||
|
||||
*|*::-servo-legacy-anonymous-table-cell {
|
||||
*|*::-servo-anonymous-table-cell {
|
||||
display: table-cell;
|
||||
position: static;
|
||||
border: none;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue