Skip to content

Commit cada812

Browse files
Support teeing readable byte streams
Teeing a readable byte stream will now create two readable byte streams (instead of two "default" readable streams). Fixes #1111. This implements the "hard" solution mentioned there: when a branch is being read using a BYOB reader, the view of that read request is forwarded to the original stream, so it can read directly into that view. After the view is filled, an identical copy is created and enqueued on the other branch, so they both see the same data.
1 parent c46742f commit cada812

File tree

5 files changed

+554
-46
lines changed

5 files changed

+554
-46
lines changed

index.bs

Lines changed: 260 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -764,8 +764,9 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission
764764
resulting branches; a composite cancellation reason will then be propagated to the stream's
765765
[=underlying source=].
766766

767-
<p>Note that the [=chunks=] seen in each branch will be the same object. If the chunks are not
768-
immutable, this could allow interference between the two branches.
767+
<p>If this stream is a [=readable byte stream=], then each branch will receive its own copy of
768+
each [=chunk=]. If not, then the chunks seen in each branch will be the same object.
769+
If the chunks are not immutable, this could allow interference between the two branches.
769770
</dl>
770771

771772
<div algorithm>
@@ -1789,18 +1790,7 @@ has the following [=struct/items=]:
17891790
The <dfn id="rbs-controller-byob-request" attribute
17901791
for="ReadableByteStreamController">byobRequest</dfn> getter steps are:
17911792

1792-
1. If [=this=].[=ReadableByteStreamController/[[byobRequest]]=] is null and [=this=].[=ReadableByteStreamController/[[pendingPullIntos]]=] is not [=list/is
1793-
empty|empty=],
1794-
1. Let |firstDescriptor| be [=this=].[=ReadableByteStreamController/[[pendingPullIntos]]=][0].
1795-
1. Let |view| be ! [$Construct$]({{%Uint8Array%}}, « |firstDescriptor|'s [=pull-into
1796-
descriptor/buffer=], |firstDescriptor|'s [=pull-into descriptor/byte offset=] +
1797-
|firstDescriptor|'s [=pull-into descriptor/bytes filled=], |firstDescriptor|'s [=pull-into
1798-
descriptor/byte length=] − |firstDescriptor|'s [=pull-into descriptor/bytes filled=] »).
1799-
1. Let |byobRequest| be a [=new=] {{ReadableStreamBYOBRequest}}.
1800-
1. Set |byobRequest|.[=ReadableStreamBYOBRequest/[[controller]]=] to [=this=].
1801-
1. Set |byobRequest|.[=ReadableStreamBYOBRequest/[[view]]=] to |view|.
1802-
1. Set [=this=].[=ReadableByteStreamController/[[byobRequest]]=] to |byobRequest|.
1803-
1. Return [=this=].[=ReadableByteStreamController/[[byobRequest]]=].
1793+
1. Return ! [$ReadableByteStreamControllerGetBYOBRequest$]([=this=]).
18041794
</div>
18051795

18061796
<div algorithm>
@@ -2053,6 +2043,21 @@ The following abstract operations operate on {{ReadableStream}} instances at a h
20532043
|startAlgorithm| throws.
20542044
</div>
20552045

2046+
<div algorithm>
2047+
<dfn abstract-op lt="CreateReadableByteStream">CreateReadableByteStream(|startAlgorithm|,
2048+
|pullAlgorithm|, |cancelAlgorithm|)</dfn> performs the following steps:
2049+
2050+
1. Let |stream| be a [=new=] {{ReadableStream}}.
2051+
1. Perform ! [$InitializeReadableStream$](|stream|).
2052+
1. Let |controller| be a [=new=] {{ReadableByteStreamController}}.
2053+
1. Perform ? [$SetUpReadableByteStreamController$](|stream|, |controller|, |startAlgorithm|,
2054+
|pullAlgorithm|, |cancelAlgorithm|, 0, undefined).
2055+
1. Return |stream|.
2056+
2057+
<p class="note">This abstract operation will throw an exception if and only if the supplied
2058+
|startAlgorithm| throws.
2059+
</div>
2060+
20562061
<div algorithm>
20572062
<dfn abstract-op lt="InitializeReadableStream"
20582063
id="initialize-readable-stream">InitializeReadableStream(|stream|)</dfn> performs the following
@@ -2211,11 +2216,25 @@ create them does not matter.
22112216
objects|transferring=] their [=chunks=]. However, it does introduce a noticeable asymmetry between
22122217
the two branches, and limits the possible [=chunks=] to serializable ones. [[!HTML]]
22132218

