From b3f8779c76de7718bc970fdbccda18a5164d2f98 Mon Sep 17 00:00:00 2001 From: Anne van Kesteren Date: Fri, 28 Apr 2017 09:38:26 +0200 Subject: [PATCH] Serializing and deserializing SharedArrayBuffer Additionally, define StructuredSerializeForStorage when for serializable objects need to be stored more persistently. Tests: https://github.com/w3c/web-platform-tests/pull/5003. StructuredSerializeForStorage follow-up: * https://github.com/whatwg/notifications/issues/99 * https://github.com/w3c/IndexedDB/issues/197 Fixes #2260 (by being the last in a set of fixes). Closes https://github.com/tc39/ecmascript_sharedmem/issues/144 and closes https://github.com/tc39/ecmascript_sharedmem/issues/65. --- source | 200 +++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 143 insertions(+), 57 deletions(-) diff --git a/source b/source index c1755d0f4e4..ac8e8c2a4b9 100644 --- a/source +++ b/source @@ -495,6 +495,13 @@ be thought of as completely serializing the execution of all scripts in all browsing contexts.

+

The exception to this general design principle is the JavaScript SharedArrayBuffer + class. Using SharedArrayBuffer objects, it can in fact be observed that scripts in + other agents are executing simultaneously. Furthermore, due to the + JavaScript memory model, there are situations which not only are un-representable via serialized + script execution, but also un-representable via serialized statement execution + among those scripts.

