Skip to content

fix: native functions read through window run with the global as receiver - #355

Open
jonasporto wants to merge 1 commit into
WebReflection:mainfrom
jonasporto:fix/window-native-functions
Open

jonasporto wants to merge 1 commit into
WebReflection:mainfrom
jonasporto:fix/window-native-functions

Conversation

@jonasporto

Copy link
Copy Markdown

Fixes #345

document.defaultView is a Proxy over globalThis. A native function read through it (window.setTimeout, window.queueMicrotask, window.requestAnimationFrame) is called with the proxy as receiver, and browsers reject that with TypeError: Illegal invocation. It shows up with linkedom/worker: d3-timer captures window.requestAnimationFrame.bind(window) at load time, so any d3 transition created while linkedom's window is the global throws. Still the case on 0.18.14 and main (e2ee208).

Change

In the proxy's get, a value that comes from globalThis and is a function without a prototype (a native that cannot be constructed) is returned bound to globalThis, once per function (WeakMap), so window.setTimeout === window.setTimeout. Constructors have a prototype and are returned as they are: new window.URL(...), instanceof and identity are unchanged. Values from the globals argument and linkedom's own exports are untouched. Proxy has no prototype and gets bound; a bound function keeps [[Construct]], so new window.Proxy({}, {}) still works.

cjs/interface/document.js is regenerated with npm run cjs. The test in test/html/document.js sets a method (no prototype) on globalThis and checks it runs with globalThis as receiver through window, that the bound function is cached, and that window.URL keeps its identity. In Node the timer functions are plain functions with a prototype, so they are not bound there; only real natives are. npm test passes with the same coverage as main.

In Chrome

The issue's test case, run in Chrome 156 (headless) against a worker.js built from this branch with rollup -c rollup/es.config.js, beside the bundle committed on main:

case main bundle this branch
window.setTimeout(f) TypeError: Illegal invocation ok
window.queueMicrotask(f) TypeError: Illegal invocation ok
window.requestAnimationFrame(f) TypeError: Illegal invocation ok
window.requestAnimationFrame.bind(window)(f) TypeError: Illegal invocation ok
new window.URL(...), window.URL === URL, new window.Proxy({}, {}), window.fetch(...) ok ok

worker.js is not rebuilt in this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

In a worker, native functions read through window throw "Illegal invocation" (window.setTimeout, window.requestAnimationFrame)

1 participant