- From: Domenic Denicola <notifications@github.com>
- Date: Thu, 09 Jul 2020 12:17:53 -0700
- To: whatwg/streams <streams@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <whatwg/streams/pull/1053/review/445883331@github.com>
@domenic commented on this pull request.
Mostly-editorial feedback. It's great to see this coming together!
> @@ -745,6 +755,36 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission
immutable, this could allow interference between the two branches.
</dl>
+<div algorithm="ReadableStream transfer steps">
I'd prefer to put these in a new section at the bottom, like asynchronous iteration.
> @@ -745,6 +755,36 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission
immutable, this could allow interference between the two branches.
</dl>
+<div algorithm="ReadableStream transfer steps">
+{{ReadableStream}} objects are [=transferable objects=]. Their [=transfer steps=], given |value| and
+|dataHolder|, are:
+
+ 1. [=Create a new MessagePort object=] whose [=owner=] is the [=incumbent settings object=] and let
Can we avoid using incumbent in new code? Probably the relevant settings object of |value| would be ideal.
> @@ -745,6 +755,36 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission
immutable, this could allow interference between the two branches.
</dl>
+<div algorithm="ReadableStream transfer steps">
+{{ReadableStream}} objects are [=transferable objects=]. Their [=transfer steps=], given |value| and
+|dataHolder|, are:
+
+ 1. [=Create a new MessagePort object=] whose [=owner=] is the [=incumbent settings object=] and let
Let's phrase this as "Let _port1_ be the result of creating a new MessagePortObject whose..."
> @@ -745,6 +755,36 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission
immutable, this could allow interference between the two branches.
</dl>
+<div algorithm="ReadableStream transfer steps">
+{{ReadableStream}} objects are [=transferable objects=]. Their [=transfer steps=], given |value| and
+|dataHolder|, are:
+
+ 1. [=Create a new MessagePort object=] whose [=owner=] is the [=incumbent settings object=] and let
I will write tests to see if what the spec actually does for MessageChannel...
> @@ -745,6 +755,36 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission
immutable, this could allow interference between the two branches.
</dl>
+<div algorithm="ReadableStream transfer steps">
+{{ReadableStream}} objects are [=transferable objects=]. Their [=transfer steps=], given |value| and
+|dataHolder|, are:
+
+ 1. [=Create a new MessagePort object=] whose [=owner=] is the [=incumbent settings object=] and let
+ |port1| be that object.
+ 1. [=Create a new MessagePort object=] whose [=owner=] is the [=incumbent settings object=] and let
+ |port2| be that object.
+ 1. [=Entangle=] the |port1| and |port2| objects.
+ 1. If ! [$IsReadableStreamLocked$](|value|) is true, throw a {{TypeError}} exception.
Should we do this earlier to avoid unnecessary work?
> @@ -745,6 +755,36 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission
immutable, this could allow interference between the two branches.
</dl>
+<div algorithm="ReadableStream transfer steps">
+{{ReadableStream}} objects are [=transferable objects=]. Their [=transfer steps=], given |value| and
+|dataHolder|, are:
+
+ 1. [=Create a new MessagePort object=] whose [=owner=] is the [=incumbent settings object=] and let
+ |port1| be that object.
+ 1. [=Create a new MessagePort object=] whose [=owner=] is the [=incumbent settings object=] and let
+ |port2| be that object.
+ 1. [=Entangle=] the |port1| and |port2| objects.
+ 1. If ! [$IsReadableStreamLocked$](|value|) is true, throw a {{TypeError}} exception.
+ 1. Let |writable| be a [=new=] {{WritableStream}}.
+ 1. Perform ! [$SetUpCrossRealmTransformWritable$](|writable|, |port1|).
+ 1. Let |promise| be ! [$ReadableStreamPipeTo$](|value|, |writable|, false, false, false, undefined).
No need to pass undefined, it seems.
> +<div algorithm="ReadableStream transfer steps">
+{{ReadableStream}} objects are [=transferable objects=]. Their [=transfer steps=], given |value| and
+|dataHolder|, are:
+
+ 1. [=Create a new MessagePort object=] whose [=owner=] is the [=incumbent settings object=] and let
+ |port1| be that object.
+ 1. [=Create a new MessagePort object=] whose [=owner=] is the [=incumbent settings object=] and let
+ |port2| be that object.
+ 1. [=Entangle=] the |port1| and |port2| objects.
+ 1. If ! [$IsReadableStreamLocked$](|value|) is true, throw a {{TypeError}} exception.
+ 1. Let |writable| be a [=new=] {{WritableStream}}.
+ 1. Perform ! [$SetUpCrossRealmTransformWritable$](|writable|, |port1|).
+ 1. Let |promise| be ! [$ReadableStreamPipeTo$](|value|, |writable|, false, false, false, undefined).
+ 1. Set |promise|.\[[PromiseIsHandled]] to true.
+ 1. Let |messagePortDataHolder| be { \[[Type]]: `"MessagePort"` }.
+ 1. Perform {{MessagePort}}'s transfer steps <!-- TODO(ricea): Link this somehow --> with |port2|
I think it would work to do
1. Set _dataHolder_.[[port]] [StructuredSerializeWithTransfer](https://html.spec.whatwg.org/#structuredserializewithtransfer)(undefined, « |port2| »).[[TransferDataHolders]][0].
(or some expanded version of that for clarity)
> + 1. Let |writable| be a [=new=] {{WritableStream}}.
+ 1. Perform ! [$SetUpCrossRealmTransformWritable$](|writable|, |port1|).
+ 1. Let |promise| be ! [$ReadableStreamPipeTo$](|value|, |writable|, false, false, false, undefined).
+ 1. Set |promise|.\[[PromiseIsHandled]] to true.
+ 1. Let |messagePortDataHolder| be { \[[Type]]: `"MessagePort"` }.
+ 1. Perform {{MessagePort}}'s transfer steps <!-- TODO(ricea): Link this somehow --> with |port2|
+ and |messagePortDataHolder|.
+ 1. Set |dataHolder|.\[[port]] to |messagePortDataHolder|.
+
+</div>
+
+<div algorithm="ReadableStream transfer receiving steps">
+Their [=transfer-receiving steps=], given |dataHolder| and |value|, are:
+
+ 1. Let |port| be a new {{MessagePort}}.
+ 1. Perform {{MessagePort}}'s transfer-receiving steps with |dataHolder|.\[[port]] and |port|.
Similarly these seems best done with a call to https://html.spec.whatwg.org/#structureddeserializewithtransfer
> @@ -745,6 +755,36 @@ option. If {{UnderlyingSource/type}} is set to undefined (including via omission
immutable, this could allow interference between the two branches.
</dl>
+<div algorithm="ReadableStream transfer steps">
+{{ReadableStream}} objects are [=transferable objects=]. Their [=transfer steps=], given |value| and
+|dataHolder|, are:
+
+ 1. [=Create a new MessagePort object=] whose [=owner=] is the [=incumbent settings object=] and let
+ |port1| be that object.
+ 1. [=Create a new MessagePort object=] whose [=owner=] is the [=incumbent settings object=] and let
+ |port2| be that object.
+ 1. [=Entangle=] the |port1| and |port2| objects.
+ 1. If ! [$IsReadableStreamLocked$](|value|) is true, throw a {{TypeError}} exception.
I wonder if "DataCloneError" DOMException would be more idiomatic.
> @@ -4894,6 +4972,37 @@ side=], or to terminate or error the stream.
<p>Returns a {{WritableStream}} representing the [=writable side=] of this transform stream.
</dl>
+<div algorithm="TransformStream transfer steps">
+{{TransformStream}} objects are [=transferable objects=]. Their [=transfer steps=], given |value|
+and |dataHolder|, are:
+
+<!-- Find a way to linkify ReadableStream transfer steps and WritableStream transfer steps -->
+
+ 1. If ! [$IsReadableStreamLocked$](|value|.\[[readable]]) is true, throw a {{TypeError}} exception.
+ 1. If ! [$IsWritableStreamLocked$](|value|.\[[writable]]) is true, throw a {{TypeError}} exception.
+ 1. Let |readableDataHolder| be { \[[Type]]: `"ReadableStream"` }.
Is the [[Type]] tagging used anywhere? I have this question generally, although in this case it seems particularly clear that it's not used.
> + 1. Perform {{WritableStream}} transfer steps with |value|.\[[writable]] and |writableDataHolder|.
+ 1. Set |dataHolder|.\[[writable]] to |writableDataHolder|.
+
+</div>
+
+<div algorithm="TransformStream transfer receiving steps">
+Their [=transfer-receiving steps=], given |dataHolder| and |value|, are:
+
+ 1. Perform {{ReadableStream}} transfer-receiving steps given |dataHolder|.\[[readable]] and
+ |value|.\[[readable]].
+ 1. Perform {{WritableStream}} transfer-receiving steps given |dataHolder|.\[[writable]] and
+ |value|.\[[writable]].
+
+<p class="note">These steps intentionally leave the \[[backpressure]],
+\[[backpressureChangePromise]] and \[[transformStreamController]] slots uninitialized. They are not
+used in a transferred TransformStream.</p>
I'm not sure that we have a well-defined concept of "uninitialized". Every other path in the spec sets all internal slots on new objects, I believe. So I think it'd be better to explicitly set them to undefined.
> @@ -15,6 +15,10 @@ Markup Shorthands: markdown yes
spec:webidl; type:dfn; text:resolve
spec:webidl; type:dfn; text:new
spec:infra; type:dfn; text:list
+spec:html; type:dfn; text:entangle
I'm surprised these are needed; there are really other specs that define "create a new MessagePort object" and friends?
Well, hopefully we can eliminate these, even if it requires some HTML edits. We'll see.
> @@ -5740,6 +5849,146 @@ for="value-with-size">value</dfn> and <dfn for="value-with-size">size</dfn>.
1. Set |container|.\[[queueTotalSize]] to 0.
</div>
+<h3 id="transferrable-streams">Transferable streams</h3>
+
+Transferable streams are implemented using a special kind of identity transform which has the
+[=writable side=] in one realm and the [=readable side=] in another realm. The following abstract
+operations are used to implement these "cross-realm transforms".
+
+<div algorithm>
+ <dfn abstract-op lt="PackAndPostMessage">PackAndPostMessage(|port|, |type|, |value|)</dfn> performs
+ the following steps:
+
+ 1. Let |message| be [$OrdinaryObjectCreate$](null).
+ 1. Perform [$CreateDataProperty$](|message|, `"type"`, |type|).
```suggestion
1. Perform [$CreateDataProperty$](|message|, "`type`", |type|).
```
and elsewhere
> @@ -5740,6 +5849,146 @@ for="value-with-size">value</dfn> and <dfn for="value-with-size">size</dfn>.
1. Set |container|.\[[queueTotalSize]] to 0.
</div>
+<h3 id="transferrable-streams">Transferable streams</h3>
+
+Transferable streams are implemented using a special kind of identity transform which has the
+[=writable side=] in one realm and the [=readable side=] in another realm. The following abstract
+operations are used to implement these "cross-realm transforms".
+
+<div algorithm>
+ <dfn abstract-op lt="PackAndPostMessage">PackAndPostMessage(|port|, |type|, |value|)</dfn> performs
+ the following steps:
+
+ 1. Let |message| be [$OrdinaryObjectCreate$](null).
+ 1. Perform [$CreateDataProperty$](|message|, `"type"`, |type|).
+ 1. Perform [$CreateDataProperty$](|message|, `"value"`, |value|).
+ 1. Let |targetPort| be the port with which |port| is entangled, if any; otherwise let it be null.
+ 1. Let |options| be «[ `"transfer"` → « » ]».
It'd be ideal if HTML made this linkable; TODO for me.
> +Transferable streams are implemented using a special kind of identity transform which has the
+[=writable side=] in one realm and the [=readable side=] in another realm. The following abstract
+operations are used to implement these "cross-realm transforms".
+
+<div algorithm>
+ <dfn abstract-op lt="PackAndPostMessage">PackAndPostMessage(|port|, |type|, |value|)</dfn> performs
+ the following steps:
+
+ 1. Let |message| be [$OrdinaryObjectCreate$](null).
+ 1. Perform [$CreateDataProperty$](|message|, `"type"`, |type|).
+ 1. Perform [$CreateDataProperty$](|message|, `"value"`, |value|).
+ 1. Let |targetPort| be the port with which |port| is entangled, if any; otherwise let it be null.
+ 1. Let |options| be «[ `"transfer"` → « » ]».
+ 1. Run the [=message port post message steps=] providing |targetPort|, |message| and |options|.
+
+ <p class="note">A JavaScript object is used for transfer to avoid having to duplicate the [=message
Hmm. Although I don't think the duplication would be that bad, I think a JS object would still have to be involved, so I think this is the best we can do, indeed.
> +operations are used to implement these "cross-realm transforms".
+
+<div algorithm>
+ <dfn abstract-op lt="PackAndPostMessage">PackAndPostMessage(|port|, |type|, |value|)</dfn> performs
+ the following steps:
+
+ 1. Let |message| be [$OrdinaryObjectCreate$](null).
+ 1. Perform [$CreateDataProperty$](|message|, `"type"`, |type|).
+ 1. Perform [$CreateDataProperty$](|message|, `"value"`, |value|).
+ 1. Let |targetPort| be the port with which |port| is entangled, if any; otherwise let it be null.
+ 1. Let |options| be «[ `"transfer"` → « » ]».
+ 1. Run the [=message port post message steps=] providing |targetPort|, |message| and |options|.
+
+ <p class="note">A JavaScript object is used for transfer to avoid having to duplicate the [=message
+ port post message steps=]. The prototype of the object is set to null to avoid interference from
+ the global object.</p>
"interference from `Object.prototype`" is probably more accurate.
> + 1. Let |result| be [$PackAndPostMessage$](|port|, |type|, |value|).
+ 1. If |result| is an abrupt completion,
+ 1. Perform ! [$CrossRealmTransformSendError$](|port|, |result|.\[[Value]]).
+ 1. Return [=a promise rejected with=] |result|.\[[Value]].
+ 1. Otherwise, return [=a promise resolved with=] undefined.
+
+</div>
+
+<div algorithm>
+ <dfn abstract-op lt="CrossRealmTransformSendError">CrossRealmTransformSendError(|port|,
+ |error|)</dfn> performs the following steps:
+
+ 1. Perform [$PackAndPostMessage$](|port|, `"error"`, |error|), discarding the result.
+
+ <p class="note">As we are already in an errored state when this abstract operation is performed, we
+ cannot handle further errors, so we just discard them.</p>
Should we set [[PromiseIsHandled]] to true?
> @@ -5740,6 +5849,146 @@ for="value-with-size">value</dfn> and <dfn for="value-with-size">size</dfn>.
1. Set |container|.\[[queueTotalSize]] to 0.
</div>
+<h3 id="transferrable-streams">Transferable streams</h3>
+
+Transferable streams are implemented using a special kind of identity transform which has the
+[=writable side=] in one realm and the [=readable side=] in another realm. The following abstract
+operations are used to implement these "cross-realm transforms".
+
+<div algorithm>
+ <dfn abstract-op lt="PackAndPostMessage">PackAndPostMessage(|port|, |type|, |value|)</dfn> performs
+ the following steps:
+
+ 1. Let |message| be [$OrdinaryObjectCreate$](null).
Put `!`s before all the infallible abstract ops. Generally every abstract op call that you're not going to explicitly handle needs a ! or ?.
> +
+ 1. Perform [$PackAndPostMessage$](|port|, `"error"`, |error|), discarding the result.
+
+ <p class="note">As we are already in an errored state when this abstract operation is performed, we
+ cannot handle further errors, so we just discard them.</p>
+
+</div>
+
+<div algorithm>
+ <dfn abstract-op lt="SetUpCrossRealmTransformReadable">SetUpCrossRealmTransformReadable(|stream|,
+ |port|)</dfn> performs the following steps:
+
+ 1. Perform ! [$InitializeReadableStream$](|stream|).
+ 1. Let |controller| be a [=new=] {{ReadableStreamDefaultController}}.
+ 1. Let |backpressurePromise| be [=a new promise=].
+ 1. Add a handler for |port|'s {{MessagePort/message}} event with the following steps:
"Add a handler" is not well-defined. I've run into this problem before; you need something like https://wicg.github.io/kv-storage/#add-a-simple-event-listener. Maybe we can upstream that into DOM.
> +
+ <p class="note">As we are already in an errored state when this abstract operation is performed, we
+ cannot handle further errors, so we just discard them.</p>
+
+</div>
+
+<div algorithm>
+ <dfn abstract-op lt="SetUpCrossRealmTransformReadable">SetUpCrossRealmTransformReadable(|stream|,
+ |port|)</dfn> performs the following steps:
+
+ 1. Perform ! [$InitializeReadableStream$](|stream|).
+ 1. Let |controller| be a [=new=] {{ReadableStreamDefaultController}}.
+ 1. Let |backpressurePromise| be [=a new promise=].
+ 1. Add a handler for |port|'s {{MessagePort/message}} event with the following steps:
+ 1. Let |data| be the data of the message.
+ 1. If ! [$Type$](|data|) is not Object, return.
How could these bad cases happen?
> + <p class="note">As we are already in an errored state when this abstract operation is performed, we
+ cannot handle further errors, so we just discard them.</p>
+
+</div>
+
+<div algorithm>
+ <dfn abstract-op lt="SetUpCrossRealmTransformReadable">SetUpCrossRealmTransformReadable(|stream|,
+ |port|)</dfn> performs the following steps:
+
+ 1. Perform ! [$InitializeReadableStream$](|stream|).
+ 1. Let |controller| be a [=new=] {{ReadableStreamDefaultController}}.
+ 1. Let |backpressurePromise| be [=a new promise=].
+ 1. Add a handler for |port|'s {{MessagePort/message}} event with the following steps:
+ 1. Let |data| be the data of the message.
+ 1. If ! [$Type$](|data|) is not Object, return.
+ 1. Let |type| be ? [$Get$](|data|, `"type"`).
Should exceptions in these cases error the stream, or be thrown? If they're thrown, I guess they'll be handled like any event listener error, and go to `window.onerror`? Do these cases have tests?
> + 1. Let |controller| be a [=new=] {{ReadableStreamDefaultController}}.
+ 1. Let |backpressurePromise| be [=a new promise=].
+ 1. Add a handler for |port|'s {{MessagePort/message}} event with the following steps:
+ 1. Let |data| be the data of the message.
+ 1. If ! [$Type$](|data|) is not Object, return.
+ 1. Let |type| be ? [$Get$](|data|, `"type"`).
+ 1. Let |value| be ? [$Get$](|data|, `"value"`).
+ 1. If ! [$Type$](|type|) is not String, return.
+ 1. If |type| is `"chunk"`,
+ 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|controller|) is true,
+ 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](|controller|, |value|).
+ 1. [=Resolve=] |backpressurePromise| with undefined.
+ 1. Set |backpressurePromise| to [=a new promise=].
+ 1. Otherwise, if |type| is `"close"`,
+ 1. Perform ! [$ReadableStreamDefaultControllerClose$](|controller|).
+ 1. Disentangle |port|.
Wow, it's weird that this is not a defined term.
> + 1. Add a handler for |port|'s {{MessagePort/message}} event with the following steps:
+ 1. Let |data| be the data of the message.
+ 1. If ! [$Type$](|data|) is not Object, return.
+ 1. Let |type| be ? [$Get$](|data|, `"type"`).
+ 1. Let |value| be ? [$Get$](|data|, `"value"`).
+ 1. If ! [$Type$](|type|) is not String, return.
+ 1. If |type| is `"chunk"`,
+ 1. If ! [$ReadableStreamDefaultControllerCanCloseOrEnqueue$](|controller|) is true,
+ 1. Perform ! [$ReadableStreamDefaultControllerEnqueue$](|controller|, |value|).
+ 1. [=Resolve=] |backpressurePromise| with undefined.
+ 1. Set |backpressurePromise| to [=a new promise=].
+ 1. Otherwise, if |type| is `"close"`,
+ 1. Perform ! [$ReadableStreamDefaultControllerClose$](|controller|).
+ 1. Disentangle |port|.
+ 1. Otherwise, if |type| is `"error"`,
+ 1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |value|)
```suggestion
1. Perform ! [$ReadableStreamDefaultControllerError$](|controller|, |value|).
```
--
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/streams/pull/1053#pullrequestreview-445883331
Received on Thursday, 9 July 2020 19:18:08 UTC