Skip to content

Drop whitespace-only text nodes that cannot render - #173

Open
zcorpan wants to merge 2 commits into
mainfrom
zcorpan/strip-whitespace-only-text-nodes
Open

zcorpan wants to merge 2 commits into
mainfrom
zcorpan/strip-whitespace-only-text-nodes

Conversation

@zcorpan

@zcorpan zcorpan commented Sep 4, 2026 •

Copy link
Copy Markdown
Member

The output carries about 64,000 whitespace-only text nodes, around 9% of all its nodes, almost all of them the source's own indentation between block-level boxes, where CSS white-space processing throws them away anyway. Skip those in the serializer.

A whitespace-only text node is dropped when its parent generates a block-level box and, on at least one side, the nearest rendered sibling is either absent or another block-level element: either way the whitespace ends up at the start or the end of a line box, where CSS removes it. Siblings that generate no box, and adjacent whitespace-only text nodes left behind by FirstPass() dropping elements, are skipped when looking for those neighbours. Everything else is left alone, in particular whitespace between inline elements, and whitespace in or next to a <pre>, which the developer's edition renders as an inline-block.

FirstPass() already drops the text children of elements that cannot contain palpable text, so whitespace in <ul>, <dl>, <table> and friends is gone before this runs. That leaves #head nav > div, which standard.css makes display: inline-block, as the only place where otherwise-droppable whitespace is significant.

Single-page build, highlighting on: whitespace-only text nodes 64,380 → 20,166, total nodes 723,692 → 679,478, file size 15,594,754 → 15,415,853 bytes.

Verified by comparing the client rects of every element and every text run, at 1280px and 375px viewport widths, against a build with unpatched Wattsi: identical on all 119 multipage and developer's edition pages and in the single-page build (659,311 rects), plus pixel-identical screenshots of the places where the style sheets deviate from the default rendering.

Part of whatwg/html#12782.

(Generated by Claude.)

@zcorpan

zcorpan commented Sep 4, 2026 •

Copy link
Copy Markdown
Member Author

(The numbers here are with --no-highlight in html-build.)

@foolip foolip 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 rules here are fairly elaborate. If you haven't already, can you try a pixel comparison of the whole spec with and without this to gain confidence that no nodes were dropped that did make a difference?

Once you have that assurance, maybe it's also possible to simplify and preserve whitespace more surgically, which would also allow removing even more whitespace nodes?

Comment thread src/wattsi.pas Outdated
// '.category-list li' and 'dl.triple dt, dl.triple dd' are 'display: inline',
// and '#head nav > div' is 'display: inline-block'.
kWhitespaceSensitiveClasses: array[0..1] of UTF8String = ('category-list', 'triple');
kWhitespaceSensitiveIDs: array[0..0] of UTF8String = ('head');

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.

This is <header class="head with-buttons" id="head"> in the source. For one-off cases like this, can we add a data-preserve-whitespace attribute or something? With that, it's possible the rules can be much simpler because we can sprinkle a handful of those in the few problematic places.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Simplified without a new attribute.

@zcorpan

zcorpan commented Sep 14, 2026

Copy link
Copy Markdown
Member Author

The exception was only needed for #head.

  • dl.triple, those were removed in whatwg/html@80143ae6
  • ul.category-list, the generated content inserts a space where needed (', ').
  • dl.switch, there's no change to the rendering with whitespace trimmed unless there's a space right after the <dt> start tag, which there isn't in source.

@zcorpan

zcorpan commented Sep 14, 2026

Copy link
Copy Markdown
Member Author

Actually there's one instance of whitespace after <dt>: second item in https://html.spec.whatwg.org/#images-3

But it looks misaligned, so should be fixed.

The output carries about 64,000 whitespace-only text nodes, around 9% of all
its nodes, almost all of them the source's own indentation between block-level
boxes, where CSS white-space processing throws them away anyway. Skip those in
the serializer.

A whitespace-only text node is dropped when its parent generates a block-level
box and the nearest rendered sibling on each side is either absent (so the
whitespace is at the start or the end of a block, where CSS removes it) or
another block-level element (so it is not in an inline formatting context at
all). Siblings that generate no box, and adjacent whitespace-only text nodes
left behind by `FirstPass()` dropping elements, are skipped when looking for
those neighbours. Everything else is left alone, in particular whitespace
between inline elements and whitespace in or next to a `<pre>`, which the
developer's edition renders as an inline-block.

`FirstPass()` already drops the text children of elements that cannot contain
palpable text, so whitespace in `<ul>`, `<dl>`, `<table>` and friends is gone
before this runs. That leaves `#head nav > div`, which the style sheets make
`display: inline-block`, as the only place where otherwise-droppable
whitespace is significant.

Part of whatwg/html#12782.
A block-level box on either side of the whitespace, not just on both, puts it
at the start or the end of a line box, where CSS white-space processing removes
it. This covers the common shape of text followed by a nested list, and takes
another 2,000 nodes out of the single-page output.
@zcorpan

zcorpan commented Sep 14, 2026 •

Copy link
Copy Markdown
Member Author

But it looks misaligned, so should be fixed.

whatwg/html#12939
whatwg/whatwg.org#508

For #head nav > div, now a direct parent check instead of an ancestor walk. I also took pre out of the block-level list, since the developer's edition has code, pre { display: inline-block }.

The second commit does your other suggestion: a block-level box on either side, not just on both, is enough.

Verification: Client rects of every element and text run, on all 119 multipage and dev pages at 1280px and 375px, are identical to an unpatched build (see OP). As a control, removing one space between two <code>s on a page does change the digest.

zcorpan added a commit to whatwg/whatwg.org that referenced this pull request Sep 14, 2026
This avoids rendering whitespace between the marker and the text.

This also lets whatwg/wattsi#173 drop its special case for `dl.switch > dt`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants