structuredClone rejects intrinsic prototype objects
Categories
(Core :: JavaScript Engine, defect, P3)
Tracking
()
People
(Reporter: akaster, Unassigned, NeedInfo)
References
(Blocks 1 open bug)
Details
User Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/117.0
Steps to reproduce:
Open JS console:
let a = { "a": 12 }
structuredClone(a.proto)
let b = new RegExp(".", "")
structuredClone(b.proto)
Actual results:
The prototype of the ordinary object a is cloned and printed to the console.
A DOM Exception is thrown when trying to clone the RegExp.prototype object from b.
Expected results:
The spec steps for structured serialize internal (html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal) don't seem to preclude serializing intrinsic object prototypes:
In step 21, the algorithm precludes any objects with funky internal slots
Otherwise, if value has any internal slot other than [[Prototype]] or [[Extensible]], then throw a "DataCloneError" DOMException.
If we look at the ES spec for RegExp Prototype: tc39.es/ecma262/#sec-properties-of-the-regexp-prototype-object
It says that that object:
is %RegExp.prototype%.
is an ordinary object.
is not a RegExp instance and does not have a [[RegExpMatcher]] internal slot or any of the other internal slots of RegExp instance objects.
has a [[Prototype]] internal slot whose value is %Object.prototype%.
Which suggests to me that it should fall through to the next step, step 23, which says:
Otherwise, if value is an exotic object and value is not the %Object.prototype% intrinsic object associated with any realm, then throw a "DataCloneError" DOMException.
Since %RegExp.prototype% is an ordinary object, it's not exotic, and so it should be cloneable as any other object.
Chromium does this per the spec, and WebKit does not.
Comment 1•2 years ago
|
||
The Bugbug bot thinks this bug should belong to the 'Core::JavaScript Engine' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.
Reporter | ||
Comment 2•2 years ago
|
||
Comment 3•2 years ago
|
||
This currently affects all intrinsic prototypes that should be cloned to empty objects, e.g. %Date.prototype%, %Error.prototype%, etc, not just %RegExp.prototype%.
Comment 4•2 years ago
|
||
Curiously I’ve found a single exception to my prior statement so far: %Symbol.prototype% does get mapped to an empty object.
Note that the primitive-boxing intrinsic prototypes that have specially-handled [[FooData]] slots (%Boolean.prototype%, %Number.prototype%, and %String.prototype%) are handled correctly already. Similarly, %Array.prototype% is already handled correctly due to being an Array exotic object.
Comment 5•2 years ago
|
||
Apologies for repeated posts (wish I could edit a previous one!) but I keep discovering more information:
It’s not just intrinsic prototypes, it’s (most) intrinsic objects in general. The %Atomics%, %Intl%, %JSON%, %Math%, and %Reflect% namespace objects should also be getting mapped to empty objects by HTML’s algorithm, but cloning all of these except (for some reason!) %Reflect% throws in FF.
Conversely, structuredClone(document.adoptedStyleSheets)
(even when document.adoptedStyleSheets.length === 0) should throw due to the (unfortunate) PEO sniffing that occurs in the cloning algorithm, but doesn’t. This effectively reveals that FF isn’t actually implementing observable array exotic objects as specified (i.e., as proxy exotic objects). As much as I wish the PEO-revealing steps in structured clone didn’t exist, so long as they do, the situation (for host reimplementation / virtualization) is made worse by honoring those steps for userland PEOs yet ignoring them for natively implemented (pseudo-)PEOs.
Comment 6•2 years ago
|
||
Steve, I'm alas not read into structured cloning, so I'm going to need-info you here.
Comment 7•2 years ago
|
||
Yes, I was looking into this yesterday, and it's something I"ll need to fix.
Structured cloning is currently mostly going off of a set of known JSClass
pointers and rejecting anything else.
Namespace objects like JSON
are rejected because we seem to use custom JSClass
es in conjunction with js::ClassSpec
for laziness and a convenient way to construct all of the contained properties and functions. Reflect
also uses js::ClassSpec
, but not a custom JSClass
, so I need to look into it to understand why the others need the custom clasp. Maybe for laziness?
Prototypes also have their own clasp
s, especially the older ones because that's how things used to be done. More recently, the preference has switched to using plain objects for builtin prototypes, iiuc. Newer things like Symbol.prototype
and Temporal.Duration.prototype
are properly serializable as a result.
We don't necessarily have anything directly equivalent to spec internal slots, so I'll probably have to add an additional mechanism. Maybe a JSClass flag of some sort, like JSCLASS_ORDINARY_OBJECT
or something.
That said, I think this may also partly be due to structured cloning being defined in the HTML spec instead of es262. People have talked about moving it over, but it hasn't happened yet. If it were moved, I suspect we'd want to replace fuzzy language like "if value has any internal slot other than [[Prototype]] or [[Extensible]]"—the spec uses internal slots for all sorts of things. Should the presence of something like [[InitialName]]
or [[Realm]]
or [[ArrayBufferDetachKey]]
really have anything to do with whether it should be structured cloneable? The es262 spec already says "All objects have an internal slot named [[PrivateElements]]", so if I were to be pedantic I would claim that no objects should be structured cloneable according to the current HTML spec.
I haven't waded through the spec enough to get a handle on the proxy exotic object stuff yet.
Updated•4 months ago
|
Description
•