2219+
If |stream| is a [=readable byte stream=], then |cloneForBranch2| is ignored and chunks are cloned
2220+
unconditionally.
2221+
22142222
<p class="note">In this standard ReadableStreamTee is always called with |cloneForBranch2| set to
22152223
false; other specifications pass true via the [=ReadableStream/tee=] wrapper algorithm.
22162224

22172225
It performs the following steps:
22182226

2227+
1. Assert: |stream| [=implements=] {{ReadableStream}}.
2228+
1. Assert: |cloneForBranch2| is a boolean.
2229+
1. If |stream|.[=ReadableStream/[[controller]]=] [=implements=] {{ReadableByteStreamController}},
2230+
return ? [$ReadableByteStreamTee$](|stream|).
2231+
1. Return ? [$ReadableStreamDefaultTee$](|stream|, |cloneForBranch2|).
2232+
</div>
2233+
2234+
<div algorithm>
2235+
<dfn abstract-op lt="ReadableStreamDefaultTee">ReadableStreamDefaultTee(|stream|,
2236+
|cloneForBranch2|)</dfn> performs the following steps:
2237+
22192238
1. Assert: |stream| [=implements=] {{ReadableStream}}.
22202239
1. Assert: |cloneForBranch2| is a boolean.
22212240
1. Let |reader| be ? [$AcquireReadableStreamDefaultReader$](|stream|).
@@ -2231,19 +2250,25 @@ create them does not matter.
22312250
1. If |reading| is true, return [=a promise resolved with=] undefined.
22322251
1. Set |reading| to true.
22332252
1. Let |readRequest| be a [=read request=] with the following [=struct/items=]:
2234-
: [=read request/chunk steps=], given |value|
2253+
: [=read request/chunk steps=], given |chunk|
22352254
::
22362255
1. [=Queue a microtask=] to perform the following steps:
22372256
1. Set |reading| to false.
2238-
1. Let |value1| and |value2| be |value|.
2239-
1. If |canceled2| is false and |cloneForBranch2| is true, set |value2| to ?
2240-
[$StructuredDeserialize$](? [$StructuredSerialize$](|value2|), [=the current Realm=]).
2241-
1. If |canceled1| is false, perform ?
2257+
1. Let |chunk1| and |chunk2| be |chunk|.
2258+
1. If |canceled2| is false and |cloneForBranch2| is true,
2259+
1. Let |cloneResult| be [$StructuredClone$](|chunk2|).
2260+
1. If |cloneResult| is an abrupt completion,
2261+
1. Perform ! [$ReadableStreamDefaultControllerError$](|branch1|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]).
2262+
1. Perform ! [$ReadableStreamDefaultControllerError$](|branch2|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]).
2263+
1. [=Resolve=] |cancelPromise| with ! [$ReadableStreamCancel$](|stream|, |cloneResult|.\[[Value]]).
2264+
1. Return.
2265+
1. Otherwise, set |chunk2| to |cloneResult|.\[[Value]].
2266+
1. If |canceled1| is false, perform !
22422267
[$ReadableStreamDefaultControllerEnqueue$](|branch1|.[=ReadableStream/[[controller]]=],
2243-
|value1|).
2244-
1. If |canceled2| is false, perform ?
2268+
|chunk1|).
2269+
1. If |canceled2| is false, perform !
22452270
[$ReadableStreamDefaultControllerEnqueue$](|branch2|.[=ReadableStream/[[controller]]=],
2246-
|value2|).
2271+
|chunk2|).
22472272

22482273
<p class="note">The microtask delay here is necessary because it takes at least a microtask to
22492274
detect errors, when we use |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] below.
@@ -2295,6 +2320,182 @@ create them does not matter.
22952320
1. Return « |branch1|, |branch2| ».
22962321
</div>
22972322

