* fix(browser): shadow PermissionStatus state instead of proxying it
Rebased onto main after #14685 landed in the same file. Both changes coexist:
#14685's Firefox gating and narrowed promptPerms are preserved untouched, and
this change replaces only the query-override implementation.
The previous Proxy bound every callable property, which broke three observable
things:
- onchange assignment threw "Illegal invocation" — the native setter received
the proxy rather than the branded target.
- Listeners were delivered with the real target and the NATIVE state, so on a
real permission change event.target.state read 'granted' while the returned
status still read 'prompt'.
- Method reads returned a new bound function each time, so name became
'bound addEventListener', toString lost its name, and identity was unstable.
Separately, the notifications state was captured once as a string, so an
already-returned status went stale after requestPermission updated it.
Shadow only 'state' on the genuine PermissionStatus with a lazy provider. The
object the site holds IS the real one, so identity, brand checks, method
fingerprints and native event delivery survive with nothing to keep in sync.
Verified against real Chromium (Chrome 151) rather than only the vm stand-in,
because the stand-in cannot show whether defineProperty succeeds on a branded
instance: the instance is extensible, the prototype 'state' accessor is
configurable, defineProperty succeeds, instanceof survives, addEventListener
keeps its native name and referential stability, and onchange assignment works.
New tests live in their own file rather than merged into anti-detection.test.ts,
whose harness diverged on main. The fallback test targets 'camera' because
#14685 narrowed the intercepted set, so a name outside it never reaches the
fallback.
* test(browser): cover intercepted PermissionStatus identity