style: Implement basic column spans.

This patch provides some of the groundwork for column spans greater than
1. It implements the column-span CSS property (prefixed so as not to be
exposed to content) as well as the corresponding colspan attribute;
although the former is not well-specified outside of CSS multi-column
layout, INTRINSIC refers to it. Although width is distributed to
spanning columns, they do not yet contribute minimum and preferred
widths; this will be implemented in a follow-up.

Additionally, this patch cleans up some miscellaneous formatting issues
and improves the handling of table rowgroups.
This commit is contained in:
Patrick Walton 2014-12-07 23:01:35 -08:00
parent 14bafb11be
commit 56b78de5bc
13 changed files with 269 additions and 129 deletions

View file

@ -11,11 +11,12 @@ use block::{ISizeConstraintInput, ISizeConstraintSolution};
use construct::FlowConstructor;
use context::LayoutContext;
use floats::FloatKind;
use flow::{Flow, FlowClass, IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS, ImmutableFlowUtils};
use flow::{TableFlowClass};
use flow::{mod, Flow, FlowClass, IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS};
use flow::{ImmutableFlowUtils, TableFlowClass};
use fragment::{Fragment, FragmentBoundsIterator};
use layout_debug;
use model::{IntrinsicISizes, IntrinsicISizesContribution};
use table_row::CellIntrinsicInlineSize;
use table_wrapper::{TableLayout, FixedLayout, AutoLayout};
use wrapper::ThreadSafeLayoutNode;
@ -105,24 +106,52 @@ impl TableFlow {
/// Update the corresponding value of `self_inline_sizes` if a value of `kid_inline_sizes` has
/// a larger value than one of `self_inline_sizes`. Returns the minimum and preferred inline
/// sizes.
pub fn update_column_inline_sizes(parent_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>,
child_inline_sizes: &Vec<ColumnIntrinsicInlineSize>)
fn update_automatic_column_inline_sizes(parent_inline_sizes: &mut Vec<ColumnInlineSize>,
child_cell_inline_sizes: &[CellIntrinsicInlineSize])
-> IntrinsicISizes {
let mut total_inline_sizes = IntrinsicISizes::new();
for (parent_sizes, child_sizes) in parent_inline_sizes.iter_mut()
.zip(child_inline_sizes.iter()) {
*parent_sizes = ColumnIntrinsicInlineSize {
minimum_length: max(parent_sizes.minimum_length, child_sizes.minimum_length),
percentage: parent_sizes.greatest_percentage(child_sizes),
preferred: max(parent_sizes.preferred, child_sizes.preferred),
constrained: parent_sizes.constrained || child_sizes.constrained
};
let mut column_index = 0;
for child_cell_inline_size in child_cell_inline_sizes.iter() {
for _ in range(0, child_cell_inline_size.column_span) {
if column_index < parent_inline_sizes.len() {
// We already have some intrinsic size information for this column. Merge it in
// according to the rules specified in INTRINSIC § 4.
let parent_sizes = &mut parent_inline_sizes[column_index];
if child_cell_inline_size.column_span > 1 {
// TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC §
// 4. For now we make this column contribute no width.
} else {
let column_size = &child_cell_inline_size.column_size;
*parent_sizes = ColumnInlineSize {
minimum_length: max(parent_sizes.minimum_length,
column_size.minimum_length),
percentage: parent_sizes.greatest_percentage(column_size),
preferred: max(parent_sizes.preferred, column_size.preferred),
constrained: parent_sizes.constrained || column_size.constrained,
}
}
} else {
// We discovered a new column. Initialize its data.
debug_assert!(column_index == parent_inline_sizes.len());
if child_cell_inline_size.column_span > 1 {
// TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC §
// 4. For now we make this column contribute no width.
parent_inline_sizes.push(ColumnInlineSize::new())
} else {
parent_inline_sizes.push(child_cell_inline_size.column_size)
}
}
total_inline_sizes.minimum_inline_size = total_inline_sizes.minimum_inline_size +
parent_sizes.minimum_length;
total_inline_sizes.preferred_inline_size = total_inline_sizes.preferred_inline_size +
parent_sizes.preferred;
parent_inline_sizes[column_index].minimum_length;
total_inline_sizes.preferred_inline_size =
total_inline_sizes.preferred_inline_size +
parent_inline_sizes[column_index].preferred;
column_index += 1
}
}
total_inline_sizes
}
@ -136,6 +165,39 @@ impl TableFlow {
fn assign_block_size_table_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) {
self.block_flow.assign_block_size_block_base(layout_context, MarginsMayNotCollapse);
}
/// Updates the minimum and preferred inline-size calculation for a single row. This is
/// factored out into a separate function because we process children of rowgroups too.
fn update_column_inline_sizes_for_row(child: &mut Flow,
column_inline_sizes: &mut Vec<ColumnInlineSize>,
computation: &mut IntrinsicISizesContribution,
did_first_row: &mut bool,
table_layout: TableLayout) {
// Read column inline-sizes from the table-row, and assign inline-size=0 for the columns
// not defined in the column group.
//
// FIXME: Need to read inline-sizes from either table-header-group OR the first table-row.
debug_assert!(child.is_table_row());
let row = child.as_table_row();
match table_layout {
FixedLayout => {
// Fixed table layout only looks at the first row.
//
// FIXME(pcwalton): This is really inefficient. We should stop after the first row!
if !*did_first_row {
*did_first_row = true;
for cell_inline_size in row.cell_intrinsic_inline_sizes.iter() {
column_inline_sizes.push(cell_inline_size.column_size);
}
}
}
AutoLayout => {
computation.union_block(&TableFlow::update_automatic_column_inline_sizes(
column_inline_sizes,
row.cell_intrinsic_inline_sizes.as_slice()))
}
}
}
}
impl Flow for TableFlow {
@ -190,50 +252,20 @@ impl Flow for TableFlow {
constrained: false,
})
}
} else if kid.is_table_rowgroup() || kid.is_table_row() {
// Read column inline-sizes from the table-row-group/table-row, and assign
// inline-size=0 for the columns not defined in the column group.
// FIXME: Need to read inline-sizes from either table-header-group OR the first
// table-row.
match self.table_layout {
FixedLayout => {
// Fixed table layout only looks at the first row.
if !did_first_row {
did_first_row = true;
for child_column_inline_size in kid.column_intrinsic_inline_sizes()
.iter() {
self.column_intrinsic_inline_sizes.push(*child_column_inline_size);
}
}
}
AutoLayout => {
let child_column_inline_sizes = kid.column_intrinsic_inline_sizes();
let mut child_intrinsic_sizes = TableFlow::update_column_inline_sizes(
&mut self.column_intrinsic_inline_sizes,
child_column_inline_sizes);
// Add new columns if processing this row caused us to discover them.
let child_column_count = child_column_inline_sizes.len();
let parent_column_count = self.column_intrinsic_inline_sizes.len();
debug!("table until the previous row has {} column(s) and this row has {} \
column(s)",
parent_column_count,
child_column_count);
self.column_intrinsic_inline_sizes.reserve(child_column_count);
for i in range(parent_column_count, child_column_count) {
let inline_size_for_new_column = (*child_column_inline_sizes)[i];
child_intrinsic_sizes.minimum_inline_size =
child_intrinsic_sizes.minimum_inline_size +
inline_size_for_new_column.minimum_length;
child_intrinsic_sizes.preferred_inline_size =
child_intrinsic_sizes.preferred_inline_size +
inline_size_for_new_column.preferred;
self.column_intrinsic_inline_sizes.push(inline_size_for_new_column);
}
computation.union_block(&child_intrinsic_sizes)
}
} else if kid.is_table_rowgroup() {
for grandkid in flow::mut_base(kid).child_iter() {
TableFlow::update_column_inline_sizes_for_row(grandkid,
&mut self.column_inline_sizes,
&mut computation,
&mut did_first_row,
self.table_layout)
}
} else if kid.is_table_row() {
TableFlow::update_column_inline_sizes_for_row(kid,
&mut self.column_inline_sizes,
&mut computation,
&mut did_first_row,
self.table_layout)
}
}

