Skip to content

Map cellspacing / cellpadding attributes as presentational hints#503

Open
mitsuru wants to merge 1 commit into
DioxusLabs:mainfrom
mitsuru:fix-cellspacing-cellpadding-mapping
Open

Map cellspacing / cellpadding attributes as presentational hints#503
mitsuru wants to merge 1 commit into
DioxusLabs:mainfrom
mitsuru:fix-cellspacing-cellpadding-mapping

Conversation

@mitsuru

@mitsuru mitsuru commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the mapping defined in the HTML Rendering spec's Tables
section
for
the legacy cellspacing and cellpadding table attributes as CSS
presentational hints, so that <table cellspacing="0"> /
<table cellpadding="0"> actually zero out the UA sheet's border-spacing: 2px
and td { padding: 1px } defaults. Without this, any WPT (or real page) that
relies on the boilerplate cellspacing="0" cellpadding="0" to reach a clean
starting geometry renders with UA padding still applied.

Approach

  • cellspacing — attribute on <table> → push PropertyDeclaration::BorderSpacing
    from the element's own attribute iteration (same shape as the existing
    align / width / height / bgcolor / hidden hints).
  • cellpadding — the attribute lives on <table> but the effect is on
    descendant <td>/<th> cells. Chrome and Firefox express this via a UA-sheet
    descendant selector; Stylo doesn't expose that surface to embedders, so we
    walk up from each cell to the first ancestor <table> and read its
    cellpadding attribute at presentational-hint time.

Both attributes are parsed per the spec's rules for parsing non-negative
integers

(approximated with str::parse::<u32>), matching Blink/Gecko's
ParseHTMLNonNegativeInteger semantics for the common case. A stricter,
whitespace-trimming HTML-integer parser could be shared across
colspan / rowspan / new hints in a follow-up.

Verification

WPT css/css-tables (339 tests, 205 run) on main@11279518:

PASS FAIL
main 64 141
this PR 67 138

Specifically fixes:

  • colspan-001.html — FAIL → PASS (5/5 subtests)
  • colspan-002.html — FAIL → PASS (5/5 subtests)
  • colspan-003.html — FAIL → PASS (5/5 subtests)

No regressions in the suite.

Follow-ups (out of scope)

  • Shared parse_html_non_negative_integer helper (would also tighten
    colspan / rowspan in layout/table.rs, currently .parse().ok()
    without whitespace trimming or + prefix handling).
  • Other pixel-length presentational hints not yet implemented: <table border>,
    <img hspace> / <vspace>, <hr size>, <body/iframe marginwidth/height>.

…al hints

Per HTML5 §14.3 (rendering: tables) these attributes map to:

- `<table cellspacing="N">` → `border-spacing: Npx`
- `<table cellpadding="N">` → `padding: Npx` on descendant `<td>` / `<th>`

Blitz's presentational-hint synthesizer already covers `align`, `width`,
`height`, `bgcolor`, `hidden` on the relevant elements but not these two,
so tests that rely on `cellspacing="0"` / `cellpadding="0"` to zero out
the UA defaults (`table { border-spacing: 2px }` at `default.css` L314,
`td { padding: 1px }` at L546) render with the UA values still applied.

For `cellspacing`, push `PropertyDeclaration::BorderSpacing` when the
attribute appears on `<table>`. For `cellpadding`, walk up from each
`<td>` / `<th>` to the first ancestor `<table>` and, if it has a
`cellpadding` attribute, push `Padding{Top,Right,Bottom,Left}` on the
cell. The walk-up mirrors Chrome/Firefox's UA-sheet descendant selector
(`table[cellpadding] td, table[cellpadding] th { padding: attr(cellpadding px) }`),
which Stylo doesn't currently expose to embedders.

WPT css/css-tables (339 tests, 205 run): 64 → 67 PASS on `main@1127951`
(v0.3.0-beta.1) with this patch applied:

- colspan-001.html   FAIL → PASS  (5/5 subtests)
- colspan-002.html   FAIL → PASS  (5/5 subtests)
- colspan-003.html   FAIL → PASS  (5/5 subtests)

