This section uses the terminology and typographic conventions from the JavaScript specification. [JAVASCRIPT]
Serializable objects support being serialized, and later deserialized, in a way that is independent of any given JavaScript Realm . This allows them to be stored on disk and later restored, or cloned across document and worker boundaries (including across documents of different origins or in different event loops ).
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
they
implement
only
interfaces
decorated
with
the
[Serializable]
IDL
extended
attribute
.
Such
interfaces
must
also
define
the
following
algorithms:
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 JavaScript Realm .
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.
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
not
appear
on
anything
other
than
an
interface.
It
must
appear
only
once
on
an
interface.
It
must
not
be
used
on
a
callback
interface.
If
it
appears
on
a
partial
interface
or
an
interface
that
is
really
a
mixin,
then
it
must
also
appear
on
the
original
or
mixed-in-to
interface,
and
any
supplied
serialization
steps
and
deserialization
steps
for
the
partial
interface
or
mixin
should
be
understood
as
being
appended
to
those
of
the
original
or
mixed-in-to
interface.
Let's
say
we
were
defining
a
platform
object
Person
,
which
had
associated
with
it
two
pieces
of
associated
data:
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:
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 .
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 JavaScript 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.
Transferable objects support being transferred across event loops . 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
they
implement
only
interfaces
decorated
with
the
[Transferable]
IDL
extended
attribute
.
Such
interfaces
must
also
define
the
following
algorithms:
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 JavaScript Realm .
These steps may throw an exception if transferral is not possible.
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
not
appear
on
anything
other
than
an
interface.
It
must
appear
only
once
on
an
interface.
It
must
not
be
used
on
a
callback
interface.
If
it
appears
on
a
partial
interface
or
an
interface
that
is
really
a
mixin,
then
it
must
also
appear
on
the
original
or
mixed-in-to
interface,
and
any
supplied
serialization
steps
and
deserialization
steps
for
the
partial
interface
or
mixin
should
be
understood
as
being
appended
to
those
of
the
original
or
mixed-in-to
interface.
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.
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.
If Type ( value ) is Undefined, Null, Boolean, Number, BigInt, or String, then return { [[Type]]: "primitive", [[Value]]: value }. [JSBIGINT]
If
Type
(
value
)
is
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]] }. [JSBIGINT]
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, then:
Let size be value .[[ArrayBufferByteLength]].
If ! IsSharedArrayBuffer ( value ) is true, then:
If
forStorage
is
true,
then
throw
a
"
DataCloneError
"
DOMException
.
Set serialized to { [[Type]]: "SharedArrayBuffer", [[ArrayBufferData]]: value .[[ArrayBufferData]], [[ArrayBufferByteLength]]: size , [[AgentCluster]]: the current Realm Record 's corresponding agent cluster }.
Otherwise:
If
!
IsDetachedBuffer
(
value
)
is
true,
then
throw
a
"
DataCloneError
"
DOMException
.
Let dataCopy be ? CreateByteDataBlock ( size ).
This
can
throw
a
RangeError
exception
upon
allocation
failure.
Perform ! CopyDataBlockBytes ( dataCopy , 0, value .[[ArrayBufferData]], 0, size ).
Set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: dataCopy , [[ArrayBufferByteLength]]: size }.
Otherwise, if value has a [[ViewedArrayBuffer]] internal slot, then:
Let buffer be the value of value 's [[ViewedArrayBuffer]] internal slot.
Let bufferSerialized be ? StructuredSerializeInternal ( buffer , forStorage , memory ).
Assert: bufferSerialized .[[Type]] is "ArrayBuffer".
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 [[MapData]] internal slot, then:
Set serialized to { [[Type]]: "Map", [[MapData]]: a new empty List }.
Set deep to true.
Otherwise, if value has [[SetData]] internal slot, then:
Set serialized to { [[Type]]: "Set", [[SetData]]: a new empty List }.
Set deep to true.
Otherwise, if value is an Array exotic object, then:
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 }.
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]]
or
[[Extensible]],
then
throw
a
"
DataCloneError
"
DOMException
.
For instance, a [[PromiseState]] or [[WeakMapData]] internal slot.
Otherwise,
if
value
is
an
exotic
object,
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.
Set memory [ value ] to serialized .
If deep is true, then:
If value has a [[MapData]] internal slot, then:
Let copiedList be a new empty List .
For each Record { [[Key]], [[Value]] } entry of value .[[MapData]]:
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, then:
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 appropriate serialization steps 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:
Let enumerableKeys be a new empty List .
For each key in ! value .[[OwnPropertyKeys]]():
For each key in enumerableKeys :
If ! HasOwnProperty ( value , key ) is true, then:
Let inputValue be ? value .[[Get]]( key , value ).
Let outputValue be ? StructuredSerializeInternal ( inputValue , forStorage , memory ).
Append { [[Key]]: key , [[Value]]: outputValue } to serialized .[[Properties]].
The key collection performed above is very similar to the JavaScript specification's EnumerableOwnProperties operation, but crucially it uses the deterministic ordering provided by the [[OwnPropertyKeys]] internal method, instead of reordering the keys in an unspecified manner as EnumerableOwnProperties does. [JAVASCRIPT]
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> } » }
Return ? StructuredSerializeInternal ( value , false).
Return ? StructuredSerializeInternal ( value , true).
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 ].
Let deep be false.
Let value be an uninitialized value.
If serialized .[[Type]] is "primitive", then set value to serialized .[[Value]].
Otherwise, 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]]. [JSBIGINT]
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:
If
targetRealm
's
corresponding
agent
cluster
is
not
serialized
.[[AgentCluster]],
then
then
throw
a
"
DataCloneError
"
DOMException
.
Otherwise, 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 "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 "ArrayBufferView", then:
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", then:
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", then:
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", then:
Let outputProto be the %ArrayPrototype% intrinsic object in targetRealm .
Set value to ! ArrayCreate ( serialized .[[Length]], outputProto ).
Set deep to true.
Otherwise, if serialized .[[Type]] is "Object", then:
Set value to a new Object in targetRealm .
Set deep to true.
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, then:
If serialized .[[Type]] is "Map", then:
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", then:
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", then:
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 and value .
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 .
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 }.
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, then:
Set dataHolder .[[Type]] to "ArrayBuffer".
Set dataHolder .[[ArrayBufferData]] to transferable .[[ArrayBufferData]].
Set dataHolder .[[ArrayBufferByteLength]] to transferable .[[ArrayBufferByteLength]].
Perform ! DetachArrayBuffer ( transferable ).
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 .
Perform the appropriate transfer steps for the interface identified by interfaceName , given transferable and dataHolder .
Set transferable . [[Detached]] to true.
Append dataHolder to transferDataHolders .
Return { [[Serialized]]: serialized , [[TransferDataHolders]]: transferDataHolders }.
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.
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:
Let interfaceName be transferDataHolder .[[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 .
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 }.
Other specifications may use the abstract operations defined here. The following provides some guidance on when each abstract operation is typically useful, with examples.
Cloning a value to another JavaScript 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
.
Creating a JavaScript 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
SharedArrayBuffer
objects,
since
storing
shared
memory
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.
This specification used to define a "structured clone" algorithm, and more recently a StructuredClone abstract operation. However, in practice all known uses of it were better served by separate serialization and deserialization steps, so it was removed.
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.