GeezerVison is showing html.spec.whatwg.org — HTML Standard
- 2.7 Safe passing of structured data
- 2.7.1 Serializable objects
- 2.7.2 Transferable objects
- 2.7.3 Restricting to the same agent cluster
- 2.7.4 StructuredSerializeInternal ( value, forStorage [ ,
memory ] )
- 2.7.5 StructuredSerialize ( value )
- 2.7.6 StructuredSerializeForStorage ( value )
- 2.7.7 StructuredDeserialize ( serialized, targetRealm [ ,
memory ] )
- 2.7.8 StructuredSerializeWithTransfer ( value, transferList
)
- 2.7.9 StructuredDeserializeWithTransfer ( serializeWithTransferResult,
targetRealm )
- 2.7.10 Performing serialization and
transferring from other specifications
- 2.7.11 Structured cloning API
2.7 Safe passing of structured data
To support passing JavaScript objects,
including platform objects, across realm
boundaries, this specification defines the following infrastructure for
serializing and deserializing objects, including in some cases transferring the underlying data
instead of copying it. Collectively this serialization/deserialization process is known as
"structured cloning", although most APIs perform separate serialization and deserialization steps.
(With the notable exception being the structuredClone()
method.)
This section uses the terminology and typographic conventions from the JavaScript
specification. [JAVASCRIPT]
2.7.1 Serializable objects
Serializable objects support being serialized, and later deserialized, in a way
that is independent of any given realm. This allows them to be stored on disk and
later restored, or cloned across agent and even agent cluster
boundaries.
Not all objects are serializable objects, and not all aspects of objects that are
serializable objects are necessarily preserved when they are serialized.
Platform objects can be serializable objects
if their primary interface is decorated with the [Serializable] IDL extended
attribute. Such interfaces must also define the following algorithms:
- serialization steps, taking a platform object
value, a Record serialized, and a boolean
forStorage
-
A set of steps that serializes the data in value into fields of
serialized. The resulting data serialized into serialized must be
independent of any realm. If serialized is restricted to the same agent cluster, then it may
also refer to data that is only accessible within that agent cluster.
serialized has an [[AgentCluster]] field, which is null unless
serialized is restricted to the same agent cluster.
These steps may throw an exception if serialization is not possible.
These steps may perform a sub-serialization to serialize nested data
structures. They should not call StructuredSerialize directly, as doing so will
omit the important memory argument.
The introduction of these steps should omit mention of the forStorage argument if
it is not relevant to the algorithm.
- deserialization steps, taking a Record serialized,
a platform object value, and a realm
targetRealm
-
A set of steps that deserializes the data in serialized, using it to set up
value as appropriate. value will be a newly-created instance of the
platform object type in question, with none of its internal data set up; setting
that up is the job of these steps.
These steps may throw an exception if deserialization is not possible.
These steps may perform a sub-deserialization to deserialize nested data
structures. They should not call StructuredDeserialize directly, as doing so will
omit the important targetRealm and memory arguments.
It is up to the definition of individual platform objects to determine what data is serialized
and deserialized by these steps. Typically the steps are very symmetric.
The [Serializable] extended attribute must take no
arguments, and must only appear on an interface. It must not appear more than once on an
interface.
For a given platform object, only the object's primary interface is
considered during the (de)serialization process. Thus, if inheritance is involved in defining the
interface, each [Serializable]-annotated interface in the
inheritance chain needs to define standalone serialization steps and
deserialization steps, including taking into account any important data that might
come from inherited interfaces.
Let's say we were defining a platform object Person, which had
associated with it two pieces of associated data:
a name value, which is a string; and a best friend value, which is either another Person instance
or null.
We could then define Person instances to be serializable
objects by annotating the Person interface with the [Serializable] extended attribute, and defining the
following accompanying algorithms:
Their serialization steps, given value and serialized:
Set serialized.[[Name]] to value's associated name
value. Let serializedBestFriend be the sub-serialization of
value's associated best friend value. Set serialized.[[BestFriend]] to serializedBestFriend.
Their deserialization steps, given serialized, value, and
targetRealm:
Set value's associated name value to
serialized.[[Name]]. Let deserializedBestFriend be the sub-deserialization of
serialized.[[BestFriend]]. Set value's associated best friend value to
deserializedBestFriend.
Objects defined in the JavaScript specification are handled by the
StructuredSerialize abstract operation directly.
Originally, this specification defined the concept of
"cloneable objects", which could be cloned from one realm to another. However, to
better specify the behavior of certain more complex situations, the model was updated to make the
serialization and deserialization explicit.
2.7.2 Transferable objects
Transferable objects support being transferred across agents. Transferring is effectively recreating the object while sharing a
reference to the underlying data and then detaching the object being transferred. This is useful
to transfer ownership of expensive resources. Not all objects are transferable
objects and not all aspects of objects that are transferable objects are
necessarily preserved when transferred.
Transferring is an irreversible and non-idempotent operation. Once an object has
been transferred, it cannot be transferred, or indeed used, again.
Platform objects can be transferable objects
if their primary interface is decorated with the [Transferable] IDL extended
attribute. Such interfaces must also define the following algorithms:
- transfer steps, taking a platform object
value and a Record dataHolder
-
A set of steps that transfers the data in value into fields of
dataHolder. The resulting data held in dataHolder must be
independent of any realm. If dataHolder is restricted to the same agent cluster, then it may
also refer to data that is only accessible within that agent cluster.
dataHolder has an [[AgentCluster]] field, which is null unless
dataHolder is restricted to the same agent cluster.
These steps may throw an exception if transferral is not possible.
- transfer-receiving steps, taking a Record
dataHolder and a platform object value
-
A set of steps that receives the data in dataHolder, using it to set up
value as appropriate. value will be a newly-created instance of the
platform object type in question, with none of its internal data set up; setting
that up is the job of these steps.
These steps may throw an exception if it is not possible to receive the transfer.
It is up to the definition of individual platform objects to determine what data is transferred
by these steps. Typically the steps are very symmetric.
The [Transferable] extended attribute must take no
arguments, and must only appear on an interface. It must not appear more than once on an
interface.
For a given platform object, only the object's primary interface is
considered during the transferring process. Thus, if inheritance is involved in defining the
interface, each [Transferable]-annotated interface in the
inheritance chain needs to define standalone transfer steps and
transfer-receiving steps, including taking into account any important data that might
come from inherited interfaces.
Platform objects that are transferable
objects have a [[Detached]] internal slot. This
is used to ensure that once a platform object has been transferred, it cannot be transferred
again.
Objects defined in the JavaScript specification are handled by the
StructuredSerializeWithTransfer abstract operation directly.
2.7.3 Restricting to the same agent cluster
Some objects, such as SharedArrayBuffer objects, are restricted to the same
agent cluster: they can only be deserialized or received in the agent cluster they
were serialized or transferred from. Such objects also cannot be serialized for storage. For
example, when postMessage() is used to send one to a cross-site
iframe, a shared worker, or a service worker, a messageerror event is fired instead of a message event.
Platform objects are restricted to the same agent
cluster when they are serialized or transferred if their primary interface is
decorated with the [SameAgentCluster] IDL extended
attribute.
The [SameAgentCluster] extended attribute must take no
arguments, and must only appear on an interface that is also decorated with the [Serializable] extended attribute or the [Transferable] extended attribute. It must not appear more than once
on an interface.
For a given platform object, only the object's primary interface is
considered. Thus, if inheritance is involved in defining the interface, each interface in the
inheritance chain whose objects are to be restricted to the same agent cluster needs to be
decorated with the [SameAgentCluster] extended
attribute.
The serialization steps and transfer steps of other interfaces can
restrict their result to the same agent cluster themselves, for instance when only some of their
objects need it:
2.7.4 StructuredSerializeInternal ( value, forStorage [ ,
memory ] )
The StructuredSerializeInternal abstract operation takes as input a JavaScript
value value and serializes it to a realm-independent form, represented
here as a Record. This serialized form has all the information necessary to later
deserialize into a new JavaScript value in a different realm.
This process can throw an exception, for example when trying to serialize un-serializable
objects.
-
If memory was not supplied, let memory be an empty map.
The purpose of the memory map is to avoid serializing objects twice.
This ends up preserving cycles and the identity of duplicate objects in graphs.
If memory[value] exists, then
return memory[value]. Let deep be false. Let sameAgentCluster be false. If value is undefined, null, a Boolean, a Number, a BigInt, or a String, then return { [[Type]]: "primitive", [[Value]]:
value }. If value is a Symbol, then throw a
"DataCloneError" DOMException. Let serialized be an uninitialized value. If value has a [[BooleanData]] internal slot, then set serialized to
{ [[Type]]: "Boolean", [[BooleanData]]: value.[[BooleanData]] }. Otherwise, if value has a [[NumberData]] internal slot, then set
serialized to { [[Type]]: "Number", [[NumberData]]: value.[[NumberData]]
}. Otherwise, if value has a [[BigIntData]]
internal slot, then set serialized to { [[Type]]: "BigInt", [[BigIntData]]:
value.[[BigIntData]] }. Otherwise, if value has a [[StringData]] internal slot, then set
serialized to { [[Type]]: "String", [[StringData]]: value.[[StringData]]
}. Otherwise, if value has a [[DateValue]] internal slot, then set
serialized to { [[Type]]: "Date", [[DateValue]]: value.[[DateValue]]
}. Otherwise, if value has a [[RegExpMatcher]] internal slot, then set
serialized to { [[Type]]: "RegExp", [[RegExpMatcher]]:
value.[[RegExpMatcher]], [[OriginalSource]]: value.[[OriginalSource]],
[[OriginalFlags]]: value.[[OriginalFlags]] }. -
Otherwise, if value has an [[ArrayBufferData]] internal slot:
-
If IsSharedArrayBuffer(value) is true:
-
If the current settings object's cross-origin isolated
capability is false, then throw a "DataCloneError"
DOMException.
This check is only needed when serializing (and not when deserializing) as
the cross-origin
isolated capability cannot change over time and a SharedArrayBuffer
cannot leave an agent cluster.
If value has an [[ArrayBufferMaxByteLength]] internal slot, then set
serialized to { [[Type]]: "GrowableSharedArrayBuffer", [[ArrayBufferData]]:
value.[[ArrayBufferData]], [[ArrayBufferByteLengthData]]:
value.[[ArrayBufferByteLengthData]], [[ArrayBufferMaxByteLength]]:
value.[[ArrayBufferMaxByteLength]] }. Otherwise, set serialized to { [[Type]]: "SharedArrayBuffer",
[[ArrayBufferData]]: value.[[ArrayBufferData]], [[ArrayBufferByteLength]]:
value.[[ArrayBufferByteLength]] }. Set sameAgentCluster to true.
-
Otherwise:
If IsDetachedBuffer(value) is true, then throw a
"DataCloneError" DOMException. Let size be value.[[ArrayBufferByteLength]]. -
Let dataCopy be ? CreateByteDataBlock(size).
This can throw a RangeError exception
upon allocation failure.
Perform CopyDataBlockBytes(dataCopy, 0,
value.[[ArrayBufferData]], 0, size). If value has an [[ArrayBufferMaxByteLength]] internal slot, then set
serialized to { [[Type]]: "ResizableArrayBuffer", [[ArrayBufferData]]:
dataCopy, [[ArrayBufferByteLength]]: size,
[[ArrayBufferMaxByteLength]]: value.[[ArrayBufferMaxByteLength]] }. Otherwise, set serialized to { [[Type]]: "ArrayBuffer",
[[ArrayBufferData]]: dataCopy, [[ArrayBufferByteLength]]: size
}.
-
Otherwise, if value has a [[ViewedArrayBuffer]] internal slot:
If IsArrayBufferViewOutOfBounds(value) is true, then throw a
"DataCloneError" DOMException. Let buffer be the value of value's [[ViewedArrayBuffer]] internal
slot. Let bufferSerialized be ?
StructuredSerializeInternal(buffer, forStorage,
memory). Assert: bufferSerialized.[[Type]] is "ArrayBuffer",
"ResizableArrayBuffer", "SharedArrayBuffer", or "GrowableSharedArrayBuffer". If value has a [[DataView]] internal slot, then set serialized to
{ [[Type]]: "ArrayBufferView", [[Constructor]]: "DataView", [[ArrayBufferSerialized]]:
bufferSerialized, [[ByteLength]]: value.[[ByteLength]], [[ByteOffset]]:
value.[[ByteOffset]] }. -
Otherwise:
Assert: value has a [[TypedArrayName]] internal slot. Set serialized to { [[Type]]: "ArrayBufferView", [[Constructor]]:
value.[[TypedArrayName]], [[ArrayBufferSerialized]]: bufferSerialized,
[[ByteLength]]: value.[[ByteLength]], [[ByteOffset]]:
value.[[ByteOffset]], [[ArrayLength]]: value.[[ArrayLength]] }.
-
Otherwise, if value has a [[MapData]] internal slot:
Set serialized to { [[Type]]: "Map", [[MapData]]: a new empty List }. Set deep to true.
-
Otherwise, if value has a [[SetData]] internal slot:
Set serialized to { [[Type]]: "Set", [[SetData]]: a new empty List }. Set deep to true.
-
Otherwise, if value has an [[ErrorData]] internal slot and value is not
a platform object:
Let name be ? Get(value,
"name"). If name is not one of "Error", "EvalError", "RangeError", "ReferenceError",
"SyntaxError", "TypeError", or "URIError", then set name to "Error". Let valueMessageDesc be ? value.[[GetOwnProperty]]("message"). Let message be undefined if
IsDataDescriptor(valueMessageDesc) is false, and
? ToString(valueMessageDesc.[[Value]]) otherwise. Let stack be an implementation-defined string that
represents value.[[Stack]]. [JSERRORSTACKACCESSOR]
[JSERRORSTACKS] Set serialized to { [[Type]]: "Error", [[Name]]: name,
[[Message]]: message, [[Stack]]: stack }. User agents should attach a serialized representation of any interesting accompanying
data which are not yet specified to serialized.
-
Otherwise, if value is an Array exotic object:
Let valueLenDescriptor be ?
OrdinaryGetOwnProperty(value, "length"). Let valueLen be valueLenDescriptor.[[Value]]. Set serialized to { [[Type]]: "Array", [[Length]]: valueLen,
[[Properties]]: a new empty List }. Set deep to true.
-
Otherwise, if value is a platform object that is a serializable object:
If value has a [[Detached]] internal slot whose value is true,
then throw a "DataCloneError" DOMException. Let typeString be the identifier of the primary interface of
value. Set serialized to { [[Type]]: typeString }. If value's primary interface is decorated with [SameAgentCluster], then set sameAgentCluster to
true. Set deep to true.
Otherwise, if value is a platform object, then throw a
"DataCloneError" DOMException. Otherwise, if IsCallable(value) is true, then throw a
"DataCloneError" DOMException. -
Otherwise, if value has any internal slot other than [[Prototype]],
[[Extensible]], or [[PrivateElements]], then throw a "DataCloneError"
DOMException.
For instance, a [[PromiseState]] or [[WeakMapData]] internal slot.
-
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.
For instance, a proxy object.
-
Otherwise:
Set serialized to { [[Type]]: "Object", [[Properties]]: a new empty List }. Set deep to true.
%Object.prototype% will end up being handled via this step and
subsequent steps. The end result is that its exoticness is ignored, and after deserialization
the result will be an empty object (not an immutable prototype exotic object).
If sameAgentCluster is true, then restrict serialized to the same agent cluster; otherwise, set
serialized.[[AgentCluster]] to null. Set memory[value] to
serialized. -
If deep is true:
-
If value has a [[MapData]] internal slot:
Let copiedList be a new empty List.
-
For each Record { [[Key]], [[Value]] }
entry of value.[[MapData]]:
Let copiedEntry be a new Record { [[Key]]:
entry.[[Key]], [[Value]]: entry.[[Value]] }. If copiedEntry.[[Key]] is not the special value empty, append copiedEntry to copiedList.
-
For each Record { [[Key]], [[Value]] }
entry of copiedList:
Let serializedKey be ?
StructuredSerializeInternal(entry.[[Key]], forStorage,
memory). Let serializedValue be ?
StructuredSerializeInternal(entry.[[Value]], forStorage,
memory). Append { [[Key]]: serializedKey,
[[Value]]: serializedValue } to serialized.[[MapData]].
-
Otherwise, if value has a [[SetData]] internal slot:
Let copiedList be a new empty List.
-
For each entry of value.[[SetData]]:
If entry is not the special value empty, append entry to copiedList.
-
For each entry of copiedList:
Let serializedEntry be ?
StructuredSerializeInternal(entry, forStorage,
memory). Append serializedEntry to
serialized.[[SetData]].
-
Otherwise, if value is a platform object that is a serializable object, then perform the serialization
steps for value's primary interface, given value,
serialized, and forStorage.
The serialization steps may need to perform a sub-serialization. This is an operation which takes as input a value
subValue, and returns StructuredSerializeInternal(subValue,
forStorage, memory). (In other words, a sub-serialization
is a specialization of StructuredSerializeInternal to be consistent within this
invocation.)
-
Otherwise, for each key in !
EnumerableOwnProperties(value, key):
-
If ! HasOwnProperty(value, key) is true:
Let inputValue be ? value.[[Get]](key,
value). Let outputValue be ?
StructuredSerializeInternal(inputValue, forStorage,
memory). Append { [[Key]]: key, [[Value]]:
outputValue } to serialized.[[Properties]].
-
If forStorage is true and serialized.[[AgentCluster]] is not null, then
throw a "DataCloneError" DOMException.
This happens after the serialization steps have run, as they can
restrict serialized to the
same agent cluster too.
Return serialized.
It's important to realize that the Records
produced by StructuredSerializeInternal might contain "pointers" to other records
that create circular references. For example, when we pass the following JavaScript object into
StructuredSerializeInternal:
const o = {};
o.myself = o;
it produces the following result:
{
[[Type]]: "Object",
[[Properties]]: «
{
[[Key]]: "myself",
[[Value]]: <a pointer to this whole structure>
}
»
}
2.7.7 StructuredDeserialize ( serialized, targetRealm [ ,
memory ] )
The StructuredDeserialize abstract operation takes as input a Record
serialized, which was previously produced by StructuredSerialize or
StructuredSerializeForStorage, and deserializes it into a new JavaScript value,
created in targetRealm.
This process can throw an exception, for example when trying to allocate memory for the new
objects (especially ArrayBuffer objects).
-
If memory was not supplied, let memory be an empty map.
The purpose of the memory map is to avoid deserializing objects
twice. This ends up preserving cycles and the identity of duplicate objects in graphs.
If memory[serialized] exists, then
return memory[serialized]. If serialized.[[Type]] is "primitive", then return
serialized.[[Value]]. If serialized.[[AgentCluster]] is not null and targetRealm's
corresponding agent cluster is not serialized.[[AgentCluster]], then
throw a "DataCloneError" DOMException. Let deep be false. Let value be an uninitialized value. If serialized.[[Type]] is "Boolean", then set value to a new
Boolean object in targetRealm whose [[BooleanData]] internal slot value is
serialized.[[BooleanData]]. Otherwise, if serialized.[[Type]] is "Number", then set value to a
new Number object in targetRealm whose [[NumberData]] internal slot value is
serialized.[[NumberData]]. Otherwise, if serialized.[[Type]] is "BigInt", then set value to a
new BigInt object in targetRealm whose [[BigIntData]] internal slot value is
serialized.[[BigIntData]]. Otherwise, if serialized.[[Type]] is "String", then set value to a
new String object in targetRealm whose [[StringData]] internal slot value is
serialized.[[StringData]]. Otherwise, if serialized.[[Type]] is "Date", then set value to a new
Date object in targetRealm whose [[DateValue]] internal slot value is
serialized.[[DateValue]]. Otherwise, if serialized.[[Type]] is "RegExp", then set value to a
new RegExp object in targetRealm whose [[RegExpMatcher]] internal slot value is
serialized.[[RegExpMatcher]], whose [[OriginalSource]] internal slot value is
serialized.[[OriginalSource]], and whose [[OriginalFlags]] internal slot value is
serialized.[[OriginalFlags]]. Otherwise, if serialized.[[Type]] is "SharedArrayBuffer", then set
value to a new SharedArrayBuffer object in targetRealm whose
[[ArrayBufferData]] internal slot value is serialized.[[ArrayBufferData]] and whose
[[ArrayBufferByteLength]] internal slot value is
serialized.[[ArrayBufferByteLength]]. Otherwise, if serialized.[[Type]] is "GrowableSharedArrayBuffer", then set
value to a new SharedArrayBuffer object in targetRealm whose
[[ArrayBufferData]] internal slot value is serialized.[[ArrayBufferData]], whose
[[ArrayBufferByteLengthData]] internal slot value is
serialized.[[ArrayBufferByteLengthData]], and whose [[ArrayBufferMaxByteLength]]
internal slot value is serialized.[[ArrayBufferMaxByteLength]]. -
Otherwise, if serialized.[[Type]] is "ArrayBuffer", then set value to a
new ArrayBuffer object in targetRealm whose [[ArrayBufferData]] internal slot value
is serialized.[[ArrayBufferData]], and whose [[ArrayBufferByteLength]] internal slot
value is serialized.[[ArrayBufferByteLength]].
If this throws an exception, catch it, and then throw a
"DataCloneError" DOMException.
This step might throw an exception if there is not enough memory available to
create such an ArrayBuffer object.
-
Otherwise, if serialized.[[Type]] is "ResizableArrayBuffer", then set
value to a new ArrayBuffer object in targetRealm whose [[ArrayBufferData]]
internal slot value is serialized.[[ArrayBufferData]], whose
[[ArrayBufferByteLength]] internal slot value is
serialized.[[ArrayBufferByteLength]], and whose [[ArrayBufferMaxByteLength]] internal
slot value is serialized.[[ArrayBufferMaxByteLength]].
If this throws an exception, catch it, and then throw a
"DataCloneError" DOMException.
This step might throw an exception if there is not enough memory available to
create such an ArrayBuffer object.
-
Otherwise, if serialized.[[Type]] is "ArrayBufferView":
Let deserializedArrayBuffer be ?
StructuredDeserialize(serialized.[[ArrayBufferSerialized]],
targetRealm, memory). If serialized.[[Constructor]] is "DataView", then set value to a
new DataView object in targetRealm whose [[ViewedArrayBuffer]] internal slot value
is deserializedArrayBuffer, whose [[ByteLength]] internal slot value is
serialized.[[ByteLength]], and whose [[ByteOffset]] internal slot value is
serialized.[[ByteOffset]]. Otherwise, set value to a new typed array object in targetRealm,
using the constructor given by serialized.[[Constructor]], whose
[[ViewedArrayBuffer]] internal slot value is deserializedArrayBuffer, whose
[[TypedArrayName]] internal slot value is serialized.[[Constructor]], whose
[[ByteLength]] internal slot value is serialized.[[ByteLength]], whose
[[ByteOffset]] internal slot value is serialized.[[ByteOffset]], and whose
[[ArrayLength]] internal slot value is serialized.[[ArrayLength]].
-
Otherwise, if serialized.[[Type]] is "Map":
Set value to a new Map object in targetRealm whose [[MapData]]
internal slot value is a new empty List. Set deep to true.
-
Otherwise, if serialized.[[Type]] is "Set":
Set value to a new Set object in targetRealm whose [[SetData]]
internal slot value is a new empty List. Set deep to true.
-
Otherwise, if serialized.[[Type]] is "Array":
Let outputProto be
targetRealm.[[Intrinsics]].[[%Array.prototype%]]. Set value to ! ArrayCreate(serialized.[[Length]],
outputProto). Set deep to true.
-
Otherwise, if serialized.[[Type]] is "Object":
Set value to a new Object in targetRealm. Set deep to true.
-
Otherwise, if serialized.[[Type]] is "Error":
Let prototype be %Error.prototype%. If serialized.[[Name]] is "EvalError", then set prototype to
%EvalError.prototype%. If serialized.[[Name]] is "RangeError", then set prototype
to %RangeError.prototype%. If serialized.[[Name]] is "ReferenceError", then set
prototype to %ReferenceError.prototype%. If serialized.[[Name]] is "SyntaxError", then set prototype
to %SyntaxError.prototype%. If serialized.[[Name]] is "TypeError", then set prototype to
%TypeError.prototype%. If serialized.[[Name]] is "URIError", then set prototype to
%URIError.prototype%. Let message be serialized.[[Message]]. Set value to OrdinaryObjectCreate(prototype, «
[[ErrorData]], [[Stack]] »). Let messageDesc be PropertyDescriptor { [[Value]]:
message, [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true
}. If message is not undefined, then perform !
OrdinaryDefineOwnProperty(value, "message",
messageDesc). Set value.[[Stack]] to serialized.[[Stack]]. Any interesting accompanying data attached to serialized should be
deserialized and attached to value.
-
Otherwise:
Let interfaceName be serialized.[[Type]]. If the interface identified by interfaceName is not
exposed in targetRealm, then throw a
"DataCloneError" DOMException. Set value to a new instance of the interface identified by
interfaceName, created in targetRealm.
Set deep to true.
Set memory[serialized] to
value. -
If deep is true:
-
If serialized.[[Type]] is "Map":
-
For each Record { [[Key]], [[Value]] }
entry of serialized.[[MapData]]:
Let deserializedKey be ?
StructuredDeserialize(entry.[[Key]], targetRealm,
memory). Let deserializedValue be ?
StructuredDeserialize(entry.[[Value]], targetRealm,
memory). Append { [[Key]]: deserializedKey,
[[Value]]: deserializedValue } to value.[[MapData]].
-
Otherwise, if serialized.[[Type]] is "Set":
-
For each entry of
serialized.[[SetData]]:
Let deserializedEntry be ?
StructuredDeserialize(entry, targetRealm,
memory). Append deserializedEntry to
value.[[SetData]].
-
Otherwise, if serialized.[[Type]] is "Array" or "Object":
-
For each Record { [[Key]], [[Value]] }
entry of serialized.[[Properties]]:
Let deserializedValue be ?
StructuredDeserialize(entry.[[Value]], targetRealm,
memory). Let result be ! CreateDataProperty(value,
entry.[[Key]], deserializedValue). Assert: result is true.
-
Otherwise:
-
Perform the appropriate deserialization steps for the interface identified
by serialized.[[Type]], given serialized, value, and
targetRealm.
The deserialization steps may need to perform a sub-deserialization. This is an operation which takes as input a
previously-serialized Record subSerialized, and returns
StructuredDeserialize(subSerialized, targetRealm,
memory). (In other words, a sub-deserialization is a specialization
of StructuredDeserialize to be consistent within this invocation.)
Return value.
2.7.8 StructuredSerializeWithTransfer ( value, transferList
)
-
Let memory be an empty map.
In addition to how it is used normally by
StructuredSerializeInternal, in this algorithm memory is also used to
ensure that StructuredSerializeInternal ignores items in transferList,
and let us do our own handling instead.
-
For each transferable of
transferList:
If transferable has neither an [[ArrayBufferData]] internal slot nor a
[[Detached]] internal slot, then throw a
"DataCloneError" DOMException. If transferable has an [[ArrayBufferData]] internal slot and
IsSharedArrayBuffer(transferable) is true, then throw a
"DataCloneError" DOMException. If memory[transferable] exists,
then throw a "DataCloneError" DOMException. -
Set memory[transferable] to {
[[Type]]: an uninitialized value, [[AgentCluster]]: null }.
transferable is not transferred yet as transferring has side
effects and StructuredSerializeInternal needs to be able to throw first.
Let serialized be ? StructuredSerializeInternal(value,
false, memory). Let transferDataHolders be a new empty List. -
For each transferable of
transferList:
If transferable has an [[ArrayBufferData]] internal slot and
IsDetachedBuffer(transferable) is true, then throw a
"DataCloneError" DOMException. If transferable has a [[Detached]] internal slot and
transferable.[[Detached]] is true, then throw a
"DataCloneError" DOMException. Let dataHolder be memory[transferable]. -
If transferable has an [[ArrayBufferData]] internal slot:
-
If transferable has an [[ArrayBufferMaxByteLength]] internal slot:
Set dataHolder.[[Type]] to "ResizableArrayBuffer". Set dataHolder.[[ArrayBufferData]] to
transferable.[[ArrayBufferData]]. Set dataHolder.[[ArrayBufferByteLength]] to
transferable.[[ArrayBufferByteLength]]. Set dataHolder.[[ArrayBufferMaxByteLength]] to
transferable.[[ArrayBufferMaxByteLength]].
-
Otherwise:
Set dataHolder.[[Type]] to "ArrayBuffer". Set dataHolder.[[ArrayBufferData]] to
transferable.[[ArrayBufferData]]. Set dataHolder.[[ArrayBufferByteLength]] to
transferable.[[ArrayBufferByteLength]].
-
Perform ? DetachArrayBuffer(transferable).
Specifications can use the [[ArrayBufferDetachKey]] internal slot to prevent
ArrayBuffers from being detached. This is used in
WebAssembly JavaScript Interface, for example. [WASMJS]
-
Otherwise:
Assert: transferable is a platform object that is
a transferable object. Let interfaceName be the identifier of the primary interface
of transferable. Set dataHolder.[[Type]] to interfaceName. If transferable's primary interface is decorated with [SameAgentCluster], then restrict dataHolder to the same agent
cluster. Perform the appropriate transfer steps for the interface identified by
interfaceName, given transferable and dataHolder. Set transferable.[[Detached]] to true.
Append dataHolder to
transferDataHolders.
Let result be { [[Serialized]]: serialized,
[[TransferDataHolders]]: transferDataHolders, [[InterfaceNames]]: an empty ordered set, [[AgentCluster]]: null }. -
For each object → record of
memory:
If object is a platform object, then append record.[[Type]] to
result.[[InterfaceNames]]. If record.[[AgentCluster]] is not null, then restrict result to the same agent cluster.
Return result.
2.7.9 StructuredDeserializeWithTransfer ( serializeWithTransferResult,
targetRealm )
-
Let memory be an empty map.
Analogous to StructuredSerializeWithTransfer, in addition to how it
is used normally by StructuredDeserialize, in this algorithm memory is
also used to ensure that StructuredDeserialize ignores items in
serializeWithTransferResult.[[TransferDataHolders]], and let us do our own handling
instead.
If serializeWithTransferResult.[[AgentCluster]] is not null and
targetRealm's corresponding agent cluster is not
serializeWithTransferResult.[[AgentCluster]], then throw a
"DataCloneError" DOMException. -
For each interfaceName of
serializeWithTransferResult.[[InterfaceNames]]:
If the interface identified by interfaceName is not exposed in targetRealm, then throw a
"DataCloneError" DOMException.
Let transferredValues be a new empty List. -
For each transferDataHolder of
serializeWithTransferResult.[[TransferDataHolders]]:
Let value be an uninitialized value. -
If transferDataHolder.[[Type]] is "ArrayBuffer", then set value to a
new ArrayBuffer object in targetRealm whose [[ArrayBufferData]] internal slot value
is transferDataHolder.[[ArrayBufferData]], and whose [[ArrayBufferByteLength]]
internal slot value is transferDataHolder.[[ArrayBufferByteLength]].
In cases where the original memory occupied by [[ArrayBufferData]] is
accessible during the deserialization, this step is unlikely to throw an exception, as no new
memory needs to be allocated: the memory occupied by [[ArrayBufferData]] is instead just
getting transferred into the new ArrayBuffer. This could be true, for example, when both the
source and target realms are in the same process.
-
Otherwise, if transferDataHolder.[[Type]] is "ResizableArrayBuffer", then set
value to a new ArrayBuffer object in targetRealm whose
[[ArrayBufferData]] internal slot value is transferDataHolder.[[ArrayBufferData]],
whose [[ArrayBufferByteLength]] internal slot value is
transferDataHolder.[[ArrayBufferByteLength]], and whose
[[ArrayBufferMaxByteLength]] internal slot value is
transferDataHolder.[[ArrayBufferMaxByteLength]].
For the same reason as the previous step, this step is also unlikely to throw
an exception.
-
Otherwise:
Let interfaceName be transferDataHolder.[[Type]]. Set value to a new instance of the interface identified by
interfaceName, created in targetRealm. Perform the appropriate transfer-receiving steps for the interface
identified by interfaceName given transferDataHolder and
value.
Set memory[transferDataHolder] to
value. Append value to
transferredValues.
Let deserialized be ?
StructuredDeserialize(serializeWithTransferResult.[[Serialized]],
targetRealm, memory). Return { [[Deserialized]]: deserialized, [[TransferredValues]]:
transferredValues }.
2.7.10 Performing serialization and
transferring from other specifications
Other specifications may use the abstract operations defined here. The following provides
some guidance on when each abstract operation is typically useful, with examples.
- StructuredSerializeWithTransfer
- StructuredDeserializeWithTransfer
-
Cloning a value to another realm, with a transfer list, but where the target
realm is not known ahead of time. In this case the serialization step can be performed
immediately, with the deserialization step delayed until the target realm becomes known.
messagePort.postMessage()
uses this pair of abstract operations, as the destination realm is not known until the
MessagePort has been shipped.
- StructuredSerialize
- StructuredSerializeForStorage
- StructuredDeserialize
-
Creating a realm-independent snapshot of a given value which can be saved for an
indefinite amount of time, and then reified back into a JavaScript value later, possibly
multiple times.
StructuredSerializeForStorage can be used for situations where the serialization
is anticipated to be stored in a persistent manner, instead of passed between realms. It throws
when attempting to serialize objects that are restricted to the same agent cluster, such as
SharedArrayBuffer objects, since storing shared memory or other data that cannot
leave an agent cluster does not make sense. Similarly, it can throw or possibly have different
behavior when given a platform object with custom serialization
steps when the forStorage argument is true.
history.pushState() and history.replaceState() use
StructuredSerializeForStorage on author-supplied state objects, storing them as
serialized state in the appropriate session history entry. Then,
StructuredDeserialize is used so that the history.state property can return a clone of the
originally-supplied state object.
broadcastChannel.postMessage() uses
StructuredSerialize on its input, then uses StructuredDeserialize
multiple times on the result to produce a fresh clone for each destination being broadcast
to. Note that transferring does not make sense in multi-destination situations.
Any API for persisting JavaScript values to the filesystem would also use
StructuredSerializeForStorage on its input and StructuredDeserialize
on its output.
In general, call sites may pass in Web IDL values instead of JavaScript values; this is to be
understood to perform an implicit conversion to the
JavaScript value before invoking these algorithms.
Call sites that are not invoked as a result of author code synchronously calling into a user
agent method must take care to properly prepare to run script and prepare to
run a callback before invoking StructuredSerialize,
StructuredSerializeForStorage, or StructuredSerializeWithTransfer
abstract operations, if they are being performed on arbitrary objects. This is necessary because
the serialization process can invoke author-defined accessors as part of its final
deep-serialization steps, and these accessors could call into operations that rely on the entry and incumbent concepts being properly set up.
window.postMessage() performs
StructuredSerializeWithTransfer on its arguments, but is careful to do so
immediately, inside the synchronous portion of its algorithm. Thus it is able to use the
algorithms without needing to prepare to run script and prepare to run a
callback.
In contrast, a hypothetical API that used StructuredSerialize to
serialize some author-supplied object periodically, directly from a task on the event loop, would need to ensure it performs
the appropriate preparations beforehand. As of this time, we know of no such APIs on the platform;
usually it is simpler to perform the serialization ahead of time, as a synchronous consequence of
author code.
2.7.11 Structured cloning API
result = self.structuredClone(value[, { transfer }])-
Takes the input value and returns a deep copy by performing the structured clone algorithm.
Transferable objects listed in the transfer array are transferred, not
just cloned, meaning that they are no longer usable in the input value.
Throws a "DataCloneError" DOMException if any part of
the input value is not serializable.
structuredClone Support in all current engines. Firefox94+Safari15.4+Chrome98+ Opera?Edge98+ Edge (Legacy)?Internet ExplorerNo Firefox Android?Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android?
|