Skip to content

fix(html): make deeply nested input linear and stop nested-table row duplication - #43

Merged
chaliy merged 15 commits into
mainfrom
claude/project-thread-n3yks7
Oct 27, 2026
Merged

chaliy merged 15 commits into
mainfrom
claude/project-thread-n3yks7

Conversation

@chaliy

@chaliy chaliy commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Михайло · project thread

What changed

  • HTML parse time is now linear in input size regardless of nesting. A byte-level pre-scan caps element nesting at max_nesting_depth + 8 before html5ever sees the input; tags past the cap are dropped, text is kept. Non-content subtrees (script, style, template, form, ...) past the cap are dropped whole, and so are hidden elements (T9 rules on raw attributes) unless include_hidden_content(true).
  • Nested tables no longer duplicate rows: table rows come from the table's direct structure (thead/tbody/tr), not descendant selection.
  • Subtrees flattened at max_nesting_depth no longer leak <script>/<style>/<template> bodies or hidden content into output (pre-existing gaps, found while fixing).
  • New invariant S21 in the threat model; knowledge, CHANGELOG updated.

Why

markkit-bench (#22) showed html_deep_nesting as the outlier. html5ever's tree builder is O(depth) per start tag, so nesting cost is quadratic: a few MB of <div> is a CPU DoS, and nested tables grew output 4x per level.

Before / After

Release build:

Input Before After
64k nested <div> 12.6 s 2.4 ms
20k nested <table><tr><td> >60 s (timeout) 2.1 ms
20k nested <ul><li> 4.5 s 45 MB of it: 0.48 s
bench html_deep_nesting 2.9 ms, 147 KiB peak ~0.5 ms, 82 KiB peak

Other HTML bench cases: allocation counts unchanged.

Risk

  • Medium-low. Output only changes for documents nested deeper than max_nesting_depth (already flattened before) and for nested tables (previously duplicated rows).
  • Pre-scan tokenizer is approximate; covered by unit tests for comments, attributes with >, raw text, foreign content, optional end tags, hidden attributes, malformed tails.

Checklist

  • Tests added or updated (html_security::deep_*, nested_table_rows_are_not_duplicated, script_and_style_bodies_do_not_leak_past_max_depth, hidden_content_does_not_leak_past_max_depth, nesting unit tests)
  • Backward compatibility considered

🤖 Generated with Claude Code

chaliy added 14 commits October 26, 2026 23:35
Measures per-case latency, ops/s, throughput and heap allocations
(count, bytes, peak) plus process peak RSS over a generated
HTML/DOCX/XLSX/ODS corpus. just bench saves JSON + Markdown reports to
crates/markkit-bench/results/, mirroring bashkit-bench. Criterion moves
to just bench-criterion.
…duplication

html5ever's tree builder is quadratic in nesting depth: 687 KiB of nested
divs took 12.6 s and 292 KiB of unclosed nested tables ran past the 60 s
timeout. A pre-scan (html::nesting::cap_nesting) now drops tags nested past
max_nesting_depth + 8 before html5ever runs, keeping text.

Tables took rows via descendant queries, copying nested-table rows into
every enclosing table (4x output per level). Rows now come from direct
structure only.

Text flattened at max_nesting_depth leaked script/style/template bodies;
flat_text skips non-content elements. New invariant S17.
# Conflicts:
#	knowledge/security/threat-model.md
@chaliy chaliy self-assigned this Oct 27, 2026
@chaliy
chaliy merged commit 48715b5 into main Oct 27, 2026
13 checks passed
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.

1 participant