Register DOM prototype members only when the prototype is used - #117
Merged
Conversation
Indexing a DOM collection from script - `document.getElementsByTagName('p')[0]`,
`element.classList[0]`, and everything jQuery builds on top of them - throws a
`TargetInvocationException` wrapping `EntryPointNotFoundException`.
`IHtmlCollection<T>`, `ITokenList` and `IStringList` declare their numeric
indexer as an explicit re-implementation of `IReadOnlyList<T>`'s indexer. A
member declared that way on an interface is private and abstract, so the
`MethodInfo` obtained from the interface's `PropertyInfo` cannot be invoked: the
implementation lives in a different slot and the runtime has no entry point for
the one we hand it.
Resolve the indexer accessor once, when the prototype is built, against the type
the prototype belongs to, and invoke that. Public accessors - the common case,
including every string indexer and the numeric indexers of `INodeList`,
`INamedNodeMap`, `IStyleSheetList` and friends - are used unchanged.
This is what makes the six currently failing tests in `ScriptingTests` and
`JqueryTests` fail; they pass again with this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0179sA2T7HuRfRfSc2JirFik
A prototype is created for every DOM type an assembly exposes, and its constructor reflects over the whole type tree of that type right away - properties, methods, events, extension methods, on every interface and base class. A document that touches a dozen DOM types still pays for all of them before the first statement runs. `ObjectInstance` already has the hook for this: `Initialize` is called the first time an object is asked for a property, and Jint marks the instance initialized before calling it, so the registration can use the object normally. Move the constructor body there verbatim - same order, same prototype link established last, so member registration still sees exactly the properties it saw before. Two places needed adjusting so that laziness actually holds: * the prototype link is now set during initialization, so reading it has to initialize too - `Object.getPrototypeOf` and `instanceof` reach it without going through a property lookup; * every exposed type gets a constructor, and the constructor writes `constructor` onto its prototype, which would initialize all of them. That write is deferred and applied at the end of initialization, where the old code performed it. The test suite drops from ~20s to ~3s, which is the same work disappearing: every test builds at least one document. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0179sA2T7HuRfRfSc2JirFik
This was referenced Jul 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A prototype is created for every DOM type an assembly exposes - around 170 of them for AngleSharp alone - and its constructor reflects over the whole type tree of that type right away: properties, methods, events and extension methods, on every interface and base class, allocating a
ClrFunctionpair per member. A document that touches a dozen DOM types still pays for all of them before the first statement runs.Change
ObjectInstance.Initializeis exactly the hook for this: Jint calls it the first time an object is asked for a property, and marks the instance initialized before calling it, so the registration can use the object normally.The constructor body moves there verbatim - same order, prototype link still established last, so member registration still sees exactly the properties it saw before (
SetMethodandSetExtensionMethodsprobeHasPropertywhile the prototype chain is still the default one).Two places needed adjusting so that laziness actually holds:
Object.getPrototypeOfandinstanceofreach it without going through a property lookup;constructoronto its prototype, which would initialize all of them again. That write is deferred and applied at the end of initialization, exactly where the old code performed it.Effect
The test suite drops from ~20s to ~3s, which is the same work disappearing - every test builds at least one document. I have not put a number on a single document; that is the honest shape of the measurement I have.
Verification
Behaviour is meant to be identical, so I checked the structure rather than a handful of properties: for ten different DOM object kinds (document, element, collection, token list, named node map, node list, window,
DOMParser, ...) I dumped every prototype in the chain with its full own-property name set and itsSymbol.toStringTag, plus the global object's own property names. The dump is byte-identical todevel.Two tests added to
DomTestsfor the parts that are easy to get wrong: the prototype chain of an element is complete and correctly tagged, andHTMLDivElement.prototype.constructor === HTMLDivElement(the deferred write).net462,net472andnet8.0: 124 passed, 0 failed.🤖 Generated with Claude Code
https://claude.ai/code/session_0179sA2T7HuRfRfSc2JirFik