2323+
<div algorithm>
2324+
<dfn abstract-op lt="ReadableByteStreamTee">ReadableByteStreamTee(|stream|)</dfn>
2325+
performs the following steps:
2326+
2327+
1. Assert: |stream| [=implements=] {{ReadableStream}}.
2328+
1. Assert: |stream|.[=ReadableStream/[[controller]]=] [=implements=]
2329+
{{ReadableByteStreamController}}.
2330+
1. Let |reader| be ? [$AcquireReadableStreamDefaultReader$](|stream|).
2331+
1. Let |reading| be false.
2332+
1. Let |canceled1| be false.
2333+
1. Let |canceled2| be false.
2334+
1. Let |reason1| be undefined.
2335+
1. Let |reason2| be undefined.
2336+
1. Let |branch1| be undefined.
2337+
1. Let |branch2| be undefined.
2338+
1. Let |cancelPromise| be [=a new promise=].
2339+
1. Let |forwardReaderError| be the following steps, taking a |thisReader| argument:
2340+
1. [=Upon rejection=] of |thisReader|.[=ReadableStreamGenericReader/[[closedPromise]]=] with reason
2341+
|r|,
2342+
1. If |thisReader| is not |reader|, return.
2343+
1. Perform ! [$ReadableByteStreamControllerError$](|branch1|.[=ReadableStream/[[controller]]=],
2344+
|r|).
2345+
1. Perform ! [$ReadableByteStreamControllerError$](|branch2|.[=ReadableStream/[[controller]]=],
2346+
|r|).
2347+
1. If |canceled1| is false or |canceled2| is false, [=resolve=] |cancelPromise| with undefined.
2348+
1. Let |pullWithDefaultReader| be the following steps:
2349+
1. If |reader| [=implements=] {{ReadableStreamBYOBReader}},
2350+
1. Assert: |reader|.[=ReadableStreamBYOBReader/[[readIntoRequests]]=] is [=list/is empty|empty=].
2351+
1. Perform ! [$ReadableStreamReaderGenericRelease$](|reader|).
2352+
1. Set |reader| to ! [$AcquireReadableStreamDefaultReader$](|stream|).
2353+
1. Perform |forwardReaderError|, given |reader|.
2354+
1. Let |readRequest| be a [=read request=] with the following [=struct/items=]:
2355+
: [=read request/chunk steps=], given |chunk|
2356+
::
2357+
1. [=Queue a microtask=] to perform the following steps:
2358+
1. Set |reading| to false.
2359+
1. Let |chunk1| and |chunk2| be |chunk|.
2360+
1. If |canceled1| is false and |canceled2| is false,
2361+
1. Let |cloneResult| be [$CloneAsUint8Array$](|chunk|).
2362+
1. If |cloneResult| is an abrupt completion,
2363+
1. Perform ! [$ReadableByteStreamControllerError$](|branch1|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]).
2364+
1. Perform ! [$ReadableByteStreamControllerError$](|branch2|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]).
2365+
1. [=Resolve=] |cancelPromise| with ! [$ReadableStreamCancel$](|stream|, |cloneResult|.\[[Value]]).
2366+
1. Return.
2367+
1. Otherwise, set |chunk2| to |cloneResult|.\[[Value]].
2368+
1. If |canceled1| is false, perform !
2369+
[$ReadableByteStreamControllerEnqueue$](|branch1|.[=ReadableStream/[[controller]]=],
2370+
|chunk1|).
2371+
1. If |canceled2| is false, perform !
2372+
[$ReadableByteStreamControllerEnqueue$](|branch2|.[=ReadableStream/[[controller]]=],
2373+
|chunk2|).
2374+
2375+
<p class="note">The microtask delay here is necessary because it takes at least a microtask to
2376+
detect errors, when we use |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] below.
2377+
We want errors in |stream| to error both branches immediately, so we cannot let successful
2378+
synchronously-available reads happen ahead of asynchronously-available errors.
2379+
2380+
: [=read request/close steps=]
2381+
::
2382+
1. Set |reading| to false.
2383+
1. If |canceled1| is false, perform !
2384+
[$ReadableByteStreamControllerClose$](|branch1|.[=ReadableStream/[[controller]]=]).
2385+
1. If |canceled2| is false, perform !
2386+
[$ReadableByteStreamControllerClose$](|branch2|.[=ReadableStream/[[controller]]=]).
2387+
1. If |branch1|.[=ReadableStream/[[controller]]=].[=ReadableByteStreamController/[[pendingPullIntos]]=]
2388+
is not [=list/is empty|empty=], perform !
2389+
[$ReadableByteStreamControllerRespond$](|branch1|.[=ReadableStream/[[controller]]=], 0).
2390+
1. If |branch2|.[=ReadableStream/[[controller]]=].[=ReadableByteStreamController/[[pendingPullIntos]]=]
2391+
is not [=list/is empty|empty=], perform !
2392+
[$ReadableByteStreamControllerRespond$](|branch2|.[=ReadableStream/[[controller]]=], 0).
2393+
1. If |canceled1| is false or |canceled2| is false, [=resolve=] |cancelPromise| with undefined.
2394+
2395+
: [=read request/error steps=]
2396+
::
2397+
1. Set |reading| to false.
2398+
1. Perform ! [$ReadableStreamDefaultReaderRead$](|reader|, |readRequest|).
2399+
1. Let |pullWithBYOBReader| be the following steps, given |view| and |forBranch2|:
2400+
1. If |reader| [=implements=] {{ReadableStreamDefaultReader}},
2401+
1. Assert: |reader|.[=ReadableStreamDefaultReader/[[readRequests]]=] is [=list/is empty|empty=].
2402+
1. Perform ! [$ReadableStreamReaderGenericRelease$](|reader|).
2403+
1. Set |reader| to ! [$AcquireReadableStreamBYOBReader$](|stream|).
2404+
1. Perform |forwardReaderError|, given |reader|.
2405+
1. Let |byobBranch| be |branch2| if |forBranch2| is true, and |branch1| otherwise.
2406+
1. Let |otherBranch| be |branch2| if |forBranch2| is false, and |branch1| otherwise.
2407+
1. Let |readIntoRequest| be a [=read-into request=] with the following [=struct/items=]:
2408+
: [=read-into request/chunk steps=], given |chunk|
2409+
::
2410+
1. [=Queue a microtask=] to perform the following steps:
2411+
1. Set |reading| to false.
2412+
1. Let |byobCanceled| be |canceled2| if |forBranch2| is true, and |canceled1| otherwise.
2413+
1. Let |otherCanceled| be |canceled2| if |forBranch2| is false, and |canceled1| otherwise.
2414+
1. If |otherCanceled| is false,
2415+
1. Let |cloneResult| be [$CloneAsUint8Array$](|chunk|).
2416+
1. If |cloneResult| is an abrupt completion,
2417+
1. Perform ! [$ReadableByteStreamControllerError$](|byobBranch|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]).
2418+
1. Perform ! [$ReadableByteStreamControllerError$](|otherBranch|.[=ReadableStream/[[controller]]=], |cloneResult|.\[[Value]]).
2419+
1. [=Resolve=] |cancelPromise| with ! [$ReadableStreamCancel$](|stream|, |cloneResult|.\[[Value]]).
2420+
1. Return.
2421+
1. Otherwise, let |clonedChunk| be |cloneResult|.\[[Value]].
2422+
1. If |byobCanceled| is false, perform !
2423+
[$ReadableByteStreamControllerRespondWithNewView$](|byobBranch|.[=ReadableStream/[[controller]]=],
2424+
|chunk|).
2425+
1. Perform ! [$ReadableByteStreamControllerEnqueue$](|otherBranch|.[=ReadableStream/[[controller]]=],
2426+
|clonedChunk|).
2427+
1. Otherwise, if |byobCanceled| is false, perform !
2428+
[$ReadableByteStreamControllerRespondWithNewView$](|byobBranch|.[=ReadableStream/[[controller]]=],
2429+
|chunk|).
2430+
2431+
<p class="note">The microtask delay here is necessary because it takes at least a microtask to
2432+
detect errors, when we use |reader|.[=ReadableStreamGenericReader/[[closedPromise]]=] below.
2433+
We want errors in |stream| to error both branches immediately, so we cannot let successful
2434+
synchronously-available reads happen ahead of asynchronously-available errors.
2435+
2436+
: [=read-into request/close steps=], given |chunk|
2437+
::
2438+
1. Set |reading| to false.
2439+
1. Let |byobCanceled| be |canceled2| if |forBranch2| is true, and |canceled1| otherwise.
2440+
1. Let |otherCanceled| be |canceled2| if |forBranch2| is false, and |canceled1| otherwise.
2441+
1. If |byobCanceled| is false, perform !
2442+
[$ReadableByteStreamControllerClose$](|byobBranch|.[=ReadableStream/[[controller]]=]).
2443+
1. If |otherCanceled| is false, perform !
2444+
[$ReadableByteStreamControllerClose$](|otherBranch|.[=ReadableStream/[[controller]]=]).
2445+
1. If |chunk| is not undefined,
2446+
1. Assert: |chunk|.\[[ByteLength]] is 0.
2447+
1. If |byobCanceled| is false, perform !
2448+
[$ReadableByteStreamControllerRespondWithNewView$](|byobBranch|.[=ReadableStream/[[controller]]=],
2449+
|chunk|).
2450+
1. If |otherCanceled| is false and
2451+
|otherBranch|.[=ReadableStream/[[controller]]=].[=ReadableByteStreamController/[[pendingPullIntos]]=]
2452+
is not [=list/is empty|empty=], perform !
2453+
[$ReadableByteStreamControllerRespond$](|otherBranch|.[=ReadableStream/[[controller]]=], 0).
2454+
1. If |byobCanceled| is false or |otherCanceled| is false, [=resolve=] |cancelPromise| with undefined.
2455+
2456+
: [=read-into request/error steps=]
2457+
::
2458+
1. Set |reading| to false.
2459+
1. Perform ! [$ReadableStreamBYOBReaderRead$](|reader|, |view|, |readIntoRequest|).
2460+
1. Let |pull1Algorithm| be the following steps:
2461+
1. If |reading| is true, return [=a promise resolved with=] undefined.
2462+
1. Set |reading| to true.
2463+
1. Let |byobRequest| be ! [$ReadableByteStreamControllerGetBYOBRequest$](|branch1|.[=ReadableStream/[[controller]]=]).
2464+
1. If |byobRequest| is null, perform |pullWithDefaultReader|.
2465+
1. Otherwise, perform |pullWithBYOBReader|, given |byobRequest|.[=ReadableStreamBYOBRequest/[[view]]=] and false.
2466+
1. Return [=a promise resolved with=] undefined.
2467+
1. Let |pull2Algorithm| be the following steps:
2468+
1. If |reading| is true, return [=a promise resolved with=] undefined.
2469+
1. Set |reading| to true.
2470+
1. Let |byobRequest| be ! [$ReadableByteStreamControllerGetBYOBRequest$](|branch2|.[=ReadableStream/[[controller]]=]).
2471+
1. If |byobRequest| is null, perform |pullWithDefaultReader|.
2472+
1. Otherwise, perform |pullWithBYOBReader|, given |byobRequest|.[=ReadableStreamBYOBRequest/[[view]]=] and true.
2473+
1. Return [=a promise resolved with=] undefined.
2474+
1. Let |cancel1Algorithm| be the following steps, taking a |reason| argument:
2475+
1. Set |canceled1| to true.
2476+
1. Set |reason1| to |reason|.
2477+
1. If |canceled2| is true,
2478+
1. Let |compositeReason| be ! [$CreateArrayFromList$](« |reason1|, |reason2| »).
2479+
1. Let |cancelResult| be ! [$ReadableStreamCancel$](|stream|, |compositeReason|).
2480+
1. [=Resolve=] |cancelPromise| with |cancelResult|.
2481+
1. Return |cancelPromise|.
2482+
1. Let |cancel2Algorithm| be the following steps, taking a |reason| argument:
2483+
1. Set |canceled2| to true.
2484+
1. Set |reason2| to |reason|.
2485+
1. If |canceled1| is true,
2486+
1. Let |compositeReason| be ! [$CreateArrayFromList$](« |reason1|, |reason2| »).
2487+
1. Let |cancelResult| be ! [$ReadableStreamCancel$](|stream|, |compositeReason|).
2488+
1. [=Resolve=] |cancelPromise| with |cancelResult|.
2489+
1. Return |cancelPromise|.
2490+
1. Let |startAlgorithm| be an algorithm that returns undefined.
2491+
1. Set |branch1| to ! [$CreateReadableByteStream$](|startAlgorithm|, |pull1Algorithm|,
2492+
|cancel1Algorithm|).
2493+
1. Set |branch2| to ! [$CreateReadableByteStream$](|startAlgorithm|, |pull2Algorithm|,
2494+
|cancel2Algorithm|).
2495+
1. Perform |forwardReaderError|, given |reader|.
2496+
1. Return « |branch1|, |branch2| ».
2497+
</div>
2498+
22982499
<h4 id="rs-abstract-ops-used-by-controllers">Interfacing with controllers</h4>
22992500