Claude-Session: https://claude.ai/code/session_01YHhheUghdrjENLNdfiMUV9

@nicoburns nicoburns left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cellspacing makes sense to me.

I don't like the tree walking in the cellpadding implementation. I think we should avoid that if at all possible.

Chrome and Firefox express this via a UA-sheet descendant selector; Stylo doesn't expose that surface to embedders

This assertion seems suspicious to me:

  1. Stylo does expose UA stylesheets. And we already have one in Blitz (default.css)
  2. Firefox uses Stylo! So if it didn't expose it, I'm not sure how Firefox would do this

@mitsuru

mitsuru commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@nicoburns
Dug into this — thanks for the pushback. I was partially wrong about Firefox
but the tree walk itself turns out to be the design the canonical Stylo
embedder uses.

The strongest evidence is Servo itself. Servo caches the parsed
cellpadding on the HTMLTableElement from attribute_mutated
(htmltableelement.rs#L540-L546),
and cells read it in synthesize_presentational_hints_for_legacy_attributes
(element.rs#L1470-L1483)
via a get_table() walk-up that hops cell → row → (optional section) →
table
(htmltablecellelement.rs#L138-L147):

if let Some(cellpadding) = self
    .downcast::<HTMLTableCellElement>()
    .and_then(|this| this.get_table())
    .and_then(|table| table.get_cellpadding())
{
    let cellpadding = NonNegative(specified::LengthPercentage::Length(
        specified::NoCalcLength::from_px(cellpadding as f32),
    ));
    push(PropertyDeclaration::PaddingTop(cellpadding.clone()));
    push(PropertyDeclaration::PaddingLeft(cellpadding.clone()));
    push(PropertyDeclaration::PaddingBottom(cellpadding.clone()));
    push(PropertyDeclaration::PaddingRight(cellpadding));
}

That's structurally identical to this PR — walk up from the cell to the
ancestor table, pull cellpadding, push four PaddingXxx declarations.
Gecko does the same thing (HTMLTableElement::BuildInheritedAttributes
parses cellpadding on AfterSetAttr and caches it on the table;
HTMLTableCellElement::GetMappedAttributesInheritedFromTable walks up to
grab that block).

This convergence is CSS-driven: attr() only reads own-element attributes,
so there's no pure-CSS way to express "padding on td/th = the ancestor
<table>'s cellpadding value". That's the constraint that pushed this PR
to walk up in the first place, and it's why every embedder ends up in the
same shape — Blink inherited HTMLTableElement::AdditionalCellStyle +
HTMLTableCellElement::AdditionalPresentationAttributeStyle from WebKit,
and Servo (an independent Rust reimplementation of the web platform)
landed on the same design. The piece all three do that this PR doesn't is
cache the parsed value on the table, so the walk-up avoids a per-cell
string parse. I spiked two angles for what to do about that:

Spike A — CSS custom-property inheritance
(main...spike/cellpadding-custom-property).
<table> pushes --blitz-cellpadding: Npx (parsed via
parse_style_attribute in the mutator, cached as
Arc<Locked<PropertyDeclarationBlock>>); default.css reads
padding: var(--blitz-cellpadding, 1px) on td, th. Cells never walk
because Stylo's custom-property cascade does it. +91/-2 across 4 files.
Introduces a few patterns new to Blitz: pushing a custom-property
declaration from Rust, var() in default.css, and a --blitz-*
UA-internal naming convention we'd want to define.

Spike B — cached walk-up (matches Servo)
(main...spike/cellpadding-approaches).
Option<u32> field on ElementData, populated on create_element /
set_attribute / clear_attribute. Cells read the cache instead of
re-parsing. +83 across 3 files. No new patterns — same shape as how
style_attribute already flows.

Both pass the same 67 WPT css/css-tables tests (+3 vs main, including the
3 colspan-* tests this PR is targeting).

I lean toward B: it's the smaller change, matches Servo's design
exactly, and the --blitz-* custom-property naming convention feels like a
separate design decision worth deferring. If you'd rather introduce the
custom-property route (and we settle on a convention now — I'd suggest
matching Blink's -internal-* prefix pattern), A is happy to go too. Let
me know which and I'll push.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants