Skip to content

Read the quirks mode without reflection - #268

Merged
FlorianRappl merged 2 commits into
AngleSharp:develfrom
michaelthyregod:feature/compat-mode-check
Nov 5, 2026
Merged

FlorianRappl merged 2 commits into
AngleSharp:develfrom
michaelthyregod:feature/compat-mode-check

Conversation

@michaelthyregod

Copy link
Copy Markdown
Contributor

The quirks mode checks in PresentationalHints.GetDeclarations (once per element) and in CssBuilder.CreateDeclarationWith read IDocument.CompatMode. In AngleSharp that getter maps the quirks mode to its string through reflection on every read (QuirksMode.GetCompatiblity(): GetField + GetCustomAttribute), which costs more than the rest of an element's cascade. In a profile of GetInnerText it was the largest single item.

This PR reads the mode directly where AngleSharp exposes it:

public static Boolean IsQuirksMode(IDocument? document) => document is IConstructableDocument constructable
    ? constructable.QuirksMode == QuirksMode.On
    : document?.CompatMode == "BackCompat";
  • Same result: only QuirksMode.On maps to "BackCompat"; Limited (limited quirks) maps to "CSS1Compat", as before. Other IDocument implementations still go through CompatMode.
  • I checked whether the string comparison was there on purpose (a workaround): both checks were added together in the presentational hints work, and CompatMode reads the same field, so I found no reason. If you'd rather stay on the public IDocument API, the alternative is caching the result per document - happy to switch.
  • New test LimitedQuirksModeHasNoneOfThem: a limited quirks document (XHTML 1.0 Transitional doctype) gets neither the quirks of the presentational hints nor the unit-less lengths of the parser. It fails when Limited is treated as quirks.

Benchmarks against devel (BenchmarkDotNet, 6 alternating rounds, pinned to the performance cores):

Change Allocations
DeepTree InnerText 100 / 500 −26.1% / −23.9% −5.6% / −5.8%
GetInnerText, real-world / selector-heavy −2.6% / −2.0% −0.5% / −0.8%
CssCascade RenderTree / ComputedStyle −2.0% / −1.1% −0.4% / −0.2%
Render(), selector-heavy document −5.0% / −2.5% (two runs) −0.7%
Render(), real-world document +10.8% / +6.3% (two runs) −0.5%

The Render() real-world number is a GC artifact, not extra work: the slow rounds are exactly those in which the candidate ran a gen2 collection (one every 8 renders); the baseline had none. The output and the element count are the same, and allocations are slightly lower. A probe without BenchmarkDotNet's forced collections between iterations measured −0.5% / −2.8% for it.

🤖 Generated with Claude Code

@FlorianRappl FlorianRappl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@FlorianRappl
FlorianRappl merged commit bb6515d into AngleSharp:devel Nov 5, 2026
5 checks passed
@michaelthyregod
michaelthyregod deleted the feature/compat-mode-check branch November 5, 2026 10:11
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