+ @@ -2912,6 +2919,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
  • The IsConstructor abstract operation
  • The IsDataDescriptor abstract operation
  • The IsDetachedBuffer abstract operation
  • +
  • The IsSharedArrayBuffer abstract operation
  • The NewObjectEnvironment abstract operation
  • The OrdinaryGetPrototypeOf abstract operation
  • @@ -7650,7 +7658,8 @@ interface DOMStringList {
    serialization steps, taking a platform object - value and a Record serialized
    + value, a Record serialized, and a boolean + forStorage

    A set of steps that serializes the data in input into fields of serialized. The resulting data serialized into serialized must be @@ -7661,6 +7670,9 @@ interface DOMStringList {

    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 @@ -7803,14 +7815,15 @@ interface DOMStringList {

    Objects defined in the JavaScript specification are handled by the StructuredSerializeWithTransfer abstract operation directly.

    -

    StructuredSerialize ( value [ , - memory ] )

    +

    StructuredSerializeInternal ( value, + forStorage [ , memory ] )

    -

    The StructuredSerialize 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.

    +

    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.

    @@ -7861,23 +7874,43 @@ interface DOMStringList {

    Otherwise, if value has an [[ArrayBufferData]] internal slot, then:

      -
    1. If IsDetachedBuffer(value) is true, then throw a - "DataCloneError" DOMException.

    2. -
    3. Let size be value.[[ArrayBufferByteLength]].

    4. -

      Let dataCopy be ? CreateByteDataBlock(size).

      +

      If ! IsSharedArrayBuffer(value) is true, then: + +

        +
      1. If forStorage is true, then throw a + "DataCloneError" DOMException.

      2. -

        This can throw a RangeError exception upon - allocation failure.

        +
      3. Set serialized to { [[Type]]: "SharedArrayBuffer", [[ArrayBufferData]]: + value.[[ArrayBufferData]], [[ArrayBufferByteLength]]: size, + [[AgentCluster]]: the current Realm Record's corresponding agent + cluster }.

      4. +
    5. -
    6. Perform ! CopyDataBlockBytes(dataCopy, 0, - value.[[ArrayBufferData]], 0, size).

    7. +
    8. +

      Otherwise:

      + +
        +
      1. If ! IsDetachedBuffer(value) is true, then throw a + "DataCloneError" DOMException.

      2. -
      3. Set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: - dataCopy, [[ArrayBufferByteLength]]: size }.

      4. +
      5. +

        Let dataCopy be ? CreateByteDataBlock(size).

        + +

        This can throw a RangeError exception + upon allocation failure.

        +
      6. + +
      7. Perform ! CopyDataBlockBytes(dataCopy, 0, + value.[[ArrayBufferData]], 0, size).

      8. + +
      9. Set serialized to { [[Type]]: "ArrayBuffer", [[ArrayBufferData]]: + dataCopy, [[ArrayBufferByteLength]]: size }.

      10. +
      +
    @@ -7888,7 +7921,8 @@ interface DOMStringList {
  • Let buffer be the value of value's [[ViewedArrayBuffer]] internal slot.

  • -
  • Let bufferSerialized be ? StructuredSerialize(buffer, +

  • Let bufferSerialized be ? + StructuredSerializeInternal(buffer, forStorage, memory).

  • Assert: bufferSerialized.[[Type]] is "ArrayBuffer".

  • @@ -8033,10 +8067,12 @@ interface DOMStringList {
    1. Let serializedKey be ? - StructuredSerialize(entry.[[Key]], memory).

    2. + StructuredSerializeInternal(entry.[[Key]], forStorage, + memory).

    3. Let serializedValue be ? - StructuredSerialize(entry.[[Value]], memory).

    4. + StructuredSerializeInternal(entry.[[Value]], forStorage, + memory).

    5. Append { [[Key]]: serializedKey, [[Value]]: serializedValue } to serialized.[[MapData]].

    6. @@ -8065,7 +8101,8 @@ interface DOMStringList {
      1. Let serializedEntry be ? - StructuredSerialize(entry, memory).

      2. + StructuredSerializeInternal(entry, forStorage, + memory).

      3. Append serializedEntry to serialized.[[SetData]].

      4. @@ -8077,13 +8114,15 @@ interface DOMStringList {
      5. Otherwise, if value is a platform object that is a serializable object, then perform the appropriate - serialization steps given value and serialized.

        + 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 StructuredSerialize(subValue, - memory). (In other words, a sub-serialization is a specialization of - StructuredSerialize to be consistent within this invocation.)

        + subValue, and returns StructuredSerializeInternal(subValue, + forStorage, memory). (In other words, a sub-serialization + is a specialization of StructuredSerializeInternal to be consistent within this + invocation.)

      6. @@ -8121,7 +8160,7 @@ interface DOMStringList { value).

      7. Let outputValue be ? - StructuredSerialize(inputValue, targetRealm, + StructuredSerializeInternal(inputValue, forStorage, memory).

      8. Append { [[Key]]: key, [[Value]]: @@ -8146,9 +8185,9 @@ interface DOMStringList {

        It's important to realize that the Records - produced by StructuredSerialize might contain "pointers" to other records that - create circular references. For example, when we pass the following JavaScript object into - StructuredSerialize:

        + 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;
        @@ -8167,13 +8206,30 @@ o.myself = o;
        +

        StructuredSerialize ( value )

        + +
          +
        1. Return ? StructuredSerializeInternal(value, false).

        2. +
        + + +

        StructuredSerializeForStorage ( value )

        + +
          +
        1. Return ? StructuredSerializeInternal(value, true).

        2. +
        + +

        StructuredDeserialize ( serialized, targetRealm [ , memory ] )

        The StructuredDeserialize abstract operation takes as input a Record - serialized, which was previously produced by StructuredSerialize, and - deserializes it into a new JavaScript value, created in targetRealm.

        + 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).

        @@ -8264,11 +8320,29 @@ o.myself = o; serialized.[[OriginalFlags]].

      9. -

        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 +

        Otherwise, if serialized.[[Type]] is "SharedArrayBuffer", then:

        + +
          +
        1. If targetRealm's corresponding agent cluster is not + serialized.[[AgentCluster]], then then throw a + "DataCloneError" DOMException.

        2. + +
        3. 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]].

        4. +
        +
      10. + +
      11. +

        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, then throw a "DataCloneError" + DOMException.

        +

        This step might throw an exception if there is not enough memory available to create such an ArrayBuffer object.

      12. @@ -8463,9 +8537,10 @@ o.myself = o;
      13. Let memory be an empty map.

        -

        In addition to how it is used normally by StructuredSerialize, in - this algorithm memory is also used to ensure that StructuredSerialize - ignores items in transferList, and let us do our own handling instead.

        +

        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.

      14. @@ -8477,7 +8552,8 @@ o.myself = o; [[Detached]] internal slot, then throw a "DataCloneError" DOMException.

      15. -
      16. If transferable has an [[ArrayBufferData]] internal slot and ! +

      17. If transferable has an [[ArrayBufferData]] internal slot and either ! + IsSharedArrayBuffer(transferable) is true or ! IsDetachedBuffer(transferable) is true, then throw a "DataCloneError" DOMException.

      18. @@ -8492,8 +8568,8 @@ o.myself = o;
      -
    7. Let serialized be ? StructuredSerialize(input, - memory).

    8. +
    9. Let serialized be ? StructuredSerializeInternal(input, + false, memory).

    10. Let transferDataHolders be a new empty List.

    11. @@ -8607,15 +8683,23 @@ o.myself = o;
      StructuredSerialize
      +
      StructuredSerializeForStorage
      StructuredDeserialize

      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 - StructuredSerialize on author-supplied state objects, storing them as + 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 @@ -8627,8 +8711,9 @@ o.myself = o; 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 likely use - these.

      +

      Any API for persisting JavaScript values to the filesystem would also use + StructuredSerializeForStorage on its input and StructuredDeserialize + on its output.

    @@ -8645,17 +8730,18 @@ o.myself = o;

    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 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 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 - StructuredSerialize on its arguments, but is careful to do so immediately, inside the - synchronous portion of its algorithm. Thus it is able to use the structured cloning algorithms - without needing to prepare to run script and prepare to run a + 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 @@ -80497,9 +80583,9 @@ callback FrameRequestCallback = void (DOMHighResTimeStamp

    Serialized state is a serialization (via - StructuredSerialize) of an object representing a user interface state. We sometimes - informally refer to "state objects", which are the objects representing user interface state - supplied by the author, or alternately the objects created by deserializing (via + StructuredSerializeForStorage) of an object representing a user interface state. We + sometimes informally refer to "state objects", which are the objects representing user interface + state supplied by the author, or alternately the objects created by deserializing (via StructuredDeserialize) serialized state.

    Pages can add serialized state to the @@ -80888,8 +80974,8 @@ interface History {

  • Let targetRealm be this History object's relevant Realm.

  • -
  • Let serializedData be StructuredSerialize(data). - Rethrow any exceptions.

  • +
  • Let serializedData be + StructuredSerializeForStorage(data). Rethrow any exceptions.