Skip to content

Swallowed-exception diagnostics, checkpoint wiring, and Jint toString() fix - #70

Merged
svenrog merged 9 commits into
masterfrom
feature/js-dom-swallowed-exception-message
Jul 9, 2026
Merged

svenrog merged 9 commits into
masterfrom
feature/js-dom-swallowed-exception-message

Conversation

@svenrog

@svenrog svenrog commented Jul 9, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Swallowed JS-render exceptions now report the exception message, not just the stack (Jint's error.stack is frames-only), routed through an unconditional __crawlerDiagnostic channel at Debug level.
  • Removed the Jint Map.keys()/values() iterator compat shim — Jint 4.11 fixed the underlying "Collection was modified" bug it patched.
  • Threaded ICheckpointStore through every crawler backend (AngleSharp, JS/Jint/V8, Playwright, Puppeteer) — --checkpoint previously only worked with the default static backend.
  • Ctrl+C now waits for in-flight requests to finish and the checkpoint to persist before the process exits, instead of racing process termination.
  • Added a no-op-but-spec-shaped Constraint Validation API and a FileList global to the JS DOM, fixing hydration crashes on frameworks that touch form-control validation or file inputs.
  • Fixed a Jint Function.prototype.toString() gap: Jint 4.11 defaults every ordinary script function's toString() to a hardcoded "[native code]" stub (unlike V8/real browsers), which made bundle-authored functions indistinguishable from the host DOM methods deliberately marked native for jQuery/Sizzle's native-code sniff. Scripts/modules are now tagged with their source text at parse time (JintFunctionSourceTagger, an external ConditionalWeakTable side table — not Node.UserData, which turned out to be Jint's own internal block-scope cache slot) so Options.Host.FunctionToStringHandler can return real source for everything else.

Not related to this PR: 3.0.0 is separately blocked on sebastienros/jint#2607 (a labeled-break-escaping-a-labeled-switch fix), which is required for full RSC rendering on the Jint backend. That PR is still open upstream as of this writing.

Test plan

  • dotnet build — clean, 0 warnings/errors
  • dotnet run --project tests/SimpleCrawler.Tests -c Release — full suite green: 252 total, 0 failed, 4 pre-existing unrelated skips
  • dotnet run --project tests/SimpleCrawler.Tests -c Release -- -filter "/*/*/JsDomRendererTests/*" — 90/90 green, including the previously-failing JsMode_HostMethodsReportNativeCode_UserFunctionsDoNot on both Jint and V8

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

svenrog and others added 9 commits July 9, 2026 06:43
V8's error.stack begins with the message line, but Jint's stack is frames
only, so reporting stack-or-message dropped the message entirely under Jint
— and the message is the one datum that identifies the failure (e.g. a
runtime TypeError, or a framework's minified error number). Emit the message
first, then the stack only when it doesn't already repeat it.

This surfaced a pre-existing engine gap invisible until now: two live
Next.js App Router RSC sites that render fully on V8 fail on Jint with
"Cannot convert undefined or null to object" thrown from React's Flight
deserialization — an expression that is a valid object on V8 is null on Jint,
so an ordinary destructure/spread of it throws only there. V8 is the default
engine and renders both sites correctly; the guard protects the fallback.

Also remove the RSC feasibility investigation doc.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
As of 4.11 the shim that used to patch an issue with the Map-iterator is no longer needed, the shims prelude is therefore removed.
AbstractCrawler already supported checkpoint/resume, but the concrete
AngleSharp, JS (Jint/V8), Playwright, and Puppeteer constructors never
forwarded an ICheckpointStore parameter, so DI could only supply one to
the default static backend. --checkpoint now works regardless of backend.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Console.CancelKeyPress cancelled the crawl token but left e.Cancel
false, so .NET terminated the process right after the handler
returned -- racing the in-flight checkpoint write. The handler now
sets e.Cancel = true on the first Ctrl+C, lets the crawl unwind and
persist its checkpoint, and logs what's happening; a second Ctrl+C
falls through to the default (immediate) termination.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Frameworks grab a form-control ref and call setCustomValidity /
checkValidity / reportValidity during render, and some touch a file
input's FileList; neither existed on HTMLElement, so hydration threw.
Both are no-ops that report an always-valid/empty state, since no user
interaction happens in a single-pass crawl.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Jint 4.11 defaults toString() to a hardcoded "function name() { [native
code] }" stub for every ordinary script function, unlike V8/real
browsers which print real source. That made bundle-authored functions
indistinguishable from the host DOM methods browser/native.ts
deliberately marks as native for jQuery/Sizzle's native-code sniff,
failing JsMode_HostMethodsReportNativeCode_UserFunctionsDoNot on Jint.

Prepared scripts/modules are now tagged with their source text at parse
time (JintFunctionSourceTagger), and Options.Host.FunctionToStringHandler
slices the real text back out for any node without a marked-native
override. The source lives in a ConditionalWeakTable keyed by node
reference, not Node.UserData -- that slot is Jint's own internal
block-scope cache, and writing to it corrupted unrelated script
execution engine-wide.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@svenrog
svenrog merged commit d02ede1 into master Jul 9, 2026
1 check passed
@svenrog
svenrog deleted the feature/js-dom-swallowed-exception-message branch July 9, 2026 09:12
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