Every variable was mapped when the engine was set up, once per evaluation,
whether or not the script ever looked at it - WritableContext did it in its
constructor for the ctx path, and Engine.SetValue did it per variable for
the non-context path. Some of those mappings are not cheap: a user variable
walks and groups every claim, a content data variable builds a wrapper. A
typical script reads a handful of the variables available to it.
Both paths now defer the mapping to the first read of the value, through
the two APIs Jint 4.15.3 added for exactly this:
- PropertyDescriptor.CreateLazy for the ctx object. Unlike a hand-written
CustomJsValue descriptor it drops the flag once the value exists, so the
descriptor rejoins the write inline cache instead of paying the
indirection for the rest of its life.
- Engine.Advanced.AddLazyGlobal for the non-context path. The options-time
AddLazyGlobal could not serve it - the variables are only known after the
engine has been built - and the descriptor a host could install itself is
declined by the global-identifier cache. The Advanced overload is
documented as being for exactly this case, and its factory may capture
engine-affine state.
In both cases the property itself is installed eagerly, so nothing about
the shape changes: key order, enumeration, `in`, Object.getOwnPropertyNames,
delete and the write-through to ScriptVars behave exactly as before, which
is what the tests pin - including a counting principal that proves the
mapping has not run for a variable the script never mentions, and has run
for one it reads. MapVariable reproduces Engine.SetValue's special case for
a CLR type so a deferred variable cannot project differently.
One edge is worth recording: Engine.SetValue writes through [[Set]] while
AddLazyGlobal replaces the descriptor, so a variable named after a
non-writable built-in global (undefined, NaN, Infinity) would now shadow it
where it was previously ignored. ScriptVars keys are domain names, so this
is not reachable in practice.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uV6H9cTntzsoKiaJRBn4f