View file

@ -30,20 +30,29 @@ use sync::Arc;
pub struct TableRowFlow {
pub block_flow: BlockFlow,
/// Information about the intrinsic inline-sizes of each column.
pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
/// Information about the intrinsic inline-sizes of each cell.
pub cell_intrinsic_inline_sizes: Vec<CellIntrinsicInlineSize>,
/// Information about the computed inline-sizes of each column.
pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>,
}
/// Information about the column inline size and span for each cell.
#[deriving(Encodable)]
pub struct CellIntrinsicInlineSize {
/// Inline sizes that this cell contributes to the column.
pub column_size: ColumnInlineSize,
/// The column span of this cell.
pub column_span: u32,
}
impl TableRowFlow {
pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode,
fragment: Fragment)
-> TableRowFlow {
TableRowFlow {
block_flow: BlockFlow::from_node_and_fragment(node, fragment),
column_intrinsic_inline_sizes: Vec::new(),
cell_intrinsic_inline_sizes: Vec::new(),
column_computed_inline_sizes: Vec::new(),
}
}
@ -53,8 +62,8 @@ impl TableRowFlow {
-> TableRowFlow {
TableRowFlow {
block_flow: BlockFlow::from_node(constructor, node),
column_intrinsic_inline_sizes: Vec::new(),
column_computed_inline_sizes: Vec::new(),
cell_intrinsic_inline_sizes: Vec::new(),
column_computed_inline_sizes: Vec::new()
}
}
@ -181,10 +190,13 @@ impl Flow for TableRowFlow {
// Collect the specified column inline-size of the cell. This is used in both fixed and
// automatic table layout calculation.
let child_specified_inline_size = kid.as_table_cell()
.fragment()
.style()
.content_inline_size();
let child_specified_inline_size;
let child_column_span;
{
let child_style = kid.as_table_cell().fragment().style();
child_specified_inline_size = child_style.content_inline_size();
child_column_span = child_style.get_table()._servo_column_span
}
// Collect minimum and preferred inline-sizes of the cell for automatic table layout
// calculation.
@ -208,15 +220,16 @@ impl Flow for TableRowFlow {
};
min_inline_size = min_inline_size + child_column_inline_size.minimum_length;
pref_inline_size = pref_inline_size + child_column_inline_size.preferred;
self.column_intrinsic_inline_sizes.push(child_column_inline_size);
self.cell_intrinsic_inline_sizes.push(CellIntrinsicInlineSize {
column_size: child_column_inline_size,
column_span: child_column_span,
});
}
self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size;
self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = max(min_inline_size,
pref_inline_size);
}
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
/// When called on this context, the context has had its inline-size set by the parent context.
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("table_row::assign_inline_sizes {:x}",
self.block_flow.base.debug_id());
@ -233,11 +246,41 @@ impl Flow for TableRowFlow {
layout_context,
containing_block_inline_size);
// Spread out the completed inline sizes among columns with spans > 1.
let mut computed_inline_size_for_cells = Vec::new();
let mut column_inline_size_iterator = self.column_inline_sizes.iter();
for cell_intrinsic_inline_size in self.cell_intrinsic_inline_sizes.iter() {
//(intrinsic_inline_size_for_column, computed_inline_size_for_column) in
// Start with the computed inline size for the first column in the span.
let mut column_inline_size = match column_inline_size_iterator.next() {
Some(column_inline_size) => *column_inline_size,
None => {
// This could happen if there are too few cells in this row. Don't crash.
break
}
};
// Add in computed inline sizes for any extra columns in the span.
for _ in range(1, cell_intrinsic_inline_size.column_span) {
let extra_column_inline_size = match column_inline_size_iterator.next() {
Some(column_inline_size) => column_inline_size,
None => break,
};
column_inline_size.minimum_length = column_inline_size.minimum_length +
extra_column_inline_size.minimum_length;
column_inline_size.preferred = column_inline_size.preferred +
extra_column_inline_size.preferred;
}
computed_inline_size_for_cells.push(column_inline_size)
}
// Push those inline sizes down to the cells.
self.block_flow.propagate_assigned_inline_size_to_children(
layout_context,
inline_start_content_edge,
containing_block_inline_size,
Some(self.column_computed_inline_sizes.as_slice()));
Some(computed_inline_size_for_cells.as_slice()));
}
fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) {

View file

@ -6,13 +6,12 @@
#![deny(unsafe_blocks)]
use block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayNotCollapse};
use block::{BlockFlow, ISizeAndMarginsComputer};
use construct::FlowConstructor;
use context::LayoutContext;
use flow::{TableRowGroupFlowClass, FlowClass, Flow, ImmutableFlowUtils};
use flow::{mod, Flow, FlowClass, TableRowGroupFlowClass};
use fragment::{Fragment, FragmentBoundsIterator};
use layout_debug;
use model::IntrinsicISizesContribution;
use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable, TableFlow};
use wrapper::ThreadSafeLayoutNode;
@ -24,6 +23,7 @@ use sync::Arc;
/// A table formatting context.
#[deriving(Encodable)]
pub struct TableRowGroupFlow {
/// Fields common to all block flows.
pub block_flow: BlockFlow,
/// Information about the intrinsic inline-sizes of each column.
@ -91,54 +91,11 @@ impl Flow for TableRowGroupFlow {
&mut self.column_computed_inline_sizes
}
/// Recursively (bottom-up) determines the context's preferred and minimum inline-sizes. When
/// called on this context, all child contexts have had their min/pref inline-sizes set. This
/// function must decide min/pref inline-sizes based on child context inline-sizes and
/// dimensions of any fragments it is responsible for flowing.
///
/// Min/pref inline-sizes set by this function are used in automatic table layout calculation.
///
/// Also, this function finds the specified column inline-sizes from the first row. These are
/// used in fixed table layout calculation.
fn bubble_inline_sizes(&mut self) {
let _scope = layout_debug_scope!("table_rowgroup::bubble_inline_sizes {:x}",
self.block_flow.base.debug_id());
let mut computation = IntrinsicISizesContribution::new();
for kid in self.block_flow.base.child_iter() {
assert!(kid.is_table_row());
// Calculate minimum and preferred inline sizes for automatic table layout.
if self.column_intrinsic_inline_sizes.is_empty() {
// We're the first row.
self.column_intrinsic_inline_sizes = kid.column_intrinsic_inline_sizes().clone();
} else {
let mut child_intrinsic_sizes =
TableFlow::update_column_inline_sizes(&mut self.column_intrinsic_inline_sizes,
kid.column_intrinsic_inline_sizes());
// update the number of column inline-sizes from table-rows.
let column_count = self.column_intrinsic_inline_sizes.len();
let child_column_count = kid.column_intrinsic_inline_sizes().len();
for i in range(column_count, child_column_count) {
let this_column_inline_size = (*kid.column_intrinsic_inline_sizes())[i];
// FIXME(pcwalton): Ignoring the percentage here seems dubious.
child_intrinsic_sizes.minimum_inline_size =
child_intrinsic_sizes.minimum_inline_size +
this_column_inline_size.minimum_length;
child_intrinsic_sizes.preferred_inline_size =
child_intrinsic_sizes.preferred_inline_size +
this_column_inline_size.preferred;
self.column_intrinsic_inline_sizes.push(this_column_inline_size);
}
computation.union_block(&child_intrinsic_sizes)
}
}
self.block_flow.base.intrinsic_inline_sizes = computation.finish()
// Proper calculation of intrinsic sizes in table layout requires access to the entire
// table, which we don't have yet. Defer to our parent.
}
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments.

View file

@ -44,8 +44,9 @@ use dom::node::{window_from_node};
use dom::nodelist::NodeList;
use dom::virtualmethods::{VirtualMethods, vtable_for};
use devtools_traits::AttrInfo;
use style::{mod, BgColorSimpleColorAttribute, BorderUnsignedIntegerAttribute, IntegerAttribute};
use style::{LengthAttribute, SimpleColorAttribute, SizeIntegerAttribute, UnsignedIntegerAttribute};
use style::{mod, BgColorSimpleColorAttribute, BorderUnsignedIntegerAttribute};
use style::{ColSpanUnsignedIntegerAttribute, IntegerAttribute, LengthAttribute};
use style::{SimpleColorAttribute, SizeIntegerAttribute, UnsignedIntegerAttribute};
use style::{WidthLengthAttribute, matches, parse_selector_list_from_str};
use servo_util::namespace;
use servo_util::str::{DOMString, LengthOrPercentageOrAuto, SimpleColor};
@ -349,6 +350,14 @@ impl RawLayoutElementHelpers for Element {
None
}
}
ColSpanUnsignedIntegerAttribute => {
if self.is_htmltablecellelement() {
let this: &HTMLTableCellElement = mem::transmute(self);
this.get_colspan()
} else {
panic!("I'm not a table cell!")
}
}
}
}