23002501
In terms of specification factoring, the way that the {{ReadableStream}} class encapsulates the
@@ -3042,6 +3243,24 @@ The following abstract operations support the implementation of the
30423243
1. Return |ready|.
30433244
</div>
30443245

3246+
<div algorithm>
3247+
<dfn abstract-op lt="ReadableByteStreamControllerGetBYOBRequest">ReadableByteStreamControllerGetBYOBRequest(|controller|)</dfn>
3248+
performs the following steps:
3249+
3250+
1. If |controller|.[=ReadableByteStreamController/[[byobRequest]]=] is null and
3251+
|controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not [=list/is empty|empty=],
3252+
1. Let |firstDescriptor| be |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0].
3253+
1. Let |view| be ! [$Construct$]({{%Uint8Array%}}, « |firstDescriptor|'s [=pull-into
3254+
descriptor/buffer=], |firstDescriptor|'s [=pull-into descriptor/byte offset=] +
3255+
|firstDescriptor|'s [=pull-into descriptor/bytes filled=], |firstDescriptor|'s [=pull-into
3256+
descriptor/byte length=] − |firstDescriptor|'s [=pull-into descriptor/bytes filled=] »).
3257+
1. Let |byobRequest| be a [=new=] {{ReadableStreamBYOBRequest}}.
3258+
1. Set |byobRequest|.[=ReadableStreamBYOBRequest/[[controller]]=] to |controller|.
3259+
1. Set |byobRequest|.[=ReadableStreamBYOBRequest/[[view]]=] to |view|.
3260+
1. Set |controller|.[=ReadableByteStreamController/[[byobRequest]]=] to |byobRequest|.
3261+
1. Return |controller|.[=ReadableByteStreamController/[[byobRequest]]=].
3262+
</div>
3263+
30453264
<div algorithm>
30463265
<dfn abstract-op lt="ReadableByteStreamControllerGetDesiredSize"
30473266
id="readable-byte-stream-controller-get-desired-size">ReadableByteStreamControllerGetDesiredSize(|controller|)</dfn>
@@ -6202,6 +6421,25 @@ The following abstract operations are a grab-bag of utilities.
62026421
\[[ArrayBufferByteLength]] internal slot value is |arrayBufferByteLength|.
62036422
</div>
62046423

