Release 1.0.0 - #129
Open
FlorianRappl wants to merge 111 commits into
Open
Conversation
Serialize DocumentReadyState
Upgraded to Jint v3
Updated target frameworks
Added support for JS modules and importmaps
Added Image constructor support
* small cleanup to API usage * add missing license expression * treat warnings as errors to catch obsolete members
Upgrade to Jint 3.1.0
* enable deterministic build
Upgrade to NUKE 8
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
docs: add scripting integration guide
Fix html5test execution
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pendencies Fix vulnerable build dependencies
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix invoking JavaScript with C# DOM arguments
The release brings the host-object hooks the DOM proxies use in the next commit, and one change that applies without any code here: an object that overrides GetOwnProperty but not Get is now recognised as having ordinary read semantics, so a dotted DOM read probes the node once instead of twice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A node is asked three different things about a property name, and until now all three arrived at GetOwnProperty and got a PropertyDescriptor built for them - including the two that only ever throw it away. Jint 4.15 splits them up, and the node answers each directly: * TryGetOwnPropertyValue hands the value over, so an indexed read no longer allocates a descriptor for the engine to unwrap and drop. Returning false is an authoritative "no own property of that name", which is exactly what the discarded descriptor used to prove. * ProbeOwnProperty answers existence and enumerability, which is what "in", hasOwnProperty, Object.assign, spread and JSON.stringify actually want. An indexed entry no longer has to be converted into a JsValue to be counted. The three share one indexer lookup so that they cannot disagree - the answer one gives has to be the answer the others would give at the same instant, and a Debug build of Jint checks that on every read. The lookup itself now takes the property key rather than its string form, and two things fall out of that: * a key is no longer turned into a String before the type is known to have an indexer at all, and most DOM types have none. A symbol used to compose "Symbol(...)" on every probe, and library code probes Symbol.toStringTag constantly - 83.7 to 2.9 bytes per read; * the string indexer's HasProperty check is handed the key it was given instead of building a JsString for it. That is on the path of every read of an ordinary member of an indexed collection - the length a loop tests - and it is 64 bytes a read, which 4.15 exposes because a host prototype is no longer eligible for the engine's prototype cache. Behaviour is unchanged. The eight tests added to DomTests pass on devel with 4.14.0 as well; they pin the agreement between the three answers, which is the thing this commit could get wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A node list, an HTML collection, a token list and a named node map are all the
same thing to script - a live, read-only, indexed view - and until now every read
of one went through the general node proxy: parse the key, reflect the numeric
indexer off the prototype, invoke it, wrap the result in a descriptor. An index
past the end was signalled by a CLR exception, so a bounds test cost about a
kilobyte.
Jint 4.15 has a base class for exactly this shape. A collection supplies the
members WebIDL says such a thing has - Length, TryGetIndex, and a containment
test - and the engine derives the whole property model from them. Nothing is
cached, so the view stays live.
Containment is the length comparison and nothing else, so every question that
only wants a yes or a no - "in", hasOwnProperty, the key enumerations, the hole
test the Array.prototype generics run per element - stops projecting an element,
wrapping it in a JS value and looking it up in the identity cache just to drop it
again.
Length and the indexed getter are the one place in this binding where reflection
was too slow to leave alone: with the descriptor gone, a MethodInfo.Invoke and its
object[] would have been all that was left of an indexed read. They are compiled
once per type instead. What carries the DOM attributes and what can be called are
not always the same member - IHtmlCollection<T> re-implements
"T IReadOnlyList<T>.this[Int32]" explicitly, and that one is private and abstract
- so the attributed declaration identifies the collection while the public method
of the same name on the concrete class is what runs.
Deciding which proxy to build is a property of the type - an indexed getter plus
a length, the same pair SetIndexer looks for - so it is resolved once per type
for the whole process and never repeats.
There was one proxy class for every DOM object and there are now two, which
cannot share a base: they agree on IDomProxy instead, which is what every caller
outside the proxies actually wanted. Named entries - document.forms.login,
el.attributes.title - are not part of the array-like model and stay on the string
indexer, with indices and length delegated to the base as that base requires. A
named entry whose indexer has no setter is described by a plain value now rather
than by an accessor pair: with nothing to forward a write to, the pair only cost
two function objects per read.
Measured, allocation per operation, net8.0, loop baseline subtracted:
| operation | devel + 4.14.0 | this branch |
| -------------------------------- | -------------- | ----------- |
| list[0] | 326.6 B | 132.1 B |
| list[999] out of range | 1086.1 B | 0.5 B |
| attrs['id'] | 1021.6 B | 278.4 B |
| list.length | 51.9 B | 0.0 B |
| 0 in list | 326.6 B | 0.0 B |
| list.hasOwnProperty(0) | 472.0 B | 0.5 B |
| Array.prototype.forEach.call(..) | 1724.5 B | 568.2 B |
What script sees changes in four ways, all toward what a browser does: for...of,
spread, Array.from and destructuring work on a collection; Object.keys and
for...in report the indices; delete c[0] and defineProperty on an index are
refused; and Array.isArray stays false with the prototype chain untouched, so
instanceof and Symbol.toStringTag are exactly as before.
One deviation is worth stating plainly: "length" becomes an own property of the
collection rather than an inherited one, so hasOwnProperty('length') answers true
where a browser answers false. It is the model the base class defines, it is what
an Array reports too, and reads, "in", enumerability and the value are unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Read the DOM through the host object surface Jint 4.15 adds
… the package needs Two follow-ups to #130. The collection proxy's GetOwnProperty asked the string indexer about a name the array-like base had already answered: an index past the end is an authoritative miss - the value and existence hooks commit to it, the engine trusts them and does not ask again - so an element whose id happens to be numeric surfaced as a descriptor for a key that reads as undefined, answers false to "in" and hasOwnProperty, and never enumerates. WebIDL resolves the same collision the same way: an object supporting indexed properties never serves an array-index name from its named getter. The named lookup now steps aside for canonical array-index keys, classified exactly as the engine classifies them. The package manifest still declared Jint [4.0.0,5.0.0), but the assembly now derives from a base class and overrides hooks that exist only in 4.15.0, and NuGet resolves the lowest version a range admits - a consumer taking the package defaults got an engine the types in it cannot load against. The floor is now the version the code is actually written to. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016uV6H9cTntzsoKiaJRBn4f
Keep array-index keys out of the named getter, and declare the Jint the package needs
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.
Types of Changes
Prerequisites
Please make sure you can check the following two boxes:
Contribution Type
What types of changes does your code introduce? Put an
xin all the boxes that apply:Description
Note that I mentioned "Breaking change" even though I don't think anything will break when updating to this release. In general people will be amazed how much is now working... at least that's my hope.
Shoutout to everyone who made this possible. Years ago I thought this project is a dead-end and finished - now I think this might be the foundation for something really great. Without people like @lahma, @arekdygas, and @tomvanenckevort the project could not have been brought to this level. Much appreciated everyone!
(PR is there to be merged once final tests and cosmetics are in place - if you see anything or want to improve it just open a PR to
devel)