layout: Add support for table rows, columns, rowgroups and colgroups (#31341)

This adds support for table rows, columns, rowgroups and colgroups.
There are few additions here:

1. The createion of fragments, which allows script queries and hit
   testing to work properly. These fragments are empty as all cells are
   still direct descendants of the table fragment.
2. Properly handling size information from tracks and track groups as
   well as frustrating rules about reordering rowgroups.
3. Painting a background seemlessly across track groups and groups. This
   is a thing that isn't done in legacy layout (nor WebKit)!

Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2024-02-20 14:22:02 +01:00 committed by GitHub
parent 74c07db56c
commit 02ae1f448e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
57 changed files with 4274 additions and 21000 deletions

View file

@ -73,8 +73,8 @@ impl TableCellFlow {
TableCellFlow {
block_flow: BlockFlow::from_fragment(fragment),
collapsed_borders: CollapsedBordersForCell::new(),
column_span: node.get_colspan(),
row_span: node.get_rowspan(),
column_span: node.get_colspan().unwrap_or(1),
row_span: node.get_rowspan().unwrap_or(1),
visible,
}
}

View file

@ -36,62 +36,68 @@ fn get_cyclic<T>(values: &[T], layer_index: usize) -> &T {
&values[layer_index % values.len()]
}
pub(super) enum Source<'a> {
Fragment,
Canvas {
style: &'a ComputedValues,
// Theoretically the painting area is the infinite 2D plane,
// but WebRender doesnt really do infinite so this is the part of it that can be visible.
painting_area: units::LayoutRect,
},
pub(super) struct BackgroundPainter<'a> {
pub style: &'a ComputedValues,
pub positioning_area_override: Option<units::LayoutRect>,
pub painting_area_override: Option<units::LayoutRect>,
}
pub(super) fn painting_area<'a>(
fragment_builder: &'a super::BuilderForBoxFragment,
source: &'a Source,
builder: &mut super::DisplayListBuilder,
layer_index: usize,
) -> (&'a units::LayoutRect, wr::CommonItemProperties) {
let fb = fragment_builder;
let (painting_area, clip) = match source {
Source::Canvas { painting_area, .. } => (painting_area, None),
Source::Fragment => {
let b = fb.fragment.style.get_background();
match get_cyclic(&b.background_clip.0, layer_index) {
Clip::ContentBox => (fb.content_rect(), fb.content_edge_clip(builder)),
Clip::PaddingBox => (fb.padding_rect(), fb.padding_edge_clip(builder)),
Clip::BorderBox => (&fb.border_rect, fb.border_edge_clip(builder)),
}
},
};
// The 'backgound-clip' property maps directly to `clip_rect` in `CommonItemProperties`:
let mut common = builder.common_properties(*painting_area, &fb.fragment.style);
if let Some(clip_chain_id) = clip {
common.clip_id = wr::ClipId::ClipChain(clip_chain_id)
impl<'a> BackgroundPainter<'a> {
pub(super) fn painting_area(
&self,
fragment_builder: &'a super::BuilderForBoxFragment,
builder: &mut super::DisplayListBuilder,
layer_index: usize,
) -> (&units::LayoutRect, wr::CommonItemProperties) {
let fb = fragment_builder;
let (painting_area, clip) = match self.painting_area_override {
Some(ref painting_area) => (painting_area, None),
None if self.positioning_area_override.is_none() => {
let b = self.style.get_background();
match get_cyclic(&b.background_clip.0, layer_index) {
Clip::ContentBox => (fb.content_rect(), fb.content_edge_clip(builder)),
Clip::PaddingBox => (fb.padding_rect(), fb.padding_edge_clip(builder)),
Clip::BorderBox => (&fb.border_rect, fb.border_edge_clip(builder)),
}
},
None => (&fb.border_rect, fb.border_edge_clip(builder)),
};
// The 'backgound-clip' property maps directly to `clip_rect` in `CommonItemProperties`:
let mut common = builder.common_properties(*painting_area, &fb.fragment.style);
if let Some(clip_chain_id) = clip {
common.clip_id = wr::ClipId::ClipChain(clip_chain_id)
}
(painting_area, common)
}
pub(super) fn positioning_area(
&self,
fragment_builder: &'a super::BuilderForBoxFragment,
layer_index: usize,
) -> &units::LayoutRect {
self.positioning_area_override.as_ref().unwrap_or_else(|| {
match get_cyclic(
&self.style.get_background().background_origin.0,
layer_index,
) {
Origin::ContentBox => fragment_builder.content_rect(),
Origin::PaddingBox => fragment_builder.padding_rect(),
Origin::BorderBox => &fragment_builder.border_rect,
}
})
}
(painting_area, common)
}
pub(super) fn layout_layer(
fragment_builder: &mut super::BuilderForBoxFragment,
source: &Source,
painter: &BackgroundPainter,
builder: &mut super::DisplayListBuilder,
layer_index: usize,
intrinsic: IntrinsicSizes,
) -> Option<BackgroundLayer> {
let style = match *source {
Source::Canvas { style, .. } => style,
Source::Fragment => &fragment_builder.fragment.style,
};
let b = style.get_background();
let (painting_area, common) = painting_area(fragment_builder, source, builder, layer_index);
let positioning_area = match get_cyclic(&b.background_origin.0, layer_index) {
Origin::ContentBox => fragment_builder.content_rect(),
Origin::PaddingBox => fragment_builder.padding_rect(),
Origin::BorderBox => &fragment_builder.border_rect,
};
let (painting_area, common) = painter.painting_area(fragment_builder, builder, layer_index);
let positioning_area = painter.positioning_area(fragment_builder, layer_index);
// https://drafts.csswg.org/css-backgrounds/#background-size
enum ContainOrCover {
@ -117,6 +123,8 @@ pub(super) fn layout_layer(
}
tile_size
};
let b = painter.style.get_background();
let mut tile_size = match get_cyclic(&b.background_size.0, layer_index) {
Size::Contain => size_contain_or_cover(ContainOrCover::Contain),
Size::Cover => size_contain_or_cover(ContainOrCover::Cover),

View file

@ -2,6 +2,7 @@
* 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 app_units::Au;
use style::color::AbsoluteColor;
use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle;
@ -86,6 +87,13 @@ impl ToWebRender for PhysicalPoint<Length> {
}
}
impl ToWebRender for PhysicalPoint<Au> {
type Type = units::LayoutPoint;
fn to_webrender(&self) -> Self::Type {
units::LayoutPoint::new(self.x.to_f32_px(), self.y.to_f32_px())
}
}
impl ToWebRender for PhysicalSize<Length> {
type Type = units::LayoutSize;
fn to_webrender(&self) -> Self::Type {
@ -93,6 +101,13 @@ impl ToWebRender for PhysicalSize<Length> {
}
}
impl ToWebRender for PhysicalSize<Au> {
type Type = units::LayoutSize;
fn to_webrender(&self) -> Self::Type {
units::LayoutSize::new(self.width.to_f32_px(), self.height.to_f32_px())
}
}
impl ToWebRender for PhysicalRect<Length> {
type Type = units::LayoutRect;
fn to_webrender(&self) -> Self::Type {
@ -100,6 +115,13 @@ impl ToWebRender for PhysicalRect<Length> {
}
}
impl ToWebRender for PhysicalRect<Au> {
type Type = units::LayoutRect;
fn to_webrender(&self) -> Self::Type {
units::LayoutRect::new(self.origin.to_webrender(), self.size.to_webrender())
}
}
impl ToWebRender for PhysicalSides<Length> {
type Type = units::LayoutSideOffsets;
fn to_webrender(&self) -> Self::Type {

View file

@ -28,7 +28,7 @@ use crate::context::LayoutContext;
use crate::display_list::conversions::ToWebRender;
use crate::display_list::stacking_context::StackingContextSection;
use crate::fragment_tree::{BoxFragment, Fragment, FragmentTree, Tag, TextFragment};
use crate::geom::{PhysicalPoint, PhysicalRect};
use crate::geom::{LogicalRect, PhysicalPoint, PhysicalRect};
use crate::replaced::IntrinsicSizes;
use crate::style_ext::ComputedValuesExt;
@ -37,6 +37,7 @@ mod conversions;
mod gradient;
mod stacking_context;
use background::BackgroundPainter;
pub use stacking_context::*;
#[derive(Clone, Copy)]
@ -205,7 +206,21 @@ impl Fragment {
Visibility::Collapse => (),
},
Fragment::AbsoluteOrFixedPositioned(_) => {},
Fragment::Anonymous(_) => {},
Fragment::Positioning(positioning_fragment) => {
if let Some(style) = positioning_fragment.style.as_ref() {
let rect = positioning_fragment
.rect
.to_physical(style.writing_mode, containing_block)
.translate(containing_block.origin.to_vector());
self.maybe_push_hit_test_for_style_and_tag(
builder,
style,
positioning_fragment.base.tag,
rect,
Cursor::Default,
);
}
},
Fragment::Image(i) => match i.style.get_inherited_box().visibility {
Visibility::Visible => {
builder.is_contentful = true;
@ -265,6 +280,33 @@ impl Fragment {
}
}
fn maybe_push_hit_test_for_style_and_tag(
&self,
builder: &mut DisplayListBuilder,
style: &ComputedValues,
tag: Option<Tag>,
rect: PhysicalRect<Length>,
cursor: Cursor,
) {
let hit_info = builder.hit_info(style, tag, cursor);
let hit_info = match hit_info {
Some(hit_info) => hit_info,
None => return,
};
let clip_chain_id = builder.current_clip_chain_id;
let spatial_id = builder.current_scroll_node_id.spatial_id;
builder.wr().push_hit_test(
&CommonItemProperties {
clip_rect: rect.to_webrender(),
clip_id: ClipId::ClipChain(clip_chain_id),
spatial_id,
flags: style.get_webrender_primitive_flags(),
},
hit_info,
);
}
fn build_display_list_for_text_fragment(
&self,
fragment: &TextFragment,
@ -291,21 +333,13 @@ impl Fragment {
return;
}
if let Some(hit_info) =
builder.hit_info(&fragment.parent_style, fragment.base.tag, Cursor::Text)
{
let clip_chain_id = builder.current_clip_chain_id;
let spatial_id = builder.current_scroll_node_id.spatial_id;
builder.wr().push_hit_test(
&CommonItemProperties {
clip_rect: rect.to_webrender(),
clip_id: ClipId::ClipChain(clip_chain_id),
spatial_id,
flags: fragment.parent_style.get_webrender_primitive_flags(),
},
hit_info,
);
}
self.maybe_push_hit_test_for_style_and_tag(
builder,
&fragment.parent_style,
fragment.base.tag,
rect,
Cursor::Text,
);
let color = fragment.parent_style.clone_color();
let font_metrics = &fragment.font_metrics;
@ -540,6 +574,27 @@ impl<'a> BuilderForBoxFragment<'a> {
builder.wr().push_hit_test(&common, hit_info);
}
fn build_background_for_painter(
&mut self,
builder: &mut DisplayListBuilder,
painter: &BackgroundPainter,
) {
let b = painter.style.get_background();
let background_color = painter.style.resolve_color(b.background_color.clone());
if background_color.alpha > 0.0 {
// https://drafts.csswg.org/css-backgrounds/#background-color
// “The background color is clipped according to the background-clip
// value associated with the bottom-most background image layer.”
let layer_index = b.background_image.0.len() - 1;
let (bounds, common) = painter.painting_area(self, builder, layer_index);
builder
.wr()
.push_rect(&common, *bounds, rgba(background_color))
}
self.build_background_image(builder, painter);
}
fn build_background(&mut self, builder: &mut DisplayListBuilder) {
if self
.fragment
@ -550,34 +605,36 @@ impl<'a> BuilderForBoxFragment<'a> {
return;
}
let source = background::Source::Fragment;
let style = &self.fragment.style;
let b = style.get_background();
let background_color = style.resolve_color(b.background_color.clone());
if background_color.alpha > 0.0 {
// https://drafts.csswg.org/css-backgrounds/#background-color
// “The background color is clipped according to the background-clip
// value associated with the bottom-most background image layer.”
let layer_index = b.background_image.0.len() - 1;
let (bounds, common) = background::painting_area(self, &source, builder, layer_index);
builder
.wr()
.push_rect(&common, *bounds, rgba(background_color))
for extra_background in self.fragment.extra_backgrounds.iter() {
let positioning_area: LogicalRect<Length> = extra_background.rect.clone().into();
let painter = BackgroundPainter {
style: &extra_background.style,
painting_area_override: None,
positioning_area_override: Some(
positioning_area
.to_physical(self.fragment.style.writing_mode, self.containing_block)
.translate(self.containing_block.origin.to_vector())
.to_webrender(),
),
};
self.build_background_for_painter(builder, &painter);
}
self.build_background_image(builder, source);
let painter = BackgroundPainter {
style: &self.fragment.style,
painting_area_override: None,
positioning_area_override: None,
};
self.build_background_for_painter(builder, &painter);
}
fn build_background_image(
&mut self,
builder: &mut DisplayListBuilder,
source: background::Source<'a>,
painter: &BackgroundPainter,
) {
use style::values::computed::image::Image;
let style = match source {
background::Source::Canvas { style, .. } => style,
background::Source::Fragment => &self.fragment.style,
};
let style = painter.style;
let b = style.get_background();
// Reverse because the property is top layer first, we want to paint bottom layer first.
for (index, image) in b.background_image.0.iter().enumerate().rev() {
@ -586,7 +643,7 @@ impl<'a> BuilderForBoxFragment<'a> {
Image::Gradient(ref gradient) => {
let intrinsic = IntrinsicSizes::empty();
if let Some(layer) =
&background::layout_layer(self, &source, builder, index, intrinsic)
&background::layout_layer(self, painter, builder, index, intrinsic)
{
gradient::build(style, gradient, layer, builder)
}
@ -627,7 +684,7 @@ impl<'a> BuilderForBoxFragment<'a> {
);
if let Some(layer) =
background::layout_layer(self, &source, builder, index, intrinsic)
background::layout_layer(self, &painter, builder, index, intrinsic)
{
let image_rendering = image_rendering(style.clone_image_rendering());
if layer.repeat {

View file

@ -32,7 +32,7 @@ use crate::cell::ArcRefCell;
use crate::display_list::conversions::{FilterToWebRender, ToWebRender};
use crate::display_list::DisplayListBuilder;
use crate::fragment_tree::{
AnonymousFragment, BoxFragment, ContainingBlockManager, Fragment, FragmentTree,
BoxFragment, ContainingBlockManager, Fragment, FragmentTree, PositioningFragment,
};
use crate::geom::{PhysicalRect, PhysicalSides};
use crate::style_ext::ComputedValuesExt;
@ -636,11 +636,12 @@ impl StackingContext {
let mut fragment_builder =
super::BuilderForBoxFragment::new(box_fragment, containing_block);
let source = super::background::Source::Canvas {
let painter = super::background::BackgroundPainter {
style,
painting_area,
painting_area_override: Some(painting_area),
positioning_area_override: None,
};
fragment_builder.build_background_image(builder, source);
fragment_builder.build_background_image(builder, &painter);
}
pub(crate) fn build_display_list(&self, builder: &mut DisplayListBuilder) {
@ -858,7 +859,7 @@ impl Fragment {
StackingContextBuildMode::IncludeHoisted,
);
},
Fragment::Anonymous(fragment) => {
Fragment::Positioning(fragment) => {
fragment.build_stacking_context_tree(
display_list,
containing_block,
@ -1501,7 +1502,7 @@ impl BoxFragment {
}
}
impl AnonymousFragment {
impl PositioningFragment {
fn build_stacking_context_tree(
&self,
display_list: &mut DisplayList,
@ -1511,7 +1512,7 @@ impl AnonymousFragment {
) {
let rect = self
.rect
.to_physical(self.mode, &containing_block.rect)
.to_physical(self.writing_mode, &containing_block.rect)
.translate(containing_block.rect.origin.to_vector());
let new_containing_block = containing_block.new_replacing_rect(&rect);
let new_containing_block_info =

View file

@ -35,8 +35,8 @@ use crate::flow::float::{FloatBox, SequentialLayoutState};
use crate::flow::FlowLayout;
use crate::formatting_contexts::{Baselines, IndependentFormattingContext};
use crate::fragment_tree::{
AnonymousFragment, BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin,
Fragment,
BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment,
PositioningFragment,
};
use crate::geom::{LogicalRect, LogicalVec2};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
@ -806,7 +806,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
);
self.fragments
.push(Fragment::Anonymous(AnonymousFragment::new(
.push(Fragment::Positioning(PositioningFragment::new_anonymous(
line_rect,
fragments,
self.containing_block.style.writing_mode,

View file

@ -1513,7 +1513,7 @@ impl PlacementState {
block_offset_from_containing_block_top.into(),
);
},
Fragment::Anonymous(_) => {},
Fragment::Positioning(_) => {},
_ => unreachable!(),
}
}

View file

@ -2,6 +2,7 @@
* 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 app_units::Au;
use gfx_traits::print_tree::PrintTree;
use serde::Serialize;
use servo_arc::Arc as ServoArc;
@ -20,6 +21,11 @@ use crate::geom::{
};
use crate::style_ext::ComputedValuesExt;
pub(crate) struct ExtraBackground {
pub style: ServoArc<ComputedValues>,
pub rect: LogicalRect<Au>,
}
#[derive(Serialize)]
pub(crate) struct BoxFragment {
pub base: BaseFragment,
@ -62,6 +68,9 @@ pub(crate) struct BoxFragment {
/// during stacking context tree construction because they rely on the size of the
/// scroll container.
pub(crate) resolved_sticky_insets: Option<PhysicalSides<LengthOrAuto>>,
#[serde(skip_serializing)]
pub extra_backgrounds: Vec<ExtraBackground>,
}
impl BoxFragment {
@ -149,6 +158,7 @@ impl BoxFragment {
scrollable_overflow_from_children,
overconstrained,
resolved_sticky_insets: None,
extra_backgrounds: Vec::new(),
}
}
@ -165,6 +175,10 @@ impl BoxFragment {
self
}
pub fn add_extra_background(&mut self, extra_background: ExtraBackground) {
self.extra_backgrounds.push(extra_background);
}
pub fn scrollable_overflow(
&self,
containing_block: &PhysicalRect<Length>,

View file

@ -10,14 +10,16 @@ use gfx_traits::print_tree::PrintTree;
use msg::constellation_msg::{BrowsingContextId, PipelineId};
use serde::Serialize;
use servo_arc::Arc as ServoArc;
use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
use style::values::computed::Length;
use style::values::specified::text::TextDecorationLine;
use style::Zero;
use webrender_api::{FontInstanceKey, ImageKey};
use super::{BaseFragment, BoxFragment, ContainingBlockManager, HoistedSharedFragment, Tag};
use super::{
BaseFragment, BoxFragment, ContainingBlockManager, HoistedSharedFragment, PositioningFragment,
Tag,
};
use crate::cell::ArcRefCell;
use crate::geom::{LogicalRect, LogicalSides, PhysicalRect};
use crate::style_ext::ComputedValuesExt;
@ -31,7 +33,7 @@ pub(crate) enum Fragment {
/// the [SequentialLayoutState] of their float containing block formatting
/// context.
Float(BoxFragment),
Anonymous(AnonymousFragment),
Positioning(PositioningFragment),
/// Absolute and fixed position fragments are hoisted up so that they
/// are children of the BoxFragment that establishes their containing
/// blocks, so that they can be laid out properly. When this happens
@ -63,18 +65,6 @@ pub(crate) struct CollapsedMargin {
min_negative: Length,
}
/// Can contain child fragments with relative coordinates, but does not contribute to painting itself.
#[derive(Serialize)]
pub(crate) struct AnonymousFragment {
pub base: BaseFragment,
pub rect: LogicalRect<Length>,
pub children: Vec<ArcRefCell<Fragment>>,
pub mode: WritingMode,
/// The scrollable overflow of this anonymous fragment's children.
pub scrollable_overflow: PhysicalRect<Length>,
}
#[derive(Serialize)]
pub(crate) struct TextFragment {
pub base: BaseFragment,
@ -119,7 +109,7 @@ impl Fragment {
Fragment::Box(fragment) => &fragment.base,
Fragment::Text(fragment) => &fragment.base,
Fragment::AbsoluteOrFixedPositioned(_) => return None,
Fragment::Anonymous(fragment) => &fragment.base,
Fragment::Positioning(fragment) => &fragment.base,
Fragment::Image(fragment) => &fragment.base,
Fragment::IFrame(fragment) => &fragment.base,
Fragment::Float(fragment) => &fragment.base,
@ -141,7 +131,7 @@ impl Fragment {
Fragment::AbsoluteOrFixedPositioned(_) => {
tree.add_item("AbsoluteOrFixedPositioned".to_string());
},
Fragment::Anonymous(fragment) => fragment.print(tree),
Fragment::Positioning(fragment) => fragment.print(tree),
Fragment::Text(fragment) => fragment.print(tree),
Fragment::Image(fragment) => fragment.print(tree),
Fragment::IFrame(fragment) => fragment.print(tree),
@ -166,7 +156,7 @@ impl Fragment {
fragment.scrollable_overflow_for_parent(containing_block)
},
Fragment::AbsoluteOrFixedPositioned(_) => PhysicalRect::zero(),
Fragment::Anonymous(fragment) => fragment.scrollable_overflow,
Fragment::Positioning(fragment) => fragment.scrollable_overflow,
Fragment::Text(fragment) => fragment
.rect
.to_physical(fragment.parent_style.writing_mode, containing_block),
@ -219,10 +209,10 @@ impl Fragment {
.iter()
.find_map(|child| child.borrow().find(&new_manager, level + 1, process_func))
},
Fragment::Anonymous(fragment) => {
Fragment::Positioning(fragment) => {
let content_rect = fragment
.rect
.to_physical(fragment.mode, containing_block)
.to_physical(fragment.writing_mode, containing_block)
.translate(containing_block.origin.to_vector());
let new_manager = manager.new_for_non_absolute_descendants(&content_rect);
fragment
@ -235,43 +225,6 @@ impl Fragment {
}
}
impl AnonymousFragment {
pub fn new(rect: LogicalRect<Length>, children: Vec<Fragment>, mode: WritingMode) -> Self {
// FIXME(mrobinson, bug 25564): We should be using the containing block
// here to properly convert scrollable overflow to physical geometry.
let containing_block = PhysicalRect::zero();
let content_origin = rect.start_corner.to_physical(mode);
let scrollable_overflow = children.iter().fold(PhysicalRect::zero(), |acc, child| {
acc.union(
&child
.scrollable_overflow(&containing_block)
.translate(content_origin.to_vector()),
)
});
AnonymousFragment {
base: BaseFragment::anonymous(),
rect,
children: children.into_iter().map(ArcRefCell::new).collect(),
mode,
scrollable_overflow,
}
}
pub fn print(&self, tree: &mut PrintTree) {
tree.new_level(format!(
"Anonymous\
\nrect={:?}\
\nscrollable_overflow={:?}",
self.rect, self.scrollable_overflow
));
for child in &self.children {
child.borrow().print(tree);
}
tree.end_level();
}
}
impl TextFragment {
pub fn print(&self, tree: &mut PrintTree) {
tree.add_item(format!(

View file

@ -106,13 +106,15 @@ impl FragmentTree {
Fragment::Box(fragment) | Fragment::Float(fragment) => fragment
.border_rect()
.to_physical(fragment.style.writing_mode, containing_block),
Fragment::Positioning(fragment) => fragment
.rect
.to_physical(fragment.writing_mode, containing_block),
Fragment::Text(fragment) => fragment
.rect
.to_physical(fragment.parent_style.writing_mode, containing_block),
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::Image(_) |
Fragment::IFrame(_) |
Fragment::Anonymous(_) => return None,
Fragment::IFrame(_) => return None,
};
found_any_nodes = true;
@ -145,37 +147,41 @@ impl FragmentTree {
return None;
}
let (style, padding_rect) = match fragment {
Fragment::Box(fragment) => (&fragment.style, fragment.padding_rect()),
let rect = match fragment {
Fragment::Box(fragment) => {
// https://drafts.csswg.org/cssom-view/#dom-element-clienttop
// " If the element has no associated CSS layout box or if the
// CSS layout box is inline, return zero." For this check we
// also explicitly ignore the list item portion of the display
// style.
if fragment.style.get_box().display.is_inline_flow() {
return Some(Rect::zero());
}
let border = fragment.style.get_border();
let padding_rect = fragment
.padding_rect()
.to_physical(fragment.style.writing_mode, containing_block);
Rect::new(
Point2D::new(
border.border_left_width.into(),
border.border_top_width.into(),
),
Size2D::new(padding_rect.size.width, padding_rect.size.height),
)
},
Fragment::Positioning(fragment) => fragment
.rect
.to_physical(fragment.writing_mode, containing_block)
.cast_unit(),
_ => return None,
};
// https://drafts.csswg.org/cssom-view/#dom-element-clienttop
// " If the element has no associated CSS layout box or if the
// CSS layout box is inline, return zero." For this check we
// also explicitly ignore the list item portion of the display
// style.
let display = &style.get_box().display;
if display.inside() == style::values::specified::box_::DisplayInside::Flow &&
display.outside() == style::values::specified::box_::DisplayOutside::Inline
{
return Some(Rect::zero());
}
let border = style.get_border();
let padding_rect = padding_rect.to_physical(style.writing_mode, containing_block);
Some(
Rect::new(
Point2D::new(
border.border_left_width.to_f32_px(),
border.border_top_width.to_f32_px(),
),
Size2D::new(padding_rect.size.width.px(), padding_rect.size.height.px()),
)
.round()
.to_i32()
.to_untyped(),
)
let rect = Rect::new(
Point2D::new(rect.origin.x.px(), rect.origin.y.px()),
Size2D::new(rect.size.width.px(), rect.size.height.px()),
);
Some(rect.round().to_i32().to_untyped())
})
.unwrap_or_else(Rect::zero)
}

View file

@ -8,6 +8,7 @@ mod containing_block;
mod fragment;
mod fragment_tree;
mod hoisted_shared_fragment;
mod positioning_fragment;
pub(crate) use base_fragment::*;
pub(crate) use box_fragment::*;
@ -15,3 +16,4 @@ pub(crate) use containing_block::*;
pub(crate) use fragment::*;
pub use fragment_tree::*;
pub(crate) use hoisted_shared_fragment::*;
pub(crate) use positioning_fragment::*;

View file

@ -0,0 +1,100 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* 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 gfx_traits::print_tree::PrintTree;
use serde::Serialize;
use servo_arc::Arc as ServoArc;
use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
use style::values::computed::Length;
use super::{BaseFragment, BaseFragmentInfo, Fragment};
use crate::cell::ArcRefCell;
use crate::geom::{LogicalRect, PhysicalRect};
/// Can contain child fragments with relative coordinates, but does not contribute to painting
/// itself. [`PositioningFragments`] may be completely anonymous, or just non-painting Fragments
/// generated by boxes.
#[derive(Serialize)]
pub(crate) struct PositioningFragment {
pub base: BaseFragment,
pub rect: LogicalRect<Length>,
pub children: Vec<ArcRefCell<Fragment>>,
pub writing_mode: WritingMode,
/// The scrollable overflow of this anonymous fragment's children.
pub scrollable_overflow: PhysicalRect<Length>,
/// If this fragment was created with a style, the style of the fragment.
#[serde(skip_serializing)]
pub style: Option<ServoArc<ComputedValues>>,
}
impl PositioningFragment {
pub fn new_anonymous(
rect: LogicalRect<Length>,
children: Vec<Fragment>,
mode: WritingMode,
) -> Self {
Self::new_with_base_fragment(BaseFragment::anonymous(), None, rect, children, mode)
}
pub fn new_empty(
base_fragment_info: BaseFragmentInfo,
rect: LogicalRect<Length>,
style: ServoArc<ComputedValues>,
) -> Self {
let writing_mode = style.writing_mode;
Self::new_with_base_fragment(
base_fragment_info.into(),
Some(style),
rect,
Vec::new(),
writing_mode,
)
}
fn new_with_base_fragment(
base: BaseFragment,
style: Option<ServoArc<ComputedValues>>,
rect: LogicalRect<Length>,
children: Vec<Fragment>,
mode: WritingMode,
) -> Self {
// FIXME(mrobinson, bug 25564): We should be using the containing block
// here to properly convert scrollable overflow to physical geometry.
let containing_block = PhysicalRect::zero();
let content_origin = rect.start_corner.to_physical(mode);
let scrollable_overflow = children.iter().fold(PhysicalRect::zero(), |acc, child| {
acc.union(
&child
.scrollable_overflow(&containing_block)
.translate(content_origin.to_vector()),
)
});
PositioningFragment {
base,
style,
rect,
children: children.into_iter().map(ArcRefCell::new).collect(),
writing_mode: mode,
scrollable_overflow,
}
}
pub fn print(&self, tree: &mut PrintTree) {
tree.new_level(format!(
"PositioningFragment\
\nbase={:?}\
\nrect={:?}\
\nscrollable_overflow={:?}",
self.base, self.rect, self.scrollable_overflow
));
for child in &self.children {
child.borrow().print(tree);
}
tree.end_level();
}
}

View file

@ -176,7 +176,7 @@ impl PositioningContext {
let start_offset = match &parent_fragment {
Fragment::Box(b) | Fragment::Float(b) => &b.content_rect.start_corner,
Fragment::AbsoluteOrFixedPositioned(_) => return,
Fragment::Anonymous(a) => &a.rect.start_corner,
Fragment::Positioning(a) => &a.rect.start_corner,
_ => unreachable!(),
};
self.adjust_static_position_of_hoisted_fragments_with_offset(start_offset, index);

View file

@ -8,7 +8,7 @@ use std::sync::{Arc, Mutex};
use app_units::Au;
use euclid::default::{Point2D, Rect};
use euclid::{Size2D, Vector2D};
use euclid::{SideOffsets2D, Size2D, Vector2D};
use log::warn;
use msg::constellation_msg::PipelineId;
use script_layout_interface::rpc::{
@ -309,32 +309,46 @@ pub fn process_resolved_style_request<'dom>(
return None;
}
let box_fragment = match fragment {
Fragment::Box(ref box_fragment) => box_fragment,
let (content_rect, margins, padding) = match fragment {
Fragment::Box(ref box_fragment) | Fragment::Float(ref box_fragment) => {
if style.get_box().position != Position::Static {
let resolved_insets = || {
box_fragment.calculate_resolved_insets_if_positioned(containing_block)
};
match longhand_id {
LonghandId::Top => return Some(resolved_insets().top.to_css_string()),
LonghandId::Right => {
return Some(resolved_insets().right.to_css_string())
},
LonghandId::Bottom => {
return Some(resolved_insets().bottom.to_css_string())
},
LonghandId::Left => {
return Some(resolved_insets().left.to_css_string())
},
_ => {},
}
}
let content_rect = box_fragment
.content_rect
.to_physical(box_fragment.style.writing_mode, containing_block);
let margins = box_fragment
.margin
.to_physical(box_fragment.style.writing_mode);
let padding = box_fragment
.padding
.to_physical(box_fragment.style.writing_mode);
(content_rect, margins, padding)
},
Fragment::Positioning(positioning_fragment) => {
let content_rect = positioning_fragment
.rect
.to_physical(positioning_fragment.writing_mode, containing_block);
(content_rect, SideOffsets2D::zero(), SideOffsets2D::zero())
},
_ => return None,
};
if style.get_box().position != Position::Static {
let resolved_insets =
|| box_fragment.calculate_resolved_insets_if_positioned(containing_block);
match longhand_id {
LonghandId::Top => return Some(resolved_insets().top.to_css_string()),
LonghandId::Right => return Some(resolved_insets().right.to_css_string()),
LonghandId::Bottom => return Some(resolved_insets().bottom.to_css_string()),
LonghandId::Left => return Some(resolved_insets().left.to_css_string()),
_ => {},
}
}
let content_rect = box_fragment
.content_rect
.to_physical(box_fragment.style.writing_mode, containing_block);
let margins = box_fragment
.margin
.to_physical(box_fragment.style.writing_mode);
let padding = box_fragment
.padding
.to_physical(box_fragment.style.writing_mode);
match longhand_id {
LonghandId::Width => Some(content_rect.size.width),
LonghandId::Height => Some(content_rect.size.height),
@ -464,10 +478,12 @@ fn process_offset_parent_query_inner(
Fragment::Text(fragment) => fragment
.rect
.to_physical(fragment.parent_style.writing_mode, containing_block),
Fragment::Positioning(fragment) => fragment
.rect
.to_physical(fragment.writing_mode, containing_block),
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::Image(_) |
Fragment::IFrame(_) |
Fragment::Anonymous(_) => unreachable!(),
Fragment::IFrame(_) => unreachable!(),
};
let border_box = fragment_relative_rect.translate(containing_block.origin.to_vector());
@ -541,10 +557,10 @@ fn process_offset_parent_query_inner(
}
},
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::Text(_) |
Fragment::Image(_) |
Fragment::IFrame(_) |
Fragment::Anonymous(_) => None,
Fragment::Image(_) |
Fragment::Positioning(_) |
Fragment::Text(_) => None,
};
while parent_node_addresses.len() <= level {
@ -596,7 +612,7 @@ fn process_offset_parent_query_inner(
Fragment::Text(_) |
Fragment::Image(_) |
Fragment::IFrame(_) |
Fragment::Anonymous(_) => None,
Fragment::Positioning(_) => None,
}
})
.unwrap()

View file

@ -4,6 +4,7 @@
use std::borrow::Cow;
use std::convert::{TryFrom, TryInto};
use std::iter::repeat;
use log::warn;
use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode;
@ -13,7 +14,10 @@ use style::selector_parser::PseudoElement;
use style::str::char_is_whitespace;
use style::values::specified::TextDecorationLine;
use super::{Table, TableSlot, TableSlotCell, TableSlotCoordinates, TableSlotOffset};
use super::{
Table, TableSlot, TableSlotCell, TableSlotCoordinates, TableSlotOffset, TableTrack,
TableTrackGroup, TableTrackGroupType,
};
use crate::context::LayoutContext;
use crate::dom::{BoxSlot, NodeExt};
use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler};
@ -224,30 +228,216 @@ impl TableBuilder {
Self::new(ComputedValues::initial_values().to_arc())
}
pub fn last_row_index_in_row_group_at_row_n(&self, n: usize) -> usize {
// TODO: This is just a linear search, because the idea is that there are
// generally less than or equal to three row groups, but if we notice a lot
// of web content with more, we can consider a binary search here.
for row_group in self.table.row_groups.iter() {
if row_group.track_range.start > n {
return row_group.track_range.start - 1;
}
}
self.table.size.height - 1
}
pub fn finish(mut self) -> Table {
// Make sure that every row has the same number of cells.
self.do_missing_cells_fixup();
self.remove_extra_columns_and_column_groups();
self.reorder_first_thead_and_tfoot();
self.do_final_rowspan_calculation();
self.table
}
/// Do <https://drafts.csswg.org/css-tables/#missing-cells-fixup> which ensures
/// that every row has the same number of cells.
fn do_missing_cells_fixup(&mut self) {
for row in self.table.slots.iter_mut() {
row.resize_with(self.table.size.width, || TableSlot::Empty);
}
}
// Turn all rowspan=0 rows into the real value to avoid having to
// make the calculation continually during layout. In addition, make
// sure that there are no rowspans that extend past the end of the
// table.
/// It's possible to define more table columns via `<colgroup>` and `<col>` elements
/// than actually exist in the table. In that case, remove these bogus columns
/// to prevent using them later in layout.
fn remove_extra_columns_and_column_groups(&mut self) {
let number_of_actual_table_columns = self.table.size.width;
self.table.columns.truncate(number_of_actual_table_columns);
let mut remove_from = None;
for (group_index, column_group) in self.table.column_groups.iter_mut().enumerate() {
if column_group.track_range.start >= number_of_actual_table_columns {
remove_from = Some(group_index);
break;
}
column_group.track_range.end = column_group
.track_range
.end
.min(number_of_actual_table_columns);
}
if let Some(remove_from) = remove_from {
self.table.column_groups.truncate(remove_from);
}
}
/// Reorder the first `<thead>` and `<tbody>` to be the first and last row groups respectively.
/// This requires fixing up all row group indices.
/// See <https://drafts.csswg.org/css-tables/#table-header-group> and
/// <https://drafts.csswg.org/css-tables/#table-footer-group>.
fn reorder_first_thead_and_tfoot(&mut self) {
let mut thead_index = None;
let mut tfoot_index = None;
for (row_group_index, row_group) in self.table.row_groups.iter().enumerate() {
if thead_index.is_none() && row_group.group_type == TableTrackGroupType::HeaderGroup {
thead_index = Some(row_group_index);
}
if tfoot_index.is_none() && row_group.group_type == TableTrackGroupType::FooterGroup {
tfoot_index = Some(row_group_index);
}
if thead_index.is_some() && tfoot_index.is_some() {
break;
}
}
if let Some(thead_index) = thead_index {
self.move_row_group_to_front(thead_index)
}
if let Some(mut tfoot_index) = tfoot_index {
// We may have moved a `<thead>` which means the original index we
// we found for this this <tfoot>` also needs to be updated!
if thead_index.unwrap_or(0) > tfoot_index {
tfoot_index += 1;
}
self.move_row_group_to_end(tfoot_index)
}
}
fn regenerate_track_ranges(&mut self) {
// Now update all track group ranges.
let mut current_row_group_index = None;
for (row_index, row) in self.table.rows.iter().enumerate() {
if current_row_group_index == row.group_index {
continue;
}
// Finish any row group that is currently being processed.
if let Some(current_group_index) = current_row_group_index {
self.table.row_groups[current_group_index].track_range.end = row_index;
}
// Start processing this new row group and update its starting index.
current_row_group_index = row.group_index;
if let Some(current_group_index) = current_row_group_index {
self.table.row_groups[current_group_index].track_range.start = row_index;
}
}
// Finish the last row group.
if let Some(current_group_index) = current_row_group_index {
self.table.row_groups[current_group_index].track_range.end = self.table.rows.len();
}
}
fn move_row_group_to_front(&mut self, index_to_move: usize) {
if index_to_move == 0 {
return;
}
// Move the slots associated with this group.
let row_range = self.table.row_groups[index_to_move].track_range.clone();
let removed_slots: Vec<Vec<TableSlot>> = self
.table
.slots
.splice(row_range.clone(), std::iter::empty())
.collect();
self.table.slots.splice(0..0, removed_slots);
// Move the rows associated with this group.
let removed_rows: Vec<TableTrack> = self
.table
.rows
.splice(row_range, std::iter::empty())
.collect();
self.table.rows.splice(0..0, removed_rows);
// Move the group itself.
let removed_row_group = self.table.row_groups.remove(index_to_move);
self.table.row_groups.insert(0, removed_row_group);
for row in self.table.rows.iter_mut() {
match row.group_index.as_mut() {
Some(group_index) if *group_index < index_to_move => *group_index += 1,
Some(group_index) if *group_index == index_to_move => *group_index = 0,
_ => {},
}
}
// Do this now, rather than after possibly moving a `<tfoot>` row group to the end,
// because moving row groups depends on an accurate `track_range` in every group.
self.regenerate_track_ranges();
}
fn move_row_group_to_end(&mut self, index_to_move: usize) {
let last_row_group_index = self.table.row_groups.len() - 1;
if index_to_move == last_row_group_index {
return;
}
// Move the slots associated with this group.
let row_range = self.table.row_groups[index_to_move].track_range.clone();
let removed_slots: Vec<Vec<TableSlot>> = self
.table
.slots
.splice(row_range.clone(), std::iter::empty())
.collect();
self.table.slots.extend(removed_slots);
// Move the rows associated with this group.
let removed_rows: Vec<TableTrack> = self
.table
.rows
.splice(row_range, std::iter::empty())
.collect();
self.table.rows.extend(removed_rows);
// Move the group itself.
let removed_row_group = self.table.row_groups.remove(index_to_move);
self.table.row_groups.push(removed_row_group);
for row in self.table.rows.iter_mut() {
match row.group_index.as_mut() {
Some(group_index) if *group_index > index_to_move => *group_index -= 1,
Some(group_index) if *group_index == index_to_move => {
*group_index = last_row_group_index
},
_ => {},
}
}
self.regenerate_track_ranges();
}
/// Turn all rowspan=0 rows into the real value to avoid having to make the calculation
/// continually during layout. In addition, make sure that there are no rowspans that extend
/// past the end of their row group.
fn do_final_rowspan_calculation(&mut self) {
for row_index in 0..self.table.size.height {
let last_row_index_in_group = self.last_row_index_in_row_group_at_row_n(row_index);
for cell in self.table.slots[row_index].iter_mut() {
if let TableSlot::Cell(ref mut cell) = cell {
let rowspan_to_end_of_table = self.table.size.height - row_index;
if cell.rowspan == 1 {
continue;
}
let rowspan_to_end_of_group = last_row_index_in_group - row_index + 1;
if cell.rowspan == 0 {
cell.rowspan = rowspan_to_end_of_table;
cell.rowspan = rowspan_to_end_of_group;
} else {
cell.rowspan = cell.rowspan.min(rowspan_to_end_of_table);
cell.rowspan = cell.rowspan.min(rowspan_to_end_of_group);
}
}
}
}
self.table
}
fn current_y(&self) -> usize {
@ -408,6 +598,9 @@ pub(crate) struct TableBuilderTraversal<'style, 'dom, Node> {
builder: TableBuilder,
current_anonymous_row_content: Vec<AnonymousTableContent<'dom, Node>>,
/// The index of the current row group, if there is one.
current_row_group_index: Option<usize>,
}
impl<'style, 'dom, Node> TableBuilderTraversal<'style, 'dom, Node>
@ -425,6 +618,7 @@ where
propagated_text_decoration_line,
builder: TableBuilder::new(info.style.clone()),
current_anonymous_row_content: Vec::new(),
current_row_group_index: None,
}
}
@ -499,17 +693,26 @@ where
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();
let next_row_index = self.builder.table.rows.len();
self.builder.table.row_groups.push(TableTrackGroup {
base_fragment_info: info.into(),
style: info.style.clone(),
group_type: internal.into(),
track_range: next_row_index..next_row_index,
});
let new_row_group_index = self.builder.table.row_groups.len() - 1;
self.current_row_group_index = Some(new_row_group_index);
NonReplacedContents::try_from(contents).unwrap().traverse(
self.context,
info,
self,
);
// TODO: Handle style for row groups here.
self.current_row_group_index = None;
self.builder.incoming_rowspans.clear();
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
@ -527,17 +730,87 @@ where
);
row_builder.finish();
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
},
DisplayLayoutInternal::TableCaption |
DisplayLayoutInternal::TableColumn |
DisplayLayoutInternal::TableColumnGroup => {
// TODO: Handle these other types of table elements.
self.builder.table.rows.push(TableTrack {
base_fragment_info: info.into(),
style: info.style.clone(),
group_index: self.current_row_group_index,
is_anonymous: false,
});
let last_row = self.builder.table.rows.len();
let row_group = self
.current_row_group_index
.map(|index| &mut self.builder.table.row_groups[index]);
if let Some(row_group) = row_group {
row_group.track_range.end = last_row;
}
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
},
DisplayLayoutInternal::TableColumn => {
let node = info.node.to_threadsafe();
let span = (node.get_span().unwrap_or(1) as usize).min(1000);
for _ in 0..span + 1 {
self.builder.table.columns.push(TableTrack {
base_fragment_info: info.into(),
style: info.style.clone(),
group_index: None,
is_anonymous: false,
})
}
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot)
},
DisplayLayoutInternal::TableColumnGroup => {
let column_group_index = self.builder.table.column_groups.len();
let mut column_group_builder = TableColumnGroupBuilder {
column_group_index,
columns: Vec::new(),
};
NonReplacedContents::try_from(contents).unwrap().traverse(
self.context,
info,
&mut column_group_builder,
);
let first_column = self.builder.table.columns.len();
if column_group_builder.columns.is_empty() {
let node = info.node.to_threadsafe();
let span = (node.get_span().unwrap_or(1) as usize).min(1000);
self.builder.table.columns.extend(
repeat(TableTrack {
base_fragment_info: info.into(),
style: info.style.clone(),
group_index: Some(column_group_index),
is_anonymous: true,
})
.take(span),
);
} else {
self.builder
.table
.columns
.extend(column_group_builder.columns);
}
self.builder.table.column_groups.push(TableTrackGroup {
base_fragment_info: info.into(),
style: info.style.clone(),
group_type: internal.into(),
track_range: first_column..self.builder.table.columns.len(),
});
::std::mem::forget(box_slot);
},
DisplayLayoutInternal::TableCaption => {
// TODO: Handle table captions.
// 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 {
@ -682,8 +955,8 @@ where
// when dealing with arbitrary DOM elements (perhaps created via
// script).
let node = info.node.to_threadsafe();
let rowspan = std::cmp::min(node.get_rowspan() as usize, 65534);
let colspan = std::cmp::min(node.get_colspan() as usize, 1000);
let rowspan = (node.get_rowspan().unwrap_or(1) as usize).min(65534);
let colspan = (node.get_colspan().unwrap_or(1) as usize).min(1000);
let contents = match contents.try_into() {
Ok(non_replaced_contents) => {
@ -735,3 +1008,50 @@ where
}
}
}
struct TableColumnGroupBuilder {
column_group_index: usize,
columns: Vec<TableTrack>,
}
impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableColumnGroupBuilder
where
Node: NodeExt<'dom>,
{
fn handle_text(&mut self, _info: &NodeAndStyleInfo<Node>, _text: Cow<'dom, str>) {}
fn handle_element(
&mut self,
info: &NodeAndStyleInfo<Node>,
display: DisplayGeneratingBox,
_contents: Contents,
box_slot: BoxSlot<'dom>,
) {
// We are doing this until we have actually set a Box for this `BoxSlot`.
::std::mem::forget(box_slot);
if !matches!(
display,
DisplayGeneratingBox::LayoutInternal(DisplayLayoutInternal::TableColumn)
) {
return;
}
self.columns.push(TableTrack {
base_fragment_info: info.into(),
style: info.style.clone(),
group_index: Some(self.column_group_index),
is_anonymous: false,
});
}
}
impl From<DisplayLayoutInternal> for TableTrackGroupType {
fn from(value: DisplayLayoutInternal) -> Self {
match value {
DisplayLayoutInternal::TableColumnGroup => TableTrackGroupType::ColumnGroup,
DisplayLayoutInternal::TableFooterGroup => TableTrackGroupType::FooterGroup,
DisplayLayoutInternal::TableHeaderGroup => TableTrackGroupType::HeaderGroup,
DisplayLayoutInternal::TableRowGroup => TableTrackGroupType::RowGroup,
_ => unreachable!(),
}
}
}

View file

@ -2,22 +2,26 @@
* 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 std::ops::Add;
use app_units::{Au, MAX_AU};
use euclid::num::Zero;
use log::warn;
use servo_arc::Arc;
use style::computed_values::border_collapse::T as BorderCollapse;
use style::logical_geometry::WritingMode;
use style::values::computed::{CSSPixelLength, Length, Percentage};
use style::properties::ComputedValues;
use style::values::computed::{
CSSPixelLength, Length, LengthPercentage as ComputedLengthPercentage, Percentage,
};
use style::values::generics::box_::{GenericVerticalAlign as VerticalAlign, VerticalAlignKeyword};
use style::values::generics::length::GenericLengthPercentageOrAuto::{Auto, LengthPercentage};
use style::Zero;
use super::{Table, TableSlot, TableSlotCell};
use super::{Table, TableSlot, TableSlotCell, TableTrackGroup};
use crate::context::LayoutContext;
use crate::formatting_contexts::{Baselines, IndependentLayout};
use crate::fragment_tree::{AnonymousFragment, BoxFragment, CollapsedBlockMargins, Fragment};
use crate::geom::{AuOrAuto, LogicalRect, LogicalSides, LogicalVec2};
use crate::fragment_tree::{
BoxFragment, CollapsedBlockMargins, ExtraBackground, Fragment, PositioningFragment,
};
use crate::geom::{AuOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalVec2};
use crate::positioned::{PositioningContext, PositioningContextLength};
use crate::sizing::ContentSizes;
use crate::style_ext::{Clamp, ComputedValuesExt, PaddingBorderMargin};
@ -108,7 +112,7 @@ impl<'a> TableLayout<'a> {
let writing_mode = containing_block.style.writing_mode;
self.compute_column_constrainedness_and_has_originating_cells(writing_mode);
self.compute_cell_measures(layout_context, containing_block);
self.compute_column_measures();
self.compute_column_measures(containing_block.style.writing_mode);
self.compute_table_width(containing_block);
self.distributed_column_widths = self.distribute_width_to_columns();
self.do_row_layout_first_pass(layout_context, containing_block, positioning_context);
@ -132,84 +136,47 @@ impl<'a> TableLayout<'a> {
_ => continue,
};
// TODO: Should `box_size` percentages be treated as zero here or resolved against
// the containing block?
let pbm = cell.style.padding_border_margin(containing_block);
let min_inline_size: Au = cell
.style
.min_box_size(writing_mode)
.inline
.percentage_relative_to(Length::zero())
.map(|value| value.into())
.auto_is(Au::zero);
let max_inline_size: Au = cell.style.max_box_size(writing_mode).inline.map_or_else(
|| MAX_AU,
|length_percentage| length_percentage.resolve(Length::zero()).into(),
);
let inline_size: Au = cell
.style
.box_size(writing_mode)
.inline
.percentage_relative_to(Length::zero())
.map(|value| value.into())
.auto_is(Au::zero);
let (size, min_size, max_size) = get_sizes_from_style(&cell.style, writing_mode);
let content_sizes = cell
.contents
.contents
.inline_content_sizes(layout_context, writing_mode);
let percentage_contribution =
get_size_percentage_contribution_from_style(&cell.style, writing_mode);
// > The outer min-content width of a table-cell is max(min-width, min-content width)
// > adjusted by the cell intrinsic offsets.
let mut outer_min_content_width = content_sizes.min_content.max(min_inline_size);
let mut outer_min_content_width = content_sizes.min_content.max(min_size.inline);
let mut outer_max_content_width = if !self.column_constrainedness[column_index] {
// > The outer max-content width of a table-cell in a non-constrained column is
// > max(min-width, width, min-content width, min(max-width, max-content width))
// > adjusted by the cell intrinsic offsets.
min_inline_size
.max(inline_size)
min_size
.inline
.max(size.inline)
.max(content_sizes.min_content)
.max(max_inline_size.min(content_sizes.max_content))
.max(max_size.inline.min(content_sizes.max_content))
} else {
// > The outer max-content width of a table-cell in a constrained column is
// > max(min-width, width, min-content width, min(max-width, width)) adjusted by the
// > cell intrinsic offsets.
min_inline_size
.max(inline_size)
min_size
.inline
.max(size.inline)
.max(content_sizes.min_content)
.max(max_inline_size.min(inline_size))
.max(max_size.inline.min(size.inline))
};
// > The percentage contribution of a table cell, column, or column group is defined
// > in terms of the computed values of width and max-width that have computed values
// > that are percentages:
// > min(percentage width, percentage max-width).
// > If the computed values are not percentages, then 0% is used for width, and an
// > infinite percentage is used for max-width.
let inline_size_percent = cell
.style
.box_size(writing_mode)
.inline
.non_auto()
.and_then(|length_percentage| length_percentage.to_percentage())
.unwrap_or(Percentage(0.));
let max_inline_size_percent = cell
.style
.max_box_size(writing_mode)
.inline
.and_then(|length_percentage| length_percentage.to_percentage())
.unwrap_or(Percentage(f32::INFINITY));
let percentage_contribution =
Percentage(inline_size_percent.0.min(max_inline_size_percent.0));
let pbm = cell.style.padding_border_margin(containing_block);
outer_min_content_width += pbm.padding_border_sums.inline;
outer_max_content_width += pbm.padding_border_sums.inline;
row_measures[column_index] = CellOrColumnMeasure {
content_sizes: ContentSizes {
min_content: outer_min_content_width,
max_content: outer_max_content_width,
},
percentage_width: percentage_contribution,
percentage_width: percentage_contribution.inline,
};
}
@ -254,7 +221,7 @@ impl<'a> TableLayout<'a> {
/// This is an implementation of *Computing Column Measures* from
/// <https://drafts.csswg.org/css-tables/#computing-column-measures>.
fn compute_column_measures(&mut self) {
fn compute_column_measures(&mut self, writing_mode: WritingMode) {
let mut column_measures = Vec::new();
// Compute the column measures only taking into account cells with colspan == 1.
@ -289,7 +256,9 @@ impl<'a> TableLayout<'a> {
// TODO: Take into account changes to this computation for fixed table layout.
let mut next_span_n = usize::MAX;
for column_index in 0..self.table.size.width {
let mut column_measure = CellOrColumnMeasure::zero();
let mut column_measure = self
.table
.get_column_measure_for_column_at_index(writing_mode, column_index);
for row_index in 0..self.table.size.height {
let coords = TableSlotCoordinates::new(column_index, row_index);
@ -652,8 +621,6 @@ impl<'a> TableLayout<'a> {
let mut max_content_sizing_guesses = Vec::new();
for column_idx in 0..self.table.size.width {
use style::Zero;
let column_measure = &self.column_measures[column_idx];
let min_content_width = column_measure.content_sizes.min_content;
let max_content_width = column_measure.content_sizes.max_content;
@ -1040,14 +1007,20 @@ impl<'a> TableLayout<'a> {
assert_eq!(self.table.size.width, self.distributed_column_widths.len());
let mut baselines = Baselines::default();
let border_spacing = self.table.border_spacing();
let mut fragments = Vec::new();
let mut row_offset = border_spacing.block;
for row_index in 0..self.table.size.height {
let mut column_offset = border_spacing.inline;
let row_size = self.row_sizes[row_index];
let row_baseline = self.row_baselines[row_index];
if self.table.size.width == 0 || self.table.size.height == 0 {
return IndependentLayout {
fragments,
content_block_size: Au::zero(),
baselines,
};
}
let dimensions = TableAndTrackDimensions::new(&self);
self.make_fragments_for_columns_rows_and_groups(&dimensions, &mut fragments);
for row_index in 0..self.table.size.height {
// From <https://drafts.csswg.org/css-align-3/#baseline-export>
// > If any cells in the row participate in first baseline/last baseline alignment along
// > the inline axis, the first/last baseline set of the row is generated from their
@ -1058,8 +1031,9 @@ impl<'a> TableLayout<'a> {
// If any cell below has baseline alignment, these values will be overwritten,
// but they are initialized to the content edge of the first row.
if row_index == 0 {
baselines.first = Some(row_offset + row_size);
baselines.last = Some(row_offset + row_size);
let row_end = dimensions.get_row_rect(0).max_block_position();
baselines.first = Some(row_end);
baselines.last = Some(row_end);
}
for column_index in 0..self.table.size.width {
@ -1078,46 +1052,65 @@ impl<'a> TableLayout<'a> {
},
};
let cell_rect = dimensions.get_cell_rect(
TableSlotCoordinates::new(column_index, row_index),
cell.rowspan,
cell.colspan,
);
// If this cell has baseline alignment, it can adjust the table's overall baseline.
let row_baseline = self.row_baselines[row_index];
if cell.effective_vertical_align() == VerticalAlignKeyword::Baseline {
let baseline = cell_rect.start_corner.block + row_baseline;
if row_index == 0 {
baselines.first = Some(row_offset + row_baseline);
baselines.first = Some(baseline);
}
baselines.last = Some(row_offset + row_baseline);
baselines.last = Some(baseline);
}
// Calculate the inline and block size of all rows and columns that this cell spans.
let inline_size: Au = (column_index..column_index + cell.colspan)
.map(|index| self.distributed_column_widths[index])
.fold(Au::zero(), Au::add) +
((cell.colspan - 1) as i32 * border_spacing.inline);
let block_size: Au = (row_index..row_index + cell.rowspan)
.map(|index| self.row_sizes[index])
.fold(Au::zero(), Au::add) +
((cell.rowspan - 1) as i32 * border_spacing.block);
let mut fragment =
cell.create_fragment(layout, cell_rect, row_baseline, positioning_context);
let cell_rect: LogicalRect<Length> = LogicalRect {
start_corner: LogicalVec2 {
inline: column_offset.into(),
block: row_offset.into(),
},
size: LogicalVec2 {
inline: inline_size.into(),
block: block_size.into(),
},
};
let column = self.table.columns.get(column_index);
let column_group = column
.and_then(|column| column.group_index)
.and_then(|index| self.table.column_groups.get(index));
if let Some(column_group) = column_group {
fragment.add_extra_background(ExtraBackground {
style: column_group.style.clone(),
rect: dimensions.get_column_group_rect(column_group),
})
}
fragments.push(Fragment::Box(cell.create_fragment(
layout,
cell_rect,
row_baseline,
positioning_context,
)));
if let Some(column) = column {
if !column.is_anonymous {
fragment.add_extra_background(ExtraBackground {
style: column.style.clone(),
rect: dimensions.get_column_rect(column_index),
})
}
}
column_offset += inline_size + border_spacing.inline;
let row = self.table.rows.get(row_index);
let row_group = row
.and_then(|row| row.group_index)
.and_then(|index| self.table.row_groups.get(index));
if let Some(row_group) = row_group {
fragment.add_extra_background(ExtraBackground {
style: row_group.style.clone(),
rect: dimensions.get_row_group_rect(row_group),
})
}
if let Some(row) = row {
fragment.add_extra_background(ExtraBackground {
style: row.style.clone(),
rect: dimensions.get_row_rect(row_index),
})
}
fragments.push(Fragment::Box(fragment));
}
row_offset += row_size + border_spacing.block;
}
if self.table.anonymous {
@ -1127,10 +1120,173 @@ impl<'a> TableLayout<'a> {
IndependentLayout {
fragments,
content_block_size: row_offset,
content_block_size: dimensions.table_rect.max_block_position(),
baselines,
}
}
fn make_fragments_for_columns_rows_and_groups(
&mut self,
dimensions: &TableAndTrackDimensions,
fragments: &mut Vec<Fragment>,
) {
for column_group in self.table.column_groups.iter() {
if !column_group.is_empty() {
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
column_group.base_fragment_info,
dimensions.get_column_group_rect(column_group).into(),
column_group.style.clone(),
)));
}
}
for (column_index, column) in self.table.columns.iter().enumerate() {
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
column.base_fragment_info,
dimensions.get_column_rect(column_index).into(),
column.style.clone(),
)));
}
for row_group in self.table.row_groups.iter() {
if !row_group.is_empty() {
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
row_group.base_fragment_info,
dimensions.get_row_group_rect(row_group).into(),
row_group.style.clone(),
)));
}
}
for (row_index, row) in self.table.rows.iter().enumerate() {
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
row.base_fragment_info,
dimensions.get_row_rect(row_index).into(),
row.style.clone(),
)));
}
}
}
struct TableAndTrackDimensions {
/// The rect of the full table, not counting for borders, padding, and margin.
table_rect: LogicalRect<Au>,
/// The rect of the full table, not counting for borders, padding, and margin
/// and offset by any border spacing and caption.
table_cells_rect: LogicalRect<Au>,
/// The min and max block offsets of each table row.
row_dimensions: Vec<(Au, Au)>,
/// The min and max inline offsets of each table column
column_dimensions: Vec<(Au, Au)>,
}
impl TableAndTrackDimensions {
fn new(table_layout: &TableLayout) -> Self {
let border_spacing = table_layout.table.border_spacing();
let mut column_dimensions = Vec::new();
let mut column_offset = border_spacing.inline;
for column_index in 0..table_layout.table.size.width {
let column_size = table_layout.distributed_column_widths[column_index];
column_dimensions.push((column_offset, column_offset + column_size));
column_offset += column_size + border_spacing.inline;
}
let mut row_dimensions = Vec::new();
let mut row_offset = border_spacing.block;
for row_index in 0..table_layout.table.size.height {
let row_size = table_layout.row_sizes[row_index];
row_dimensions.push((row_offset, row_offset + row_size));
row_offset += row_size + border_spacing.block;
}
let table_start_corner = LogicalVec2 {
inline: column_dimensions[0].0,
block: row_dimensions[0].0,
};
let table_size = &LogicalVec2 {
inline: column_dimensions[column_dimensions.len() - 1].1,
block: row_dimensions[row_dimensions.len() - 1].1,
} - &table_start_corner;
let table_cells_rect = LogicalRect {
start_corner: table_start_corner,
size: table_size,
};
let table_rect = LogicalRect {
start_corner: LogicalVec2::zero(),
size: LogicalVec2 {
inline: column_offset,
block: row_offset,
},
};
Self {
table_rect,
table_cells_rect,
row_dimensions,
column_dimensions,
}
}
fn get_row_rect(&self, row_index: usize) -> LogicalRect<Au> {
let mut row_rect = self.table_cells_rect.clone();
let row_dimensions = self.row_dimensions[row_index];
row_rect.start_corner.block = row_dimensions.0;
row_rect.size.block = row_dimensions.1 - row_dimensions.0;
row_rect
}
fn get_column_rect(&self, column_index: usize) -> LogicalRect<Au> {
let mut row_rect = self.table_cells_rect.clone();
let column_dimensions = self.column_dimensions[column_index];
row_rect.start_corner.inline = column_dimensions.0;
row_rect.size.inline = column_dimensions.1 - column_dimensions.0;
row_rect
}
fn get_row_group_rect(&self, row_group: &TableTrackGroup) -> LogicalRect<Au> {
if row_group.is_empty() {
return LogicalRect::zero();
}
let mut row_group_rect = self.table_cells_rect.clone();
let block_start = self.row_dimensions[row_group.track_range.start].0;
let block_end = self.row_dimensions[row_group.track_range.end - 1].1;
row_group_rect.start_corner.block = block_start;
row_group_rect.size.block = block_end - block_start;
row_group_rect
}
fn get_column_group_rect(&self, column_group: &TableTrackGroup) -> LogicalRect<Au> {
if column_group.is_empty() {
return LogicalRect::zero();
}
let mut column_group_rect = self.table_cells_rect.clone();
let inline_start = self.column_dimensions[column_group.track_range.start].0;
let inline_end = self.column_dimensions[column_group.track_range.end - 1].1;
column_group_rect.start_corner.inline = inline_start;
column_group_rect.size.inline = inline_end - inline_start;
column_group_rect
}
fn get_cell_rect(
&self,
coordinates: TableSlotCoordinates,
rowspan: usize,
colspan: usize,
) -> LogicalRect<Au> {
let start_corner = LogicalVec2 {
inline: self.column_dimensions[coordinates.x].0,
block: self.row_dimensions[coordinates.y].0,
};
let size = &LogicalVec2 {
inline: self.column_dimensions[coordinates.x + colspan - 1].1,
block: self.row_dimensions[coordinates.y + rowspan - 1].1,
} - &start_corner;
LogicalRect { start_corner, size }
}
}
impl Table {
@ -1200,6 +1356,33 @@ impl Table {
.0
}
fn get_column_measure_for_column_at_index(
&self,
writing_mode: WritingMode,
column_index: usize,
) -> CellOrColumnMeasure {
let column = match self.columns.get(column_index) {
Some(column) => column,
None => return CellOrColumnMeasure::zero(),
};
let (size, min_size, max_size) = get_sizes_from_style(&column.style, writing_mode);
let percentage_contribution =
get_size_percentage_contribution_from_style(&column.style, writing_mode);
CellOrColumnMeasure {
content_sizes: ContentSizes {
// > The outer min-content width of a table-column or table-column-group is
// > max(min-width, width).
min_content: min_size.inline.max(size.inline),
// > The outer max-content width of a table-column or table-column-group is
// > max(min-width, min(max-width, width)).
max_content: min_size.inline.max(max_size.inline.min(size.inline)),
},
percentage_width: percentage_contribution.inline,
}
}
pub(crate) fn layout(
&self,
layout_context: &LayoutContext,
@ -1250,13 +1433,14 @@ impl TableSlotCell {
fn create_fragment(
&self,
mut layout: CellLayout,
cell_rect: LogicalRect<Length>,
cell_rect: LogicalRect<Au>,
cell_baseline: Au,
positioning_context: &mut PositioningContext,
) -> BoxFragment {
// This must be scoped to this function because it conflicts with euclid's Zero.
use style::Zero as StyleZero;
let cell_rect: LogicalRect<Length> = cell_rect.into();
let cell_content_rect = cell_rect.deflate(&(&layout.padding + &layout.border));
let content_block_size = layout.layout.content_block_size.into();
let vertical_align_offset = match self.effective_vertical_align() {
@ -1278,7 +1462,7 @@ impl TableSlotCell {
inline: Length::new(0.),
block: vertical_align_offset,
};
let vertical_align_fragment = AnonymousFragment::new(
let vertical_align_fragment = PositioningFragment::new_anonymous(
vertical_align_fragment_rect,
layout.layout.fragments,
self.style.writing_mode,
@ -1302,7 +1486,7 @@ impl TableSlotCell {
BoxFragment::new(
self.base_fragment_info,
self.style.clone(),
vec![Fragment::Anonymous(vertical_align_fragment)],
vec![Fragment::Positioning(vertical_align_fragment)],
cell_content_rect,
layout.padding,
layout.border,
@ -1313,3 +1497,73 @@ impl TableSlotCell {
.with_baselines(layout.layout.baselines)
}
}
fn get_size_percentage_contribution_from_style(
style: &Arc<ComputedValues>,
writing_mode: WritingMode,
) -> LogicalVec2<Percentage> {
// From <https://drafts.csswg.org/css-tables/#percentage-contribution>
// > The percentage contribution of a table cell, column, or column group is defined
// > in terms of the computed values of width and max-width that have computed values
// > that are percentages:
// > min(percentage width, percentage max-width).
// > If the computed values are not percentages, then 0% is used for width, and an
// > infinite percentage is used for max-width.
let size = style.box_size(writing_mode);
let max_size = style.max_box_size(writing_mode);
let get_contribution_for_axis =
|size: LengthPercentageOrAuto<'_>, max_size: Option<&ComputedLengthPercentage>| {
let size_percentage = size
.non_auto()
.and_then(|length_percentage| length_percentage.to_percentage())
.unwrap_or(Percentage(0.));
let max_size_percentage = max_size
.and_then(|length_percentage| length_percentage.to_percentage())
.unwrap_or(Percentage(f32::INFINITY));
Percentage(size_percentage.0.min(max_size_percentage.0))
};
LogicalVec2 {
inline: get_contribution_for_axis(size.inline, max_size.inline),
block: get_contribution_for_axis(size.block, max_size.block),
}
}
fn get_sizes_from_style(
style: &Arc<ComputedValues>,
writing_mode: WritingMode,
) -> (LogicalVec2<Au>, LogicalVec2<Au>, LogicalVec2<Au>) {
let get_max_size_for_axis = |size: Option<&ComputedLengthPercentage>| {
size.map_or_else(
|| MAX_AU,
|length_percentage| length_percentage.resolve(Length::zero()).into(),
)
};
let max_size = style.max_box_size(writing_mode);
let max_size = LogicalVec2 {
inline: get_max_size_for_axis(max_size.inline),
block: get_max_size_for_axis(max_size.block),
};
let get_size_for_axis = |size: LengthPercentageOrAuto<'_>| {
size.percentage_relative_to(Length::zero())
.map(|value| value.into())
.auto_is(Au::zero)
};
let min_size = style.min_box_size(writing_mode);
let min_size = LogicalVec2 {
inline: get_size_for_axis(min_size.inline),
block: get_size_for_axis(min_size.block),
};
let size = style.box_size(writing_mode);
let size = LogicalVec2 {
inline: get_size_for_axis(size.inline),
block: get_size_for_axis(size.block),
};
(size, min_size, max_size)
}

View file

@ -12,6 +12,8 @@
mod construct;
mod layout;
use std::ops::Range;
pub(crate) use construct::AnonymousTableContent;
pub use construct::TableBuilder;
use euclid::{Point2D, Size2D, UnknownUnit, Vector2D};
@ -32,6 +34,19 @@ pub struct Table {
#[serde(skip_serializing)]
style: Arc<ComputedValues>,
/// The column groups for this table.
pub column_groups: Vec<TableTrackGroup>,
/// The columns of this tabled defined by `<colgroup> | display: table-column-group`
/// and `<col> | display: table-column` elements as well as `display: table-column`.
pub columns: Vec<TableTrack>,
/// The rows groups for this table deinfed by `<tbody>`, `<thead>`, and `<tfoot>`.
pub row_groups: Vec<TableTrackGroup>,
/// The rows of this tabled defined by `<tr>` or `display: table-row` elements.
pub rows: Vec<TableTrack>,
/// The content of the slots of this table.
pub slots: Vec<Vec<TableSlot>>,
@ -46,6 +61,10 @@ impl Table {
pub(crate) fn new(style: Arc<ComputedValues>) -> Self {
Self {
style,
column_groups: Vec::new(),
columns: Vec::new(),
row_groups: Vec::new(),
rows: Vec::new(),
slots: Vec::new(),
size: TableSize::zero(),
anonymous: false,
@ -167,3 +186,52 @@ impl TableSlot {
Self::Spanned(vec![offset])
}
}
/// A row or column of a table.
#[derive(Clone, Debug, Serialize)]
pub struct TableTrack {
/// The [`BaseFragmentInfo`] of this cell.
base_fragment_info: BaseFragmentInfo,
/// The style of this table column.
#[serde(skip_serializing)]
style: Arc<ComputedValues>,
/// The index of the table row or column group parent in the table's list of row or column
/// groups.
group_index: Option<usize>,
/// Whether or not this [`TableTrack`] was anonymous, for instance created due to
/// a `span` attribute set on a parent `<colgroup>`.
is_anonymous: bool,
}
#[derive(Debug, PartialEq, Serialize)]
pub enum TableTrackGroupType {
HeaderGroup,
FooterGroup,
RowGroup,
ColumnGroup,
}
#[derive(Debug, Serialize)]
pub struct TableTrackGroup {
/// The [`BaseFragmentInfo`] of this [`TableTrackGroup`].
base_fragment_info: BaseFragmentInfo,
/// The style of this [`TableTrackGroup`].
#[serde(skip_serializing)]
style: Arc<ComputedValues>,
/// The type of this [`TableTrackGroup`].
group_type: TableTrackGroupType,
/// The range of tracks in this [`TableTrackGroup`].
track_range: Range<usize>,
}
impl TableTrackGroup {
pub(super) fn is_empty(&self) -> bool {
self.track_range.is_empty()
}
}

View file

@ -64,6 +64,7 @@ use xml5ever::serialize::TraversalScope::{
};
use xml5ever::serialize::{SerializeOpts as XmlSerializeOpts, TraversalScope as XmlTraversalScope};
use super::htmltablecolelement::{HTMLTableColElement, HTMLTableColElementLayoutHelpers};
use crate::dom::activation::Activatable;
use crate::dom::attr::{Attr, AttrHelpersForLayout};
use crate::dom::bindings::cell::{ref_filter_map, DomRefCell, Ref, RefMut};
@ -614,8 +615,9 @@ pub trait LayoutElementHelpers<'dom> {
fn synthesize_presentational_hints_for_legacy_attributes<V>(self, hints: &mut V)
where
V: Push<ApplicableDeclarationBlock>;
fn get_colspan(self) -> u32;
fn get_rowspan(self) -> u32;
fn get_span(self) -> Option<u32>;
fn get_colspan(self) -> Option<u32>;
fn get_rowspan(self) -> Option<u32>;
fn is_html_element(self) -> bool;
fn id_attribute(self) -> *const Option<Atom>;
fn style_attribute(self) -> *const Option<Arc<Locked<PropertyDeclarationBlock>>>;
@ -1019,24 +1021,22 @@ impl<'dom> LayoutElementHelpers<'dom> for LayoutDom<'dom, Element> {
}
}
fn get_colspan(self) -> u32 {
if let Some(this) = self.downcast::<HTMLTableCellElement>() {
this.get_colspan().unwrap_or(1)
} else {
// Don't panic since `display` can cause this to be called on arbitrary
// elements.
1
}
fn get_span(self) -> Option<u32> {
// Don't panic since `display` can cause this to be called on arbitrary elements.
self.downcast::<HTMLTableColElement>()
.and_then(|element| element.get_span())
}
fn get_rowspan(self) -> u32 {
if let Some(this) = self.downcast::<HTMLTableCellElement>() {
this.get_rowspan().unwrap_or(1)
} else {
// Don't panic since `display` can cause this to be called on arbitrary
// elements.
1
}
fn get_colspan(self) -> Option<u32> {
// Don't panic since `display` can cause this to be called on arbitrary elements.
self.downcast::<HTMLTableCellElement>()
.and_then(|element| element.get_colspan())
}
fn get_rowspan(self) -> Option<u32> {
// Don't panic since `display` can cause this to be called on arbitrary elements.
self.downcast::<HTMLTableCellElement>()
.and_then(|element| element.get_rowspan())
}
#[inline]

View file

@ -162,24 +162,22 @@ impl VirtualMethods for HTMLTableCellElement {
match *local_name {
local_name!("colspan") => {
let mut attr = AttrValue::from_u32(value.into(), DEFAULT_COLSPAN);
if let AttrValue::UInt(ref mut s, ref mut val) = attr {
if let AttrValue::UInt(_, ref mut val) = attr {
if *val == 0 {
*val = 1;
*s = "1".into();
}
}
attr
},
local_name!("rowspan") => {
let mut attr = AttrValue::from_u32(value.into(), DEFAULT_ROWSPAN);
if let AttrValue::UInt(ref mut s, ref mut val) = attr {
if let AttrValue::UInt(_, ref mut val) = attr {
if *val == 0 {
let node = self.upcast::<Node>();
let doc = node.owner_doc();
// rowspan = 0 is not supported in quirks mode
if doc.quirks_mode() != QuirksMode::NoQuirks {
*val = 1;
*s = "1".into();
}
}
}

View file

@ -3,14 +3,23 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use html5ever::{local_name, namespace_url, ns, LocalName, Prefix};
use js::rust::HandleObject;
use style::attr::AttrValue;
use super::bindings::root::LayoutDom;
use super::element::Element;
use crate::dom::bindings::codegen::Bindings::HTMLTableColElementBinding::HTMLTableColElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::element::LayoutElementHelpers;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::node::Node;
use crate::dom::virtualmethods::VirtualMethods;
const DEFAULT_SPAN: u32 = 1;
#[dom_struct]
pub struct HTMLTableColElement {
@ -47,3 +56,46 @@ impl HTMLTableColElement {
n
}
}
impl HTMLTableColElementMethods for HTMLTableColElement {
// <https://html.spec.whatwg.org/multipage/#attr-col-span>
make_uint_getter!(Span, "span", DEFAULT_SPAN);
// <https://html.spec.whatwg.org/multipage/#attr-col-span>
make_uint_setter!(SetSpan, "span", DEFAULT_SPAN);
}
pub trait HTMLTableColElementLayoutHelpers<'dom> {
fn get_span(self) -> Option<u32>;
}
impl<'dom> HTMLTableColElementLayoutHelpers<'dom> for LayoutDom<'dom, HTMLTableColElement> {
fn get_span(self) -> Option<u32> {
self.upcast::<Element>()
.get_attr_for_layout(&ns!(), &local_name!("span"))
.map(AttrValue::as_uint)
}
}
impl VirtualMethods for HTMLTableColElement {
fn super_type(&self) -> Option<&dyn VirtualMethods> {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
match *local_name {
local_name!("span") => {
let mut attr = AttrValue::from_u32(value.into(), DEFAULT_SPAN);
if let AttrValue::UInt(_, ref mut val) = attr {
if *val == 0 {
*val = 1;
}
}
attr
},
_ => self
.super_type()
.unwrap()
.parse_plain_attribute(local_name, value),
}
}
}

View file

@ -5,6 +5,7 @@
use html5ever::LocalName;
use style::attr::AttrValue;
use super::htmltablecolelement::HTMLTableColElement;
use crate::dom::attr::Attr;
use crate::dom::bindings::inheritance::{
Castable, ElementTypeId, HTMLElementTypeId, HTMLMediaElementTypeId, NodeTypeId,
@ -250,6 +251,9 @@ pub fn vtable_for(node: &Node) -> &dyn VirtualMethods {
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLTableCellElement,
)) => node.downcast::<HTMLTableCellElement>().unwrap() as &dyn VirtualMethods,
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableColElement)) => {
node.downcast::<HTMLTableColElement>().unwrap() as &dyn VirtualMethods
},
NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableRowElement)) => {
node.downcast::<HTMLTableRowElement>().unwrap() as &dyn VirtualMethods
},

View file

@ -7,8 +7,8 @@
interface HTMLTableColElement : HTMLElement {
[HTMLConstructor] constructor();
// [CEReactions]
// attribute unsigned long span;
[CEReactions]
attribute unsigned long span;
// also has obsolete members
};

View file

@ -465,7 +465,16 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ThreadSafeLayoutNode<'dom>
this.iframe_pipeline_id()
}
fn get_colspan(&self) -> u32 {
fn get_span(&self) -> Option<u32> {
unsafe {
self.get_jsmanaged()
.downcast::<Element>()
.unwrap()
.get_span()
}
}
fn get_colspan(&self) -> Option<u32> {
unsafe {
self.get_jsmanaged()
.downcast::<Element>()
@ -474,7 +483,7 @@ impl<'dom, LayoutDataType: LayoutDataTrait> ThreadSafeLayoutNode<'dom>
}
}
fn get_rowspan(&self) -> u32 {
fn get_rowspan(&self) -> Option<u32> {
unsafe {
self.get_jsmanaged()
.downcast::<Element>()

View file

@ -294,9 +294,9 @@ pub trait ThreadSafeLayoutNode<'dom>:
/// not an iframe element, fails. Returns None if there is no nested browsing context.
fn iframe_pipeline_id(&self) -> Option<PipelineId>;
fn get_colspan(&self) -> u32;
fn get_rowspan(&self) -> u32;
fn get_span(&self) -> Option<u32>;
fn get_colspan(&self) -> Option<u32>;
fn get_rowspan(&self) -> Option<u32>;
fn fragment_type(&self) -> FragmentType {
self.get_pseudo_element_type().fragment_type()

View file

@ -1,6 +0,0 @@
[HTMLTableColElement.html]
[span on HTMLTableColElement must enqueue an attributeChanged reaction when adding a new attribute]
expected: FAIL
[span on HTMLTableColElement must enqueue an attributeChanged reaction when replacing an existing attribute]
expected: FAIL

View file

@ -3282,9 +3282,6 @@
[HTMLTableElement interface: attribute frame]
expected: FAIL
[HTMLTableColElement interface: document.createElement("colgroup") must inherit property "span" with the proper type]
expected: FAIL
[HTMLParamElement interface: attribute valueType]
expected: FAIL
@ -4005,9 +4002,6 @@
[HTMLAreaElement interface: attribute hostname]
expected: FAIL
[HTMLTableColElement interface: attribute span]
expected: FAIL
[HTMLSlotElement interface object name]
expected: FAIL
@ -4422,9 +4416,6 @@
[HTMLFrameElement interface: attribute scrolling]
expected: FAIL
[HTMLTableColElement interface: document.createElement("col") must inherit property "span" with the proper type]
expected: FAIL
[HTMLElement interface: document.createElement("noscript") must inherit property "accessKeyLabel" with the proper type]
expected: FAIL

View file

@ -1,2 +0,0 @@
[table-backgrounds-bs-colgroup-001.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[table-backgrounds-bs-column-001.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[table-backgrounds-bs-row-001.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[table-backgrounds-bs-rowgroup-001.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[border-collapse-rowspan-cell.html]
expected: FAIL

View file

@ -2,26 +2,14 @@
[Table-cell is 100px tall]
expected: FAIL
[Table-row is 100px wide]
expected: FAIL
[Table-row is 100px tall]
expected: FAIL
[Table-row-group is 100px wide]
expected: FAIL
[Table-row-group is 100px tall]
expected: FAIL
[Table-column is 100px wide]
expected: FAIL
[Table-column is 100px tall]
expected: FAIL
[Table-column-group is 100px wide]
expected: FAIL
[Table-column-group is 100px tall]
expected: FAIL

View file

@ -13,15 +13,3 @@
[Table-column-group is 100px tall]
expected: FAIL
[Table-row is 100px wide]
expected: FAIL
[Table-row-group is 100px wide]
expected: FAIL
[Table-column is 100px wide]
expected: FAIL
[Table-column-group is 100px wide]
expected: FAIL

View file

@ -7,15 +7,3 @@
[First (empty) table-row-group should be located at 10px top]
expected: FAIL
[Second table-row-group is 100px wide]
expected: FAIL
[Second table-row-group should be located at 10px left]
expected: FAIL
[Second table-row-group should be located at 10px top]
expected: FAIL
[Second table-row-group is 100px tall]
expected: FAIL

View file

@ -0,0 +1,2 @@
[col-definite-max-size-001.html]
expected: FAIL

View file

@ -0,0 +1,2 @@
[col-definite-min-size-001.html]
expected: FAIL

View file

@ -0,0 +1,2 @@
[col-definite-size-001.html]
expected: FAIL

View file

@ -5,8 +5,5 @@
[Checking intermediate min-content width for span 2 (2)]
expected: FAIL
[Checking intermediate min-content width for span 2 (3)]
expected: FAIL
[Checking intermediate min-content width for span 2 (4)]
expected: FAIL

View file

@ -2,9 +2,6 @@
[HTML -> CSS Mapping is applied correctly on proper table markup (border-spacing, padding)]
expected: FAIL
[HTML -> CSS Mapping is applied correctly on improper table markup (no table => no border-spacing, but padding)]
expected: FAIL
[HTML -> CSS Mapping is applied correctly on improper table markup (no td => border-spacing, but no padding)]
expected: FAIL

View file

@ -0,0 +1,2 @@
[col-paint-htb-rtl.html]
expected: FAIL

View file

@ -7,3 +7,12 @@
[Replaced elements inside a table cannot be table-row and are considered inline -- input elements (width)]
expected: FAIL
[Replaced elements inside a table cannot be table-row and are considered inline -- input elements (top)]
expected: FAIL
[Replaced elements inside a table cannot be table-column and are considered inline -- input elements (top)]
expected: FAIL
[Replaced elements outside a table cannot be table-row and are considered inline -- input=file elements]
expected: FAIL

View file

@ -11,9 +11,6 @@
[2.2. An anonymous table-row box must be generated around each sequence of consecutive children of a table-row-grouping box which are not table-row boxes. (2/3)]
expected: FAIL
[2.3 happens after 2.1. and 2.2. (2/2)]
expected: FAIL
[3.2. An anonymous table or inline-table box must be generated around each sequence of consecutive proper table child box which are misparented]
expected: FAIL
@ -22,3 +19,6 @@
[1.4. Anonymous inline boxes which contains only white space and are between two immediate siblings *each* of which is a table-non-root element, are treated as if they had display: none.]
expected: FAIL
[2.3 happens after 2.1. and 2.2. (1/2)]
expected: FAIL

View file

@ -7,3 +7,6 @@
[.container 13]
expected: FAIL
[.container 3]
expected: FAIL

View file

@ -82,3 +82,6 @@
[table 31]
expected: FAIL
[table 8]
expected: FAIL

View file

@ -1,6 +1,3 @@
[element-sizing.html]
[table 1]
expected: FAIL
[table 2]
expected: FAIL

View file

@ -23,27 +23,12 @@
[table 9]
expected: FAIL
[table 10]
expected: FAIL
[table 11]
expected: FAIL
[table 12]
expected: FAIL
[table 13]
expected: FAIL
[table 14]
expected: FAIL
[table 15]
expected: FAIL
[table 16]
expected: FAIL
[table 17]
expected: FAIL
@ -62,8 +47,5 @@
[table 23]
expected: FAIL
[table 24]
expected: FAIL
[table 20]
expected: FAIL

View file

@ -1,10 +1,4 @@
[td-box-sizing-003.html]
[table 2]
expected: FAIL
[table 3]
expected: FAIL
[table 8]
expected: FAIL

View file

@ -1,3 +1,9 @@
[visibility-collapse-row-group-001.html]
[row group visibility:collapse changes table height]
expected: FAIL
[the first row should be collapsed]
expected: FAIL
[the second row should be collapsed]
expected: FAIL

View file

@ -1,3 +1,6 @@
[visibility-collapse-rowspan-002-border-separate.html]
[spanning cell shrinks to sum of remaining three rows' height]
expected: FAIL
[spanning row visibility:collapse makes row height 0]
expected: FAIL

View file

@ -1,3 +1,6 @@
[visibility-collapse-rowspan-002.html]
[spanning cell shrinks to sum of remaining three rows' height]
expected: FAIL
[spanning row visibility:collapse makes row height 0]
expected: FAIL

View file

@ -0,0 +1,3 @@
[visibility-collapse-rowspan-003-border-separate.html]
[collapsed row has zero height]
expected: FAIL

View file

@ -0,0 +1,3 @@
[visibility-collapse-rowspan-003.html]
[collapsed row has zero height]
expected: FAIL

View file

@ -4,3 +4,9 @@
[(2nd collapse) spanning cell shrinks to sum of remaining three rows' height]
expected: FAIL
[third row visibility:collapse makes row height 0]
expected: FAIL
[(2nd collapse) third row visibility:collapse makes row height 0]
expected: FAIL

View file

@ -1,6 +0,0 @@
[HTMLTableColElement.html]
[span on HTMLTableColElement must enqueue an attributeChanged reaction when adding a new attribute]
expected: FAIL
[span on HTMLTableColElement must enqueue an attributeChanged reaction when replacing an existing attribute]
expected: FAIL

View file

@ -3432,9 +3432,6 @@
[HTMLAreaElement interface: attribute origin]
expected: FAIL
[HTMLTableColElement interface: document.createElement("colgroup") must inherit property "span" with the proper type]
expected: FAIL
[HTMLTableSectionElement interface: attribute chOff]
expected: FAIL
@ -3831,9 +3828,6 @@
[HTMLAreaElement interface: attribute hostname]
expected: FAIL
[HTMLTableColElement interface: attribute span]
expected: FAIL
[HTMLTableElement interface: document.createElement("table") must inherit property "border" with the proper type]
expected: FAIL
@ -4221,9 +4215,6 @@
[HTMLFrameElement interface: attribute scrolling]
expected: FAIL
[HTMLTableColElement interface: document.createElement("col") must inherit property "span" with the proper type]
expected: FAIL
[HTMLElement interface: document.createElement("noscript") must inherit property "accessKeyLabel" with the proper type]
expected: FAIL

View file

@ -1829,30 +1829,6 @@
[colgroup.tabIndex: IDL set to -2147483648]
expected: FAIL
[colgroup.span: typeof IDL attribute]
expected: FAIL
[colgroup.span: IDL get with DOM attribute unset]
expected: FAIL
[colgroup.span: setAttribute() to -2147483649]
expected: FAIL
[colgroup.span: setAttribute() to -2147483648]
expected: FAIL
[colgroup.span: setAttribute() to -36]
expected: FAIL
[colgroup.span: setAttribute() to -1]
expected: FAIL
[colgroup.span: setAttribute() to 0]
expected: FAIL
[colgroup.span: setAttribute() to 1]
expected: FAIL
[colgroup.span: setAttribute() to 2147483647]
expected: FAIL
@ -1865,171 +1841,18 @@
[colgroup.span: setAttribute() to 4294967296]
expected: FAIL
[colgroup.span: setAttribute() to ""]
expected: FAIL
[colgroup.span: setAttribute() to "-1"]
expected: FAIL
[colgroup.span: setAttribute() to "-0"]
expected: FAIL
[colgroup.span: setAttribute() to "0"]
expected: FAIL
[colgroup.span: setAttribute() to "1"]
expected: FAIL
[colgroup.span: setAttribute() to "\\t7"]
expected: FAIL
[colgroup.span: setAttribute() to "\\v7"]
expected: FAIL
[colgroup.span: setAttribute() to "\\f7"]
expected: FAIL
[colgroup.span: setAttribute() to " 7"]
expected: FAIL
[colgroup.span: setAttribute() to " 7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "\\n7"]
expected: FAIL
[colgroup.span: setAttribute() to "\\r7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to " 7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to "7"]
expected: FAIL
[colgroup.span: setAttribute() to " 7"]
expected: FAIL
[colgroup.span: setAttribute() to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo "]
expected: FAIL
[colgroup.span: setAttribute() to undefined]
expected: FAIL
[colgroup.span: setAttribute() to 1.5]
expected: FAIL
[colgroup.span: setAttribute() to "5%"]
expected: FAIL
[colgroup.span: setAttribute() to "+100"]
expected: FAIL
[colgroup.span: setAttribute() to ".5"]
expected: FAIL
[colgroup.span: setAttribute() to true]
expected: FAIL
[colgroup.span: setAttribute() to false]
expected: FAIL
[colgroup.span: setAttribute() to object "[object Object\]"]
expected: FAIL
[colgroup.span: setAttribute() to NaN]
expected: FAIL
[colgroup.span: setAttribute() to Infinity]
expected: FAIL
[colgroup.span: setAttribute() to -Infinity]
expected: FAIL
[colgroup.span: setAttribute() to "\\0"]
expected: FAIL
[colgroup.span: setAttribute() to object "2"]
expected: FAIL
[colgroup.span: setAttribute() to object "3"]
expected: FAIL
[colgroup.span: setAttribute() to 1000]
expected: FAIL
[colgroup.span: setAttribute() to 1001]
expected: FAIL
[colgroup.span: IDL set to 0]
expected: FAIL
[colgroup.span: IDL set to 1]
expected: FAIL
[colgroup.span: IDL set to 257]
expected: FAIL
[colgroup.span: IDL set to 2147483647]
expected: FAIL
[colgroup.span: IDL set to "-0"]
expected: FAIL
[colgroup.span: IDL set to 2147483648]
expected: FAIL
[colgroup.span: IDL set to 4294967295]
expected: FAIL
[colgroup.span: IDL set to 1000]
expected: FAIL
[colgroup.span: IDL set to 1001]
expected: FAIL
@ -2909,30 +2732,6 @@
[col.tabIndex: IDL set to -2147483648]
expected: FAIL
[col.span: typeof IDL attribute]
expected: FAIL
[col.span: IDL get with DOM attribute unset]
expected: FAIL
[col.span: setAttribute() to -2147483649]
expected: FAIL
[col.span: setAttribute() to -2147483648]
expected: FAIL
[col.span: setAttribute() to -36]
expected: FAIL
[col.span: setAttribute() to -1]
expected: FAIL
[col.span: setAttribute() to 0]
expected: FAIL
[col.span: setAttribute() to 1]
expected: FAIL
[col.span: setAttribute() to 2147483647]
expected: FAIL
@ -2945,171 +2744,18 @@
[col.span: setAttribute() to 4294967296]
expected: FAIL
[col.span: setAttribute() to ""]
expected: FAIL
[col.span: setAttribute() to "-1"]
expected: FAIL
[col.span: setAttribute() to "-0"]
expected: FAIL
[col.span: setAttribute() to "0"]
expected: FAIL
[col.span: setAttribute() to "1"]
expected: FAIL
[col.span: setAttribute() to "\\t7"]
expected: FAIL
[col.span: setAttribute() to "\\v7"]
expected: FAIL
[col.span: setAttribute() to "\\f7"]
expected: FAIL
[col.span: setAttribute() to " 7"]
expected: FAIL
[col.span: setAttribute() to " 7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "\\n7"]
expected: FAIL
[col.span: setAttribute() to "\\r7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to " 7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to "7"]
expected: FAIL
[col.span: setAttribute() to " 7"]
expected: FAIL
[col.span: setAttribute() to " \\0\\x01\\x02\\x03\\x04\\x05\\x06\\x07 \\b\\t\\n\\v\\f\\r\\x0e\\x0f \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17 \\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f foo "]
expected: FAIL
[col.span: setAttribute() to undefined]
expected: FAIL
[col.span: setAttribute() to 1.5]
expected: FAIL
[col.span: setAttribute() to "5%"]
expected: FAIL
[col.span: setAttribute() to "+100"]
expected: FAIL
[col.span: setAttribute() to ".5"]
expected: FAIL
[col.span: setAttribute() to true]
expected: FAIL
[col.span: setAttribute() to false]
expected: FAIL
[col.span: setAttribute() to object "[object Object\]"]
expected: FAIL
[col.span: setAttribute() to NaN]
expected: FAIL
[col.span: setAttribute() to Infinity]
expected: FAIL
[col.span: setAttribute() to -Infinity]
expected: FAIL
[col.span: setAttribute() to "\\0"]
expected: FAIL
[col.span: setAttribute() to object "2"]
expected: FAIL
[col.span: setAttribute() to object "3"]
expected: FAIL
[col.span: setAttribute() to 1000]
expected: FAIL
[col.span: setAttribute() to 1001]
expected: FAIL
[col.span: IDL set to 0]
expected: FAIL
[col.span: IDL set to 1]
expected: FAIL
[col.span: IDL set to 257]
expected: FAIL
[col.span: IDL set to 2147483647]
expected: FAIL
[col.span: IDL set to "-0"]
expected: FAIL
[col.span: IDL set to 2147483648]
expected: FAIL
[col.span: IDL set to 4294967295]
expected: FAIL
[col.span: IDL set to 1000]
expected: FAIL
[col.span: IDL set to 1001]
expected: FAIL
@ -7037,9 +6683,6 @@
[td.tabIndex: IDL set to -2147483648]
expected: FAIL
[td.colSpan: setAttribute() to 0]
expected: FAIL
[td.colSpan: setAttribute() to 2147483647]
expected: FAIL
@ -7052,12 +6695,6 @@
[td.colSpan: setAttribute() to 4294967296]
expected: FAIL
[td.colSpan: setAttribute() to "-0"]
expected: FAIL
[td.colSpan: setAttribute() to "0"]
expected: FAIL
[td.colSpan: setAttribute() to 1001]
expected: FAIL
@ -8660,9 +8297,6 @@
[th.tabIndex: IDL set to -2147483648]
expected: FAIL
[th.colSpan: setAttribute() to 0]
expected: FAIL
[th.colSpan: setAttribute() to 2147483647]
expected: FAIL
@ -8675,12 +8309,6 @@
[th.colSpan: setAttribute() to 4294967296]
expected: FAIL
[th.colSpan: setAttribute() to "-0"]
expected: FAIL
[th.colSpan: setAttribute() to "0"]
expected: FAIL
[th.colSpan: setAttribute() to 1001]
expected: FAIL
@ -9995,60 +9623,12 @@
[colgroup.tabIndex: setAttribute() to object "3"]
expected: FAIL
[colgroup.span: setAttribute() to "-"]
expected: FAIL
[colgroup.span: setAttribute() to "+"]
expected: FAIL
[colgroup.span: setAttribute() to "\\t\\v7"]
expected: FAIL
[colgroup.span: setAttribute() to "\\n\\v7"]
expected: FAIL
[colgroup.span: setAttribute() to "\\f\\v7"]
expected: FAIL
[colgroup.span: setAttribute() to "\\r\\v7"]
expected: FAIL
[colgroup.span: setAttribute() to " \\v7"]
expected: FAIL
[colgroup.span: setAttribute() to "7\\v"]
expected: FAIL
[col.tabIndex: setAttribute() to "7\\v"]
expected: FAIL
[col.tabIndex: setAttribute() to object "3"]
expected: FAIL
[col.span: setAttribute() to "-"]
expected: FAIL
[col.span: setAttribute() to "+"]
expected: FAIL
[col.span: setAttribute() to "\\t\\v7"]
expected: FAIL
[col.span: setAttribute() to "\\n\\v7"]
expected: FAIL
[col.span: setAttribute() to "\\f\\v7"]
expected: FAIL
[col.span: setAttribute() to "\\r\\v7"]
expected: FAIL
[col.span: setAttribute() to " \\v7"]
expected: FAIL
[col.span: setAttribute() to "7\\v"]
expected: FAIL
[tbody.tabIndex: setAttribute() to "7\\v"]
expected: FAIL