- 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