6424+
<div algorithm>
6425+
<dfn abstract-op lt="CloneAsUint8Array">CloneAsUint8Array(|O|)</dfn> performs the following steps:
6426+
6427+
1. Assert: [$Type$](|O|) is Object.
6428+
1. Assert: |O| has an \[[ViewedArrayBuffer]] internal slot.
6429+
1. Assert: ! [$IsDetachedBuffer$](|O|.\[[ViewedArrayBuffer]]) is false.
6430+
1. Let |buffer| be ? [$CloneArrayBuffer$](|O|.\[[ViewedArrayBuffer]], |O|.\[[ByteOffset]],
6431+
|O|.\[[ByteLength]], {{%ArrayBuffer%}}).
6432+
1. Let |array| be ! [$Construct$]({{%Uint8Array%}}, « |buffer| »).
6433+
1. Return |array|.
6434+
</div>
6435+
6436+
<div algorithm>
6437+
<dfn abstract-op lt="StructuredClone">StructuredClone(|v|)</dfn> performs the following steps:
6438+
6439+
1. Let |serialized| be ? [$StructuredSerialize$](|v|).
6440+
1. Return ? [$StructuredDeserialize$](|serialized|, [=the current Realm=]).
6441+
</div>
6442+
62056443
<h2 id="other-specs">Using streams in other specifications</h2>
62066444

62076445
Much of this standard concerns itself with the internal machinery of streams. Other specifications

0 commit comments

Comments
 (0)