webidl icon indicating copy to clipboard operation
webidl copied to clipboard

Should DOMException.[[Prototype]] be %Error%?

Open bathos opened this issue 3 years ago • 5 comments

The custom bindings for DOMException specify that DOMException.prototype.[[Prototype]] is %Error.prototype%. However it doesn’t also set DOMException.[[Prototype]] to %Error%, so instead it ends up with %Function.prototype%, which is what would occur for a “base” constructor.

This seems to make DOMException a pretty peculiar outlier. Among ECMAScript intrinsics, there are no cases where F.prototype.[[Prototype]] and F.[[Prototype]].prototype both exist yet point at different values. In Web IDL, this does also occur with [LegacyFactoryFunction] but in these cases, F.prototype.constructor isn’t actually F (and F does exhibit the normal pattern), so it’s relatively tough to run into the difference. In any case, scanning for it among globals suggests that DOMException’s constructor-prototype relationship is likely a true one-off:

console screenshot. the code in the screenshot follows as text

(code from image)
let i = 0;

for (let key of Object.getOwnPropertyNames(globalThis).sort()) {
  let { value } = Object.getOwnPropertyDescriptor(globalThis, key);

  if (typeof value === "function" && value.prototype) {
    let cp = Object.getPrototypeOf(value);
    let pp = Object.getPrototypeOf(value.prototype);
    let isDerived = pp && pp !== Object.prototype;

    if (isDerived && value.prototype.constructor !== value) {
      console.warn(`${ key } is weird, but in a [LegacyFactoryFunction] way`);
    } else if (isDerived && cp !== pp.constructor) {
      console.error(`${ key } is (uniquely) weird!`);
    } else {
      i++;
    }
  }
}

console.log(`[${ i } other objects looked like normal constructors]`);

// In Firefox, this printed:
// Audio is weird, but in a [LegacyFactoryFunction] way
// DOMException is (uniquely) weird!
// Image is weird, but in a [LegacyFactoryFunction] way
// Option is weird, but in a [LegacyFactoryFunction] way
// [595 other objects looked like normal constructors]

Among other quirks, this means Error[@@hasInstance] and Error.isPrototypeOf tell unexpectedly different stories about the relationship between Error and DOMException.

console.log(new EvalError instanceof Error);                      // true
console.log(Error.isPrototypeOf(EvalError));                      // true
console.log(Error.isPrototypeOf(new EvalError().constructor));    // true

console.log(new DOMException instanceof Error);                   // true
console.log(Error.isPrototypeOf(DOMException));                   // false
console.log(Error.isPrototypeOf(new DOMException().constructor)); // false

Is DOMException’s departure from the typical prototype inheritance pattern intentional? It seems like the sort of thing that could plausibly have been necessitated by a web compat issue, but I didn’t turn up any history on it, and it also seems plausible that it was an oversight and actually should be specified to inherit from %Error%.

bathos avatar Feb 25 '22 14:02 bathos

cc @evilpie @shvaikalesh @yuki3

annevk avatar Feb 25 '22 15:02 annevk

I don't think this is really intentional. It's just a historical artifact of how DOMException has custom bindings, and nobody ever implemented this. See https://github.com/whatwg/webidl/pull/378 and in particular the second comment there.

IMO we should change this and it shouldn't be risky.

domenic avatar Feb 25 '22 17:02 domenic

I think changing this should be relatively easy for Gecko.

evilpie avatar Feb 26 '22 15:02 evilpie

I think that we can ask V8 team to expose the v8::FunctionTemplate of Error and we can accommodate the right inheritance. No objection.

yuki3 avatar Feb 28 '22 11:02 yuki3