fix(stealth): bind iframe contentWindow proxy methods to the real window
The srcdoc-iframe contentWindow Proxy returned native window methods unbound, so iframe.contentWindow.getComputedStyle()/addEventListener()/ setTimeout() ran with the Proxy as `this` and threw "Illegal invocation" on any page that uses a srcdoc iframe under --launch (FullLaunch). The sibling matchMedia proxy already bound its methods; this one did not. Wrap each function in an apply/construct trap that swaps the Proxy receiver for the real window while passing .prototype/.name/.toString/ identity straight through (a plain .bind() drops .prototype and breaks instanceof/constructors). Cached in a WeakMap for stable identity. Verified before/after on a launched stealth browser: getComputedStyle, addEventListener, setTimeout all OK; .prototype preserved.
This commit is contained in:
@@ -308,12 +308,39 @@ const __abRedefineNavProto = (name, getterImpl) => {
|
|||||||
try {
|
try {
|
||||||
if (iframe.contentWindow) return;
|
if (iframe.contentWindow) return;
|
||||||
} catch {}
|
} catch {}
|
||||||
|
// Native window methods are bound to the real Window via an internal slot;
|
||||||
|
// calling them with the Proxy as `this` throws "Illegal invocation". Wrap
|
||||||
|
// each function in an apply/construct trap that swaps the Proxy receiver for
|
||||||
|
// the real window, while passing `.prototype`/`.name`/`.toString`/identity
|
||||||
|
// straight through (a plain `.bind()` would drop `.prototype` and break
|
||||||
|
// `instanceof`). Cached so repeated reads return the same function.
|
||||||
|
const fnProxyCache = new WeakMap();
|
||||||
|
const bindToRealWindow = (fn) => {
|
||||||
|
let wrapped = fnProxyCache.get(fn);
|
||||||
|
if (wrapped) return wrapped;
|
||||||
|
try {
|
||||||
|
wrapped = new Proxy(fn, {
|
||||||
|
apply(target, thisArg, args) {
|
||||||
|
return Reflect.apply(target, thisArg === proxy ? window : thisArg, args);
|
||||||
|
},
|
||||||
|
construct(target, args, newTarget) {
|
||||||
|
return Reflect.construct(target, args, newTarget);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
wrapped = fn;
|
||||||
|
}
|
||||||
|
fnProxyCache.set(fn, wrapped);
|
||||||
|
return wrapped;
|
||||||
|
};
|
||||||
const proxy = new Proxy(window, {
|
const proxy = new Proxy(window, {
|
||||||
get(target, key) {
|
get(target, key) {
|
||||||
if (key === 'self') return proxy;
|
if (key === 'self') return proxy;
|
||||||
if (key === 'frameElement') return iframe;
|
if (key === 'frameElement') return iframe;
|
||||||
if (key === '0') return undefined;
|
if (key === '0') return undefined;
|
||||||
return Reflect.get(target, key, target);
|
const value = Reflect.get(target, key, target);
|
||||||
|
if (typeof value === 'function') return bindToRealWindow(value);
|
||||||
|
return value;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
iframeProxyMap.set(iframe, proxy);
|
iframeProxyMap.set(iframe, proxy);
|
||||||
|
|||||||
Reference in New Issue
Block a user