Playwright routes page.evaluate() to the execution context juggler names '',
and upstream puts that context on the page window itself. Everything the
automation evaluates is then reachable by page script: a detection script can
hook Function.prototype.toString, window.eval or Object.defineProperty and
watch the automation work. That is the leak this fork exists to avoid, and it
regressed silently in 03c1230 ("migrate Juggler modules from JSM to ESM"),
which replaced the juggler sources with upstream's -- a two-line change,
invisible in a diff full of module-format churn, and no test noticed, because
page.evaluate() keeps working either way. It just stops being hidden.
FrameTree.js gives the '' world a Cu.Sandbox over the page window instead, so
the automation runs in its own compartment. tests/patches/isolated-evaluate.py
pins the property so it cannot regress the same way twice.
The cost is that Xray vision hides the page's own JS state, so
page.evaluate('window.pageVar') reads undefined. Runtime.js therefore carries a
`mw:` escape hatch: a standalone re-implementation of Playwright's
utilityScript.evaluate compiled inside the page's real global, gated on the
`allowMainWorld` config key and off by default. It mirrors the wire format
exactly, since the client would otherwise misread a returned object such as
{a: 1} as a serialized array. Handles are refused rather than silently
mistranslated.
forceScopeAccess now selects a system-principal sandbox for that world rather
than installing an accessor on the page's Element.prototype (#628), so the flag
no longer advertises itself to anything that probes for shadowRootUnl.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>