I want to interceptor the window.onerror
which user defined, and get the error parameters info to do some analysis. The following is a part of the interceptor code:
const userError = window.onerror;
delete window.onerror;
const errorFn = (...args) => {
// some code to collect args info
if (userError) {
userError.apply(window, args)
}
}
Object.defineProperty(window, 'onerror', {
get() {
console.log('ONERROR GETTER');
return errorFn
},
set() {
// ... don't care
}
})
However, the getter would not execute when an error occurs, such as calling window.abcdefg() which does not actually exist in window. Additionally, it does not print the identifier "ONERROR GETTER", which seems strange to me 😅
I have shared my question and requested help. It is currently understood that the DOM is not a standard JavaScript object, and the behavior of onerror is not defined in the specification.
Now, I am sharing my question and hoping to receive more opinions.
2
Answers
For anyone who needs to interceptor the user defined
window.onerror
, following is a solution:TL;DR: Why do you need
Object.defineProperty()
? Just wrap your "interceptor" around the user’s event handler, and reassign it like so:I’m going to give a highly speculative answer based on my own tests because I couldn’t find much information from browser source code, MDN documentation, or the HTML spec:
So as you probably know,
onerror
is an attribute event listener, which enables you to conveniently get and set an event handler for "error" events, instead of usingaddEventListener()
.But did you know that the
onerror
property ofwindow
is by default an accessor property, meaning its descriptor defines getters and setters rather than a straightforward value. We can confirm this by inspectingObject.getOwnPropertyDescriptor(window, "onerror")
, and this seems to be consistent across browsers.So, this actually might hint towards how
window.onerror
implements attaching the event listener towindow
. That is, theset()
accessor ofonerror
effectively just callsaddEventListener()
on the new value, andremoveEventListener()
on the old value, but in native code. However, we can easily reimplement this behavior in JavaScript like so:So what does this entail? Well it means that when the window receives an "error" event (or any other attribute event), the browser isn’t performing some special case to call
window.onerror()
. Instead, the browser likely uses the same logic as running event handlers attached directly withaddEventListener()
. In other words, the browser never actually accesseswindow.onerror
, and that’s why yourget()
accessor doesn’t execute and"ONERROR GETTER"
isn’t printed.This also explains how attribute event listeners respect definition order when multiple event listeners of the same type are defined; attribute event listeners are essentially just syntactic sugar for calling
addEventListener()
.As a result, if you’re overriding the descriptor with
Object.defineProperty()
, you will need to write something similar to the code above to preserve the semantics ofonerror
. However, see the TL;DR for how you can achieve what you probably want much more simply.