- From: arai-a <notifications@github.com>
- Date: Mon, 30 Jun 2025 05:46:25 -0700
- To: whatwg/webidl <webidl@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <whatwg/webidl/issues/1497@github.com>
arai-a created an issue (whatwg/webidl#1497) ### What is the issue with the Web IDL Standard? The WebIDL spec requires the attribute setters to check the arguments count as a first step, in the step 4.1 below. https://webidl.spec.whatwg.org/#dfn-attribute-setter ``` The attribute setter is created as follows, given an attribute attribute, a namespace or interface target, and a realm realm: 4. Let steps be the following series of steps: 1. If no arguments were passed, then throw a TypeError. 2. Let V be the value of the first argument passed. 3. Let id be attribute’s identifier. 4. Let idlObject be null. 5. If attribute is a regular attribute: 1. Let jsValue be the this value, if it is not null or undefined, or realm’s global object otherwise. (This will subsequently cause a TypeError in a few steps, if the global object does not implement target and [LegacyLenientThis] is not specified.) 2. If jsValue is a platform object, then perform a security check, passing jsValue, id, and "setter". 3. Let validThis be true if jsValue implements target, or false otherwise. 4. If validThis is false and attribute was not specified with the [LegacyLenientThis] extended attribute, then throw a TypeError. 5. If attribute is declared with the [Replaceable] extended attribute, then: 1. Perform ? CreateDataPropertyOrThrow(jsValue, id, V). 2. Return undefined. 6. If validThis is false, then return undefined. ... ``` But no browser matches that behavior. Firefox first checks the `this` value, which is the step 4.5.4, and then checks the arguments count, for step 4.1, in most cases. Chrome also checks the `this` value first, and then in some case it checks the arguments count, and treats the passed value as `undefined` otherwise. Safari checks the `this` value first, and never checks the arguments count, but always treat it as `undefined` Then, while looking into [IDL for ECMAScript](https://github.com/tc39/proposal-idl/issues/7) ([doc](https://docs.google.com/document/d/1kj7VQ-LOfg-rq59vPEaJFmL6EJsKBwJ52AZzIGfdc_0/edit?tab=t.0#heading=h.cjhmzputw2zu)), the arguments count check at the step 4.1 becomes problematic, because ECMAScript built-ins don't perform that, and always treat such case as `undefined` is passed. Given that the behavior already mismatches between browsers, and also it's less likely the setter function is extracted and then called with no arguments in wild, it would be nice to drop the step, and instead make it so that `V` becomes `undefined` in such case, and then possibly reflect it to each browser. ---- Experiment results below: Environments: * Firefox Nightly 142.0a1 (2025-06-29) (aarch64) * Chrome Canary: 140.0.7269.0 (Official Build) canary (arm64) * Safari Technology Preview: Release 221 (WebKit 20622.1.15.19.2) ## Testcases A: basic cases 1 https://searchfox.org/mozilla-central/rev/adc939fe81358079709e3cf985d928863a19e272/dom/webidl/Animation.webidl#18,23 ``` interface Animation : EventTarget { ... attribute DOMString id; ``` ### Testcase A-1: invalid this + no arguments ``` Object.getOwnPropertyDescriptor(Animation.prototype, "id").set.call(undefined); Object.getOwnPropertyDescriptor(Animation.prototype, "id").set.call(null); Object.getOwnPropertyDescriptor(Animation.prototype, "id").set.call({}); Object.getOwnPropertyDescriptor(Animation.prototype, "id").set.call(new Image()); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: 'set id' called on an object that does not implement interface Animation.` | | Chrome | throws `TypeError: Illegal invocation` | | Safari | throws `TypeError: The Animation.id setter can only be used on instances of Animation` | All of the implementation throws an error for `this` value, in the step 4.5.4, and all browsers don't match the spec. ### Testcase A-2: invalid this + one argument ``` Object.getOwnPropertyDescriptor(Animation.prototype, "id").set.call(undefined, "a"); Object.getOwnPropertyDescriptor(Animation.prototype, "id").set.call({}, "a"); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: 'set id' called on an object that does not implement interface Animation.` | | Chrome | throws `TypeError: Illegal invocation` | | Safari | throws `TypeError: The Animation.id setter can only be used on instances of Animation` | The same error happens A-1, which means the arguments count isn't checked before step 4.5.4. ### Testcase A-3: valid this + no arguments ``` Object.getOwnPropertyDescriptor(Animation.prototype, "id").set.call(new Animation()); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: Animation attribute setter: At least 1 argument required, but only 0 passed` | | Chrome | returns `undefined` | | Safari | returns `undefined` | Only Firefox throws an error about the arguments count. (See B-3 for Chrome behavior) ### Testcase A-4: valid this + one argument ``` Object.getOwnPropertyDescriptor(Animation.prototype, "id").set.call(new Animation(), "a"); ``` | Browser | Result | |--|--| | Firefox | returns `undefined` | | Chrome | returns `undefined` | | Safari | returns `undefined` | ## Testcases B: basic cases 2 https://searchfox.org/mozilla-central/rev/adc939fe81358079709e3cf985d928863a19e272/dom/webidl/Document.webidl#119,136-137 ``` partial interface Document { ... [CEReactions, Pure, SetterThrows] attribute HTMLElement? body; ``` ### Testcase B-1: invalid this + no arguments ``` Object.getOwnPropertyDescriptor(Document.prototype, "body").set.call(undefined); Object.getOwnPropertyDescriptor(Document.prototype, "body").set.call(null); Object.getOwnPropertyDescriptor(Document.prototype, "body").set.call({}); Object.getOwnPropertyDescriptor(Document.prototype, "body").set.call(new Image()); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: 'set body' called on an object that does not implement interface Document.` | | Chrome | throws `TypeError: Illegal invocation` | | Safari | throws `TypeError: The Document.body setter can only be used on instances of Document` | ### Testcase B-2: invalid this + one argument ``` Object.getOwnPropertyDescriptor(Document.prototype, "body").set.call(undefined, "a"); Object.getOwnPropertyDescriptor(Document.prototype, "body").set.call({}, "a"); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: 'set body' called on an object that does not implement interface Document.` | | Chrome | throws `TypeError: Illegal invocation` | | Safari | throws `TypeError: The Document.body setter can only be used on instances of Document` | ### Testcase B-3: valid this + no arguments ``` Object.getOwnPropertyDescriptor(Document.prototype, "body").set.call(document); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: Document attribute setter: At least 1 argument required, but only 0 passed` | | Chrome | throws `TypeError: Failed to set the 'body' property on 'Document': 1 argument required, but only 0 present.` | | Safari | throws `HierarchyRequestError: The operation would yield an incorrect node tree.` | In contrast to the testcases A, Chrome also throws TypeError. Safari throws different error, because `undefined` cannot be set to body. ### Testcase B-4: valid this + one invalid argument ``` Object.getOwnPropertyDescriptor(Document.prototype, "body").set.call(document, "a"); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: Document.body setter: Value being assigned is not an object.` | | Chrome | throws `TypeError: Failed to set the 'body' property on 'Document': Failed to convert value to 'HTMLElement'.` | | Safari | throws `TypeError: The Document.body attribute must be an instance of HTMLElement` | In this case, all browsers throw an error for the argument type. ### Testcase B-5: valid this + one valid argument ``` Object.getOwnPropertyDescriptor(Document.prototype, "body").set.call(document, document.body); ``` | Browser | Result | |--|--| | Firefox | returns `undefined` | | Chrome | returns `undefined` | | Safari | returns `undefined` | ## Testcases C: [Replaceable] https://searchfox.org/mozilla-central/rev/adc939fe81358079709e3cf985d928863a19e272/dom/webidl/Window.webidl#197,210 ``` /*sealed*/ interface Window : EventTarget { ... [Replaceable, Throws] readonly attribute BarProp locationbar; ``` ### Testcase C-1 ``` Object.getOwnPropertyDescriptor(window, "locationbar").set.call(window); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: Window attribute setter: At least 1 argument required, but only 0 passed` | | Chrome | throws `TypeError: Failed to set the 'locationbar' property on 'Window': 1 argument required, but only 0 present.` | | Safari | returns `undefined` | Similar to B-3, Firefox and Chrome throws TypeError for the arguments count. ### Testcase C-2 ``` Object.getOwnPropertyDescriptor(window, "locationbar").set.call(window, 1); ``` | Browser | Result | |--|--| | Firefox | returns `undefined` | | Chrome | returns `undefined` | | Safari | returns `undefined` | ## Testcases D: [LegacyLenientThis] https://searchfox.org/mozilla-central/rev/adc939fe81358079709e3cf985d928863a19e272/dom/webidl/Document.webidl#119,185 ``` partial interface Document { ... [LegacyLenientThis] attribute EventHandler onreadystatechange; ``` ### Testcase D-1 ``` Object.getOwnPropertyDescriptor(Document.prototype, "onreadystatechange").set.call(undefined); Object.getOwnPropertyDescriptor(Document.prototype, "onreadystatechange").set.call(null); Object.getOwnPropertyDescriptor(Document.prototype, "onreadystatechange").set.call({}); Object.getOwnPropertyDescriptor(Document.prototype, "onreadystatechange").set.call(undefined, "foo"); Object.getOwnPropertyDescriptor(Document.prototype, "onreadystatechange").set.call({}, "foo"); ``` | Browser | Result | |--|--| | Firefox | returns `undefined` | | Chrome | returns `undefined` | | Safari | returns `undefined` | All browsers don't check the arguments count, and all browsers don't match the spec. ## Testcases E: [LegacyLenientSetter] https://searchfox.org/mozilla-central/rev/adc939fe81358079709e3cf985d928863a19e272/dom/webidl/Document.webidl#281,288-289 ``` partial interface Document { ... [LegacyLenientSetter, NeedsCallerType] readonly attribute boolean fullscreenEnabled; ``` ### Testcase E-1 ``` Object.getOwnPropertyDescriptor(Document.prototype, "fullscreenEnabled").set.call(undefined); Object.getOwnPropertyDescriptor(Document.prototype, "fullscreenEnabled").set.call(null); Object.getOwnPropertyDescriptor(Document.prototype, "fullscreenEnabled").set.call({}); Object.getOwnPropertyDescriptor(Document.prototype, "fullscreenEnabled").set.call({}, false); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: 'set fullscreenEnabled' called on an object that does not implement interface Document.` | | Chrome | throws `TypeError: Illegal invocation` | | Safari | throws `TypeError: The Document.fullscreenEnabled setter can only be used on instances of Document` | ### Testcase E-2 ``` Object.getOwnPropertyDescriptor(Document.prototype, "fullscreenEnabled").set.call(document); ``` | Browser | Result | |--|--| | Firefox | throws `Uncaught TypeError: Document attribute setter: At least 1 argument required, but only 0 passed` | | Chrome | returns `undefined` | | Safari | returns `undefined` | ### Testcase E-3 ``` Object.getOwnPropertyDescriptor(Document.prototype, "fullscreenEnabled").set.call(document, false); ``` | Browser | Result | |--|--| | Firefox | returns `undefined` | | Chrome | returns `undefined` | | Safari | returns `undefined` | ## Testcases F: [PutForwards] extended attribute https://searchfox.org/mozilla-central/rev/adc939fe81358079709e3cf985d928863a19e272/dom/webidl/HTMLAnchorElement.webidl#16,29-30 ``` interface HTMLAnchorElement : HTMLElement { ... [PutForwards=value] readonly attribute DOMTokenList relList; ``` ### Testcase F-1 ``` Object.getOwnPropertyDescriptor(HTMLAnchorElement.prototype, "relList").set.call(undefined); Object.getOwnPropertyDescriptor(HTMLAnchorElement.prototype, "relList").set.call(null); Object.getOwnPropertyDescriptor(HTMLAnchorElement.prototype, "relList").set.call({}); Object.getOwnPropertyDescriptor(HTMLAnchorElement.prototype, "relList").set.call({}, 10); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: 'set relList' called on an object that does not implement interface HTMLAnchorElement.` | | Chrome | throws `TypeError: Illegal invocation` | | Safari | throws `TypeError: The HTMLAnchorElement.relList setter can only be used on instances of HTMLAnchorElement` | ### Testcase F-2 ``` Object.getOwnPropertyDescriptor(HTMLAnchorElement.prototype, "relList").set.call(document.createElement("a")); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: HTMLAnchorElement attribute setter: At least 1 argument required, but only 0 passed` | | Chrome | throws `TypeError: Failed to set the 'relList' property on 'HTMLAnchorElement': 1 argument required, but only 0 present.` | | Safari | returns `undefined` | ### Testcase F-3 ``` Object.getOwnPropertyDescriptor(HTMLAnchorElement.prototype, "relList").set.call(document.createElement("a"), 10); ``` | Browser | Result | |--|--| | Firefox | returns `undefined` | | Chrome | returns `undefined` | | Safari | returns `undefined` | ## Testcases G: ToString for enum https://searchfox.org/mozilla-central/rev/adc939fe81358079709e3cf985d928863a19e272/dom/webidl/CanvasRenderingContext2D.webidl#308,312 ``` interface mixin CanvasPathDrawingStyles { ... attribute CanvasLineCap lineCap; // (default "butt") }; ... CanvasRenderingContext2D includes CanvasPathDrawingStyles; ``` ### Testcase G-1 ``` Object.getOwnPropertyDescriptor(CanvasRenderingContext2D.prototype, "lineCap").set.call(undefined, { get toString() { console.log("get"); return function() { console.log("call") }; }}); ``` | Browser | Result | |--|--| | Firefox | throws `TypeError: 'set lineCap' called on an object that does not implement interface CanvasRenderingContext2D.` | | Chrome | throws `TypeError: Illegal invocation` | | Safari | throws `TypeError: The CanvasRenderingContext2D.lineCap setter can only be used on instances of CanvasRenderingContext2D` | ### Testcase G-2 ``` Object.getOwnPropertyDescriptor(CanvasRenderingContext2D.prototype, "lineCap").set.call(document.createElement("canvas").getContext("2d"), { get toString() { console.log("get"); return function() { console.log("call") }; }}); ``` | Browser | Result | |--|--| | Firefox | logs `get`, logs `call`, returns `undefined` | | Chrome | logs `get`, logs `call`, returns `undefined` | | Safari | logs `get`, logs `call`, returns `undefined` | -- Reply to this email directly or view it on GitHub: https://github.com/whatwg/webidl/issues/1497 You are receiving this because you are subscribed to this thread. Message ID: <whatwg/webidl/issues/1497@github.com>
Received on Monday, 30 June 2025 12:46:29 UTC