Structured cloning and transfer

Overview

Structured cloning algorithm defines the semantics of copying a well-defined subset of ECMAScript objects between Code Realms. This algorithm is extensible by host enviroment to support cloning of host objects.

Optionally, some kinds of objects may support a "transfer" operation, the effect of which is to transfer "ownership" of some resource associated with an object to a different Code Realm. The object then becomes unusable in the source Code Realm.


This specification combines and subsumes http://www.whatwg.org/specs/web-apps/current-work/#dom-messageport-postmessage and http://www.whatwg.org/specs/web-apps/current-work/#structured-clone as they really belong together.

HTML spec will be updated to refer to this specification of the StructuredClone algorithm.


We introduce a StructuredClone operator.

Transferable objects carry a [[Transfer]] internal data property that is either a transfer operator or "neutered", and an [[OnSuccessfulTransfer]] internal method.

Objects defined outside ECMAScript need to define a [[Clone]] internal method that returns a copy of the object.

Note: The first iteration is not user-pluggable. It is about moving the semantics into ECMAScript proper and tying them down.

StructuredClone(input, transferList, targetRealm)

The operator StructuredClone either returns a structured clone of input or throws an exception. A structured clone of an object input is an object in Code Realm targetRealm. transferList is a list of objects that should be transferred during cloning of input.

  1. Let memory be a map of source-to-destination object mappings.
  2. For each object transferable in transferList:
    1. If transferable does not have a [[Transfer]] internal data property whose value is an operator, throw a DataCloneError exception.
    2. Let transferResult be a result of a call to a transferable's internal method [[Transfer]] with argument targetRealm.
    3. ReturnIfAbrupt( transferResult )
    4. Append a mapping from transferable to transferResult to memory.
  3. Let clone be the result of InternalStructuredClone( input, memory, targetRealm ).
  4. ReturnIfAbrupt( clone ).
  5. For each object transferable in transferList:
    1. Let transferResult be a target of mapping from transferable in memory.
    2. Run transferable's internal method [[OnSuccessfulTransfer]](transferResult, targetRealm).
  6. Return clone.

InternalStructuredClone(input, memory, targetRealm)