View file

@ -22,6 +22,7 @@ pub struct HTMLTableCellElement {
htmlelement: HTMLElement,
background_color: Cell<Option<SimpleColor>>,
border: Cell<Option<u32>>,
colspan: Cell<Option<u32>>,
width: Cell<LengthOrPercentageOrAuto>,
}
@ -45,6 +46,7 @@ impl HTMLTableCellElement {
htmlelement: HTMLElement::new_inherited(type_id, tag_name, prefix, document),
background_color: Cell::new(None),
border: Cell::new(None),
colspan: Cell::new(None),
width: Cell::new(AutoLpa),
}
}
@ -58,6 +60,7 @@ impl HTMLTableCellElement {
pub trait HTMLTableCellElementHelpers {
fn get_background_color(&self) -> Option<SimpleColor>;
fn get_border(&self) -> Option<u32>;
fn get_colspan(&self) -> Option<u32>;
fn get_width(&self) -> LengthOrPercentageOrAuto;
}
@ -70,6 +73,10 @@ impl HTMLTableCellElementHelpers for HTMLTableCellElement {
self.border.get()
}
fn get_colspan(&self) -> Option<u32> {
self.colspan.get()
}
fn get_width(&self) -> LengthOrPercentageOrAuto {
self.width.get()
}
@ -97,6 +104,9 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLTableCellElement> {
.as_slice()
.chars()).unwrap_or(1)))
}
&atom!("colspan") => {
self.colspan.set(str::parse_unsigned_integer(attr.value().as_slice().chars()));
}
&atom!("width") => self.width.set(str::parse_length(attr.value().as_slice())),
_ => ()
}
@ -111,6 +121,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLTableCellElement> {
match attr.local_name() {
&atom!("bgcolor") => self.background_color.set(None),
&atom!("border") => self.border.set(None),
&atom!("colspan") => self.colspan.set(None),
&atom!("width") => self.width.set(AutoLpa),
_ => ()
}

View file

@ -108,6 +108,7 @@ impl HTMLTableElementHelpers for HTMLTableElement {
fn get_border(&self) -> Option<u32> {
self.border.get()
}
fn get_width(&self) -> LengthOrPercentageOrAuto {
self.width.get()
}
@ -135,6 +136,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLTableElement> {
.as_slice()
.chars()).unwrap_or(1)))
}
&atom!("width") => self.width.set(str::parse_length(attr.value().as_slice())),
_ => ()
}
}
@ -148,6 +150,7 @@ impl<'a> VirtualMethods for JSRef<'a, HTMLTableElement> {
match attr.local_name() {
&atom!("bgcolor") => self.background_color.set(None),
&atom!("border") => self.border.set(None),
&atom!("width") => self.width.set(AutoLpa),
_ => ()
}
}

