Hwaro::Utils::HtmlMinifier
Constants
HTML block-level elements. Whitespace adjacent to a block-level neighbour has no visual effect:
- between two block siblings the user agent renders them on separate lines anyway,
- leading whitespace inside a block parent is collapsed by the browser before the first inline child,
- trailing whitespace before a block close is collapsed
after the last inline child.
Anything not listed here is treated as inline, where whitespace
between siblings collapses to a single rendered space and must
therefore be kept as one literal space. Replaced elements that
default to display:inline (iframe, video, audio, canvas, embed,
object — and
picture, an inline wrapper around its<img>) are deliberately NOT listed: whitespace adjacent to them renders as a visible space and must be preserved.
Protected tags that render nothing (display:none; noscript while
scripting is on). They take no part in whitespace collapsing, so
the space beside one still separates the inline content around
it: <b>a</b> <script>…</script> <b>c</b> renders "a c", and
treating the script as block deleted that gap ("ac").
Classification of protected tags by default HTML display so the inter-token whitespace pass can treat sealed blocks as if they were their original tag.
Tags whose content must be preserved verbatim. Extracted into
placeholders before any whitespace pass and restored afterward.
code is included because inline-code snippets often carry
whitespace that authors expect to ship unchanged.
Order matters: a <style> body may legitimately contain the
literal string <script> (e.g. content: "<script>"), so
style is extracted first to remove that text from view
before the script pass runs.
Instance methods
Restore in a fixed-point loop: a protected block (e.g. a <style>
extracted before <script>) can be captured inside a later block, so
the first restore leaves an inner placeholder behind. Re-scan until no
token remains; a dangling index returns $0 unchanged. The original
termination argument — "restored content is author HTML, which cannot
contain a valid \x00-delimited token because NUL is illegal in author
input" — turned out to be false (NUL reaches the rendered page through
data-file values), so termination now rests on minify()'s NUL scrub
plus the hard pass cap below rather than on that invariant alone.
Public (not private) so the counterfeit-token guards below stay
testable: with minify's NUL scrub in front of it, no forged token can
reach this method through the public entry point any more, and a spec
that goes through minify therefore exercises neither guard — a
mutation that deletes both still passes. They are defence-in-depth for
any future caller, and dead guards nobody can test are how they rot.
Minify the given HTML.
Example: minify("
Hello
\n=> "Hello
"
Hello
minify("x\ny")
=> "x y"
Drop every NUL byte from a rendered document.
NUL is not valid in HTML text (parsers replace it with U+FFFD), and it
never appears in author input — but it does reach the rendered page
through a data-file value (data/x.json, data/x.yml), and its absence is
the ONLY thing that makes the \x00-delimited sentinels below
collision-proof: a forged \x00HW_HTML_PB_0\x00 arriving that way made
restore_sensitive_blocks substitute a preserved block into itself, the
string grew by one copy per pass, and the fixed point never converged —
hwaro build --minify hung forever.
Applied by the RENDER phase to every page, minified or not, so the two
build modes cannot disagree on page bytes: scrubbing only inside
minify made an optional whitespace pass silently delete a byte of
author-controlled content that a plain build emitted. Dropping (rather
than substituting U+FFFD) is deliberate — the byte carries no meaning
here and a replacement character in the page would be a worse surprise
than its absence.
Filtered byte-wise rather than with String#delete so that invalid
UTF-8 elsewhere in the page survives verbatim instead of being folded
to U+FFFD; NUL never appears inside a multi-byte sequence, so this is
safe.