The operator InternalStructuredClone either returns a structured clone of input in Code Realm targetRealm or throws an exception.

  1. If input is the source object of a pair of objects in memory, then return the destination object in that pair of objects.
  2. If input's [[Transfer]] is “neutered”, throw a DataCloneError exception.
  3. If input is a primitive value, return input.
  4. Let deepClone be false.
  5. If input has a [[BooleanData]] internal data property:
    • Let output be a new Boolean object in targetRealm whose [[BooleanData]] is [[BooleanData]] of input.
  6. If input has a [[NumberData]] internal data property:
    • Let output be a new Number object in targetRealm whose [[NumberData]] is [[NumberData]] of input.
  7. If input has a [[StringData]] internal data property:
    • Let output be a new String object in targetRealm whose [[StringData]] is [[StringData]] of input.
  8. If input has a [[DateValue]] internal data property:
    • Let output be a new Date object in targetRealm whose [[DateValue]] is [[DateValue]] of input.
  9. If input.[[RegExpMatcher]] exists:
    • Let output be new RegExp object r in targetRealm such that:
      • [[RegExpMatcher]] of r is [[RegExpMatcher]] of input.
      • [[OriginalSource]] of r is [[OriginalSource]] of input.
      • [[OriginalFlags]] of r is [[OriginalFlags]] of input.
  10. If input has [[ArrayBufferData]] internal data property:
    1. Set output to CopyArrayBufferToRealm(input, targetRealm).
  11. If input has [[ViewedArrayBuffer]] internal data property, then:
    1. let arrayBuffer be a value of input's [[ViewedArrayBuffer]] internal data property.
    2. let arrayBufferClone be InternalStructuredClone(arrayBuffer, memory, targetRealm)
    3. ReturnIfAbrupt(_arrayBufferClone_)
    4. if input instanceof %DataView% intrinsic object in current realm:
      1. Let output be an instance of %DataView% intrinsic object in targetRealm.
      2. Set output's [[ViewedArrayBuffer]] to arrayBufferClone.
      3. Set output's [[ByteOffset]] to input's [[ByteOffset]].
      4. Set output's [[ByteLength]] to input's [[ByteLength]].
    5. Otherwise, if input instanceof %TypedArray% for one of typed arrays' intrinsics TypedArray in current code realm:
      1. Let output be an instance of %TypedArray% intrinsic object in targetRealm.
      2. Set output's [[ByteOffset]] to input's [[ByteOffset]].
      3. Set output's [[ByteLength]] to input's [[ByteLength]].
      4. Set output's [[ArrayLength]] to input's [[ArrayLength]].
  12. If input has [[MapData]] internal data property, ...
  13. If input has [[SetData]] internal data property, ...
  14. If input is an exotic Array object:
    1. Let output be a new Array in targetRealm.
    2. Set output.length to input.length.
    3. Set deepClone to true.
  15. Otherwise, if IsCallable( input), throw a DataCloneError exception.
  16. Otherwise, if input has [[ErrorData]] propety, throw a DataCloneError exception.
  17. Otherwise, if input has [[Clone]] internal method:
    1. Set output to a result of input.[[Clone]]( targetRealm )
  18. Otherwise, if input is an exotic object, throw a DataCloneError exception.
  19. Otherwise:
    1. Let object be a new Object in targetRealm.
    2. set deepClone to true.
  20. Add a mapping from input (the source object) to output (the destination object) to memory.
  21. If deepClone is true:
    1. Let keys be input.[[OwnPropertyKeys]]().
    2. For each key in keys:
      1. If key is a primitive String value, set outputKey to key
      2. TODO: Symbols
      3. Let sourceValue be a result of a call to input's internal method [[Get]]( key, input).
      4. ReturnIfAbrupt( sourceValue).
      5. Let clonedValue be InternalStructuredClone( sourceValue, memory).
      6. ReturnIfAbrupt( clonedValue).
      7. Let outputSet be a result of a call to output's internal method [[Set]]( outputKey, clonedValue, output).
      8. ReturnIfAbrupt( outputSet )
  22. Return output.

Definition of [[Transfer]](targetRealm) on ECMAScript exotic objects.

Definition of object.[[Transfer]]( targetRealm ):

  1. If object has an [[ArrayBufferData]] internal data property then:
  2. Return CopyArrayBufferToRealm(object, targetRealm).

Definition of CopyArrayBufferToRealm(arrayBuffer, targetRealm)

  1. Let result be a new ArrayBuffer arrayBuffer in targetRealm.
  2. Let length be a value of arrayBuffer's [[ArrayBufferByteLength]] internal slot.
  3. Let srcBlock be the value of arrayBuffer's [[ArrayBufferData]] internal slot.
  4. Let setStatus be a result of SetArrayBufferData(result,length).
  5. ReturnIfAbrupt(setStatus).
  6. Let targetBlock be a value of result's [[ArrayBufferData]] internal slot.
  7. Perform CopyDataBlock(targetBlock, 0, srcBlock, 0, length).
  8. Return result.

Definition of [[OnSuccessfulTransfer]]() on ECMAScript exotic objects.

Definition of internal method object.[[OnSuccessfulTransfer]]( transferResult, targetRealm ):

  1. If object has an [[ArrayBufferData]] internal data property then:
    1. Let neuteringResult be SetArrayBufferData( object, 0 ).
    2. ReturnIfAbrupt( neuteringResult ).
    3. Set value of object's [[Transfer]] internal data property to "neutered".

DataCloneError error object

Indicates failure of the structured clone algorithm.

{Rationale: typically, ECMAScript operations throw RangeError for similar failures, but we need to preserve DOM compatibnility}