View file

@ -8,7 +8,8 @@
use node::{TElement, TElementAttributes, TNode};
use properties::{BackgroundColorDeclaration, BorderBottomWidthDeclaration};
use properties::{BorderLeftWidthDeclaration, BorderRightWidthDeclaration};
use properties::{BorderTopWidthDeclaration, SpecifiedValue, WidthDeclaration, specified};
use properties::{BorderTopWidthDeclaration, ServoColumnSpanDeclaration, SpecifiedValue};
use properties::{WidthDeclaration, specified};
use selector_matching::{DeclarationBlock, Stylist};
use cssparser::{RGBA, RGBAColor};
@ -32,6 +33,8 @@ pub enum IntegerAttribute {
pub enum UnsignedIntegerAttribute {
/// `<td border>`
BorderUnsignedIntegerAttribute,
/// `<td colspan>`
ColSpanUnsignedIntegerAttribute,
}
/// Legacy presentational attributes that take a simple color as defined in HTML5 § 2.4.6.
@ -111,6 +114,14 @@ impl PresentationalHintSynthesis for Stylist {
*shareable = false
}
}
match element.get_unsigned_integer_attribute(ColSpanUnsignedIntegerAttribute) {
None => {}
Some(value) => {
matching_rules_list.vec_push(DeclarationBlock::from_declaration(
ServoColumnSpanDeclaration(SpecifiedValue(value))));
*shareable = false
}
}
self.synthesize_presentational_hint_for_legacy_background_color_attribute(
element,
matching_rules_list,

View file

@ -54,9 +54,10 @@ pub use selectors::{PseudoElement, Before, After, SelectorList, parse_selector_l
pub use selectors::{AttrSelector, NamespaceConstraint, SpecificNamespace, AnyNamespace};
pub use selectors::{SimpleSelector, LocalNameSelector};
pub use cssparser::{Color, RGBA};
pub use legacy::{BgColorSimpleColorAttribute, BorderUnsignedIntegerAttribute, IntegerAttribute};
pub use legacy::{LengthAttribute, SimpleColorAttribute, SizeIntegerAttribute};
pub use legacy::{UnsignedIntegerAttribute, WidthLengthAttribute};
pub use legacy::{BgColorSimpleColorAttribute, BorderUnsignedIntegerAttribute};
pub use legacy::{ColSpanUnsignedIntegerAttribute, IntegerAttribute, LengthAttribute};
pub use legacy::{SimpleColorAttribute, SizeIntegerAttribute, UnsignedIntegerAttribute};
pub use legacy::{WidthLengthAttribute};
pub use font_face::{Source, LocalSource, UrlSource_};
mod stylesheets;

View file

@ -1316,6 +1316,34 @@ pub mod longhands {
${single_keyword("table-layout", "auto fixed")}
<%self:single_component_value name="-servo-column-span">
// The handling of this property is not well-specified by INTRINSIC, but its presence is
// assumed. HTML5 14.3.9 specifies that the `colspan` attribute is to be a nonnegative
// integer.
pub use super::computed_as_specified as to_computed_value;
pub mod computed_value {
pub type T = u32;
}
pub type SpecifiedValue = computed_value::T;
#[inline]
pub fn get_initial_value() -> computed_value::T {
1
}
pub fn from_component_value(input: &ComponentValue, _: &Url) -> Result<SpecifiedValue,()> {
match input {
&Number(ref value) => {
match value.int_value {
None => Err(()),
Some(n) => Ok(n as SpecifiedValue),
}
}
_ => Err(()),
}
}
</%self:single_component_value>
// CSS 2.1, Section 18 - User interface

View file

@ -802,8 +802,8 @@ pub fn common_style_affecting_attributes() -> [CommonStyleAffectingAttributeInfo
/// Attributes that, if present, disable style sharing. All legacy HTML attributes must be in
/// either this list or `common_style_affecting_attributes`. See the comment in
/// `synthesize_presentational_hints_for_legacy_attributes`.
pub fn rare_style_affecting_attributes() -> [Atom, ..2] {
[ atom!("bgcolor"), atom!("border") ]
pub fn rare_style_affecting_attributes() -> [Atom, ..3] {
[ atom!("bgcolor"), atom!("border"), atom!("colspan") ]
}
/// Determines whether the given element matches the given single selector.

View file

@ -209,5 +209,6 @@ fragment=top != ../html/acid2.html acid2_ref.html
== box_shadow_inset_parsing_a.html box_shadow_inset_parsing_ref.html
!= list_style_type_a.html list_style_type_ref.html
== list_style_position_a.html list_style_position_ref.html
== table_colspan_simple_a.html table_colspan_simple_ref.html
== legacy_td_bgcolor_attribute_a.html legacy_td_bgcolor_attribute_ref.html
== legacy_table_border_attribute_a.html legacy_table_border_attribute_ref.html

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<style>
td[colspan="2"] {
background-color: blue;
color: white;
}
td[colspan="3"] {
background-color: green;
color: white;
}
</style>
<body>
<table border=0 cellspacing=0 cellpadding=0>
<tr><td width=100>&nbsp;</td><td width=100>&nbsp;</td><td width=100>&nbsp;</td></tr>
<tr><td colspan=2>&nbsp;</td><td>&nbsp;</td></tr>
<tr><td>&nbsp;<td colspan=2>&nbsp;</td></tr>
<tr><td colspan=3>&nbsp;</td></tr>
</table>
</body>
</html>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<style>
td.two {
background-color: blue;
color: white;
}
td.three {
background-color: green;
color: white;
}
</style>
<body>
<table border=0 cellspacing=0 cellpadding=0>
<tr><td width=100>&nbsp;</td><td width=100>&nbsp;</td><td width=100>&nbsp;</td></tr>
<tr><td class=two>&nbsp;</td><td class=two></td><td>&nbsp;</td></tr>
<tr><td>&nbsp;<td class=two>&nbsp;</td><td class=two></td></tr>
<tr><td class=three>&nbsp;</td><td class=three></td><td class=three></td></tr>
</table>
</body>
</html>