1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "ReadableStreamPipeTo.h"
9 #include "js/RootingAPI.h"
10 #include "js/String.h"
11 #include "js/TypeDecls.h"
13 #include "mozilla/AlreadyAddRefed.h"
14 #include "mozilla/dom/DOMExceptionBinding.h"
15 #include "nsCycleCollectionParticipant.h"
16 #include "nsIDOMEventListener.h"
17 #include "nsIGlobalObject.h"
18 #include "mozilla/ErrorResult.h"
19 #include "mozilla/ResultVariant.h"
20 #include "mozilla/dom/DOMException.h"
21 #include "mozilla/dom/MessageEvent.h"
22 #include "mozilla/dom/MessageChannel.h"
23 #include "mozilla/dom/MessagePort.h"
24 #include "mozilla/dom/Promise.h"
25 #include "mozilla/dom/Promise-inl.h"
26 #include "mozilla/dom/ReadableStream.h"
27 #include "mozilla/dom/WritableStream.h"
28 #include "mozilla/dom/TransformStream.h"
29 #include "nsISupportsImpl.h"
31 namespace mozilla::dom
{
33 using namespace streams_abstract
;
35 static void PackAndPostMessage(JSContext
* aCx
, MessagePort
* aPort
,
36 const nsAString
& aType
,
37 JS::Handle
<JS::Value
> aValue
, ErrorResult
& aRv
) {
38 JS::Rooted
<JSObject
*> obj(aCx
,
39 JS_NewObjectWithGivenProto(aCx
, nullptr, nullptr));
41 // XXX: Should we crash here and there? See also bug 1762233.
42 JS_ClearPendingException(aCx
);
43 aRv
.Throw(NS_ERROR_UNEXPECTED
);
47 JS::Rooted
<JS::Value
> type(aCx
);
48 if (!xpc::NonVoidStringToJsval(aCx
, aType
, &type
)) {
49 JS_ClearPendingException(aCx
);
50 aRv
.Throw(NS_ERROR_UNEXPECTED
);
53 if (!JS_DefineProperty(aCx
, obj
, "type", type
, JSPROP_ENUMERATE
)) {
54 JS_ClearPendingException(aCx
);
55 aRv
.Throw(NS_ERROR_UNEXPECTED
);
58 JS::Rooted
<JS::Value
> value(aCx
, aValue
);
59 if (!JS_WrapValue(aCx
, &value
)) {
60 JS_ClearPendingException(aCx
);
61 aRv
.Throw(NS_ERROR_UNEXPECTED
);
64 if (!JS_DefineProperty(aCx
, obj
, "value", value
, JSPROP_ENUMERATE
)) {
65 JS_ClearPendingException(aCx
);
66 aRv
.Throw(NS_ERROR_UNEXPECTED
);
70 Sequence
<JSObject
*> transferables
; // none in this case
71 JS::Rooted
<JS::Value
> objValue(aCx
, JS::ObjectValue(*obj
));
72 aPort
->PostMessage(aCx
, objValue
, transferables
, aRv
);
75 // https://streams.spec.whatwg.org/#abstract-opdef-crossrealmtransformsenderror
76 static void CrossRealmTransformSendError(JSContext
* aCx
, MessagePort
* aPort
,
77 JS::Handle
<JS::Value
> aError
) {
78 // Step 1: Perform PackAndPostMessage(port, "error", error), discarding the
80 PackAndPostMessage(aCx
, aPort
, u
"error"_ns
, aError
, IgnoreErrors());
83 class SetUpTransformWritableMessageEventListener final
84 : public nsIDOMEventListener
{
86 SetUpTransformWritableMessageEventListener(
87 WritableStreamDefaultController
* aController
, Promise
* aPromise
)
88 : mController(aController
), mBackpressurePromise(aPromise
) {}
90 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
91 NS_DECL_CYCLE_COLLECTION_CLASS(SetUpTransformWritableMessageEventListener
)
93 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
94 // The handler steps of Step 4.
95 MOZ_CAN_RUN_SCRIPT NS_IMETHOD
HandleEvent(Event
* aEvent
) override
{
97 if (!jsapi
.Init(mController
->GetParentObject())) {
100 JSContext
* cx
= jsapi
.cx();
101 MessageEvent
* messageEvent
= aEvent
->AsMessageEvent();
102 if (NS_WARN_IF(!messageEvent
|| !messageEvent
->IsTrusted())) {
106 // Step 1: Let data be the data of the message.
107 JS::Rooted
<JS::Value
> dataValue(cx
);
108 IgnoredErrorResult rv
;
109 messageEvent
->GetData(cx
, &dataValue
, rv
);
114 // Step 2: Assert: Type(data) is Object.
115 // (But we check in runtime instead to avoid potential malicious events from
116 // a compromised process. Same below.)
117 if (NS_WARN_IF(!dataValue
.isObject())) {
120 JS::Rooted
<JSObject
*> data(cx
, &dataValue
.toObject());
122 // Step 3: Let type be ! Get(data, "type").
123 JS::Rooted
<JS::Value
> type(cx
);
124 if (!JS_GetProperty(cx
, data
, "type", &type
)) {
125 // XXX: See bug 1762233
126 JS_ClearPendingException(cx
);
130 // Step 4: Let value be ! Get(data, "value").
131 JS::Rooted
<JS::Value
> value(cx
);
132 if (!JS_GetProperty(cx
, data
, "value", &value
)) {
133 JS_ClearPendingException(cx
);
137 // Step 5: Assert: Type(type) is String.
138 if (NS_WARN_IF(!type
.isString())) {
142 // Step 6: If type is "pull",
144 if (!JS_StringEqualsLiteral(cx
, type
.toString(), "pull", &equals
)) {
145 JS_ClearPendingException(cx
);
149 // Step 6.1: If backpressurePromise is not undefined,
150 MaybeResolveAndClearBackpressurePromise();
151 return NS_OK
; // implicit
154 // Step 7: If type is "error",
155 if (!JS_StringEqualsLiteral(cx
, type
.toString(), "error", &equals
)) {
156 JS_ClearPendingException(cx
);
160 // Step 7.1: Perform !
161 // WritableStreamDefaultControllerErrorIfNeeded(controller, value).
162 WritableStreamDefaultControllerErrorIfNeeded(cx
, mController
, value
, rv
);
167 // Step 7.2: If backpressurePromise is not undefined,
168 MaybeResolveAndClearBackpressurePromise();
169 return NS_OK
; // implicit
172 // Logically it should be unreachable here, but we should expect random
173 // malicious messages.
174 NS_WARNING("Got an unexpected type other than pull/error.");
178 void MaybeResolveAndClearBackpressurePromise() {
179 if (mBackpressurePromise
) {
180 mBackpressurePromise
->MaybeResolveWithUndefined();
181 mBackpressurePromise
= nullptr;
185 // Note: This promise field is shared with the sink algorithms.
186 Promise
* BackpressurePromise() { return mBackpressurePromise
; }
188 void CreateBackpressurePromise() {
189 mBackpressurePromise
=
190 Promise::CreateInfallible(mController
->GetParentObject());
194 ~SetUpTransformWritableMessageEventListener() = default;
196 // mController never changes before CC
197 // TODO: MOZ_IMMUTABLE_OUTSIDE_CC
198 MOZ_KNOWN_LIVE RefPtr
<WritableStreamDefaultController
> mController
;
199 RefPtr
<Promise
> mBackpressurePromise
;
202 NS_IMPL_CYCLE_COLLECTION(SetUpTransformWritableMessageEventListener
,
203 mController
, mBackpressurePromise
)
204 NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformWritableMessageEventListener
)
205 NS_IMPL_CYCLE_COLLECTING_RELEASE(SetUpTransformWritableMessageEventListener
)
206 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
207 SetUpTransformWritableMessageEventListener
)
208 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener
)
211 class SetUpTransformWritableMessageErrorEventListener final
212 : public nsIDOMEventListener
{
214 SetUpTransformWritableMessageErrorEventListener(
215 WritableStreamDefaultController
* aController
, MessagePort
* aPort
)
216 : mController(aController
), mPort(aPort
) {}
218 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
219 NS_DECL_CYCLE_COLLECTION_CLASS(
220 SetUpTransformWritableMessageErrorEventListener
)
222 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
223 // The handler steps of Step 5.
224 MOZ_CAN_RUN_SCRIPT NS_IMETHOD
HandleEvent(Event
* aEvent
) override
{
226 MakeScopeExit([port
= RefPtr
<MessagePort
>(mPort
)]() { port
->Close(); });
228 if (NS_WARN_IF(!aEvent
->AsMessageEvent() || !aEvent
->IsTrusted())) {
232 // Step 1: Let error be a new "DataCloneError" DOMException.
233 RefPtr
<DOMException
> exception
=
234 DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR
);
237 if (!jsapi
.Init(mPort
->GetParentObject())) {
240 JSContext
* cx
= jsapi
.cx();
241 JS::Rooted
<JS::Value
> error(cx
);
242 if (!ToJSValue(cx
, *exception
, &error
)) {
246 // Step 2: Perform ! CrossRealmTransformSendError(port, error).
247 CrossRealmTransformSendError(cx
, mPort
, error
);
250 // ! WritableStreamDefaultControllerErrorIfNeeded(controller, error).
251 WritableStreamDefaultControllerErrorIfNeeded(cx
, mController
, error
,
254 // Step 4: Disentangle port.
257 cleanupPort
.release();
263 ~SetUpTransformWritableMessageErrorEventListener() = default;
265 // mController never changes before CC
266 // TODO: MOZ_IMMUTABLE_OUTSIDE_CC
267 MOZ_KNOWN_LIVE RefPtr
<WritableStreamDefaultController
> mController
;
268 RefPtr
<MessagePort
> mPort
;
271 NS_IMPL_CYCLE_COLLECTION(SetUpTransformWritableMessageErrorEventListener
,
273 NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformWritableMessageErrorEventListener
)
274 NS_IMPL_CYCLE_COLLECTING_RELEASE(
275 SetUpTransformWritableMessageErrorEventListener
)
276 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
277 SetUpTransformWritableMessageErrorEventListener
)
278 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener
)
281 // https://streams.spec.whatwg.org/#abstract-opdef-packandpostmessagehandlingerror
282 static bool PackAndPostMessageHandlingError(
283 JSContext
* aCx
, mozilla::dom::MessagePort
* aPort
, const nsAString
& aType
,
284 JS::Handle
<JS::Value
> aValue
, JS::MutableHandle
<JS::Value
> aError
) {
285 // Step 1: Let result be PackAndPostMessage(port, type, value).
287 PackAndPostMessage(aCx
, aPort
, aType
, aValue
, rv
);
289 // Step 2: If result is an abrupt completion,
291 // Step 2.2: Perform ! CrossRealmTransformSendError(port, result.[[Value]]).
292 MOZ_ALWAYS_TRUE(ToJSValue(aCx
, std::move(rv
), aError
));
293 CrossRealmTransformSendError(aCx
, aPort
, aError
);
297 // Step 3: Return result as a completion record.
301 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
302 class CrossRealmWritableUnderlyingSinkAlgorithms final
303 : public UnderlyingSinkAlgorithmsBase
{
305 NS_DECL_ISUPPORTS_INHERITED
306 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
307 CrossRealmWritableUnderlyingSinkAlgorithms
, UnderlyingSinkAlgorithmsBase
)
309 CrossRealmWritableUnderlyingSinkAlgorithms(
310 SetUpTransformWritableMessageEventListener
* aListener
, MessagePort
* aPort
)
311 : mListener(aListener
), mPort(aPort
) {}
313 void StartCallback(JSContext
* aCx
,
314 WritableStreamDefaultController
& aController
,
315 JS::MutableHandle
<JS::Value
> aRetVal
,
316 ErrorResult
& aRv
) override
{
317 // Step 7. Let startAlgorithm be an algorithm that returns undefined.
318 aRetVal
.setUndefined();
321 already_AddRefed
<Promise
> WriteCallback(
322 JSContext
* aCx
, JS::Handle
<JS::Value
> aChunk
,
323 WritableStreamDefaultController
& aController
, ErrorResult
& aRv
) override
{
324 // Step 1: If backpressurePromise is undefined, set backpressurePromise to a
325 // promise resolved with undefined.
326 // Note: This promise field is shared with the message event listener.
327 if (!mListener
->BackpressurePromise()) {
328 mListener
->CreateBackpressurePromise();
329 mListener
->BackpressurePromise()->MaybeResolveWithUndefined();
332 // Step 2: Return the result of reacting to backpressurePromise with the
333 // following fulfillment steps:
335 mListener
->BackpressurePromise()->ThenWithCycleCollectedArgsJS(
336 [](JSContext
* aCx
, JS::Handle
<JS::Value
>, ErrorResult
& aRv
,
337 SetUpTransformWritableMessageEventListener
* aListener
,
339 JS::Handle
<JS::Value
> aChunk
) -> already_AddRefed
<Promise
> {
340 // Step 2.1: Set backpressurePromise to a new promise.
341 aListener
->CreateBackpressurePromise();
343 // Step 2.2: Let result be PackAndPostMessageHandlingError(port,
345 JS::Rooted
<JS::Value
> error(aCx
);
346 bool result
= PackAndPostMessageHandlingError(
347 aCx
, aPort
, u
"chunk"_ns
, aChunk
, &error
);
349 // Step 2.3: If result is an abrupt completion,
351 // Step 2.3.1: Disentangle port.
355 // Step 2.3.2: Return a promise rejected with result.[[Value]].
356 return Promise::CreateRejected(aPort
->GetParentObject(), error
,
360 // Step 2.4: Otherwise, return a promise resolved with undefined.
361 return Promise::CreateResolvedWithUndefined(
362 aPort
->GetParentObject(), aRv
);
364 std::make_tuple(mListener
, mPort
), std::make_tuple(aChunk
));
365 if (result
.isErr()) {
366 aRv
.Throw(result
.unwrapErr());
369 return result
.unwrap().forget();
372 already_AddRefed
<Promise
> CloseCallback(JSContext
* aCx
,
373 ErrorResult
& aRv
) override
{
374 // Step 1: Perform ! PackAndPostMessage(port, "close", undefined).
375 PackAndPostMessage(aCx
, mPort
, u
"close"_ns
, JS::UndefinedHandleValue
, aRv
);
376 // (We'll check the result after step 2)
378 // Step 2: Disentangle port.
379 // (Close() will do this)
386 // Step 3: Return a promise resolved with undefined.
387 return Promise::CreateResolvedWithUndefined(mPort
->GetParentObject(), aRv
);
390 already_AddRefed
<Promise
> AbortCallback(
391 JSContext
* aCx
, const Optional
<JS::Handle
<JS::Value
>>& aReason
,
392 ErrorResult
& aRv
) override
{
393 // Step 1: Let result be PackAndPostMessageHandlingError(port, "error",
395 JS::Rooted
<JS::Value
> error(aCx
);
396 bool result
= PackAndPostMessageHandlingError(
397 aCx
, mPort
, u
"error"_ns
,
398 aReason
.WasPassed() ? aReason
.Value() : JS::UndefinedHandleValue
,
401 // Step 2: Disentangle port.
402 // (Close() will do this)
405 // Step 3: If result is an abrupt completion, return a promise rejected with
408 return Promise::CreateRejected(mPort
->GetParentObject(), error
, aRv
);
411 // Step 4: Otherwise, return a promise resolved with undefined.
412 return Promise::CreateResolvedWithUndefined(mPort
->GetParentObject(), aRv
);
416 ~CrossRealmWritableUnderlyingSinkAlgorithms() override
= default;
419 RefPtr
<SetUpTransformWritableMessageEventListener
> mListener
;
420 RefPtr
<MessagePort
> mPort
;
423 NS_IMPL_CYCLE_COLLECTION_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms
,
424 UnderlyingSinkAlgorithmsBase
, mListener
,
426 NS_IMPL_ADDREF_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms
,
427 UnderlyingSinkAlgorithmsBase
)
428 NS_IMPL_RELEASE_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms
,
429 UnderlyingSinkAlgorithmsBase
)
430 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
431 CrossRealmWritableUnderlyingSinkAlgorithms
)
432 NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsBase
)
434 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
435 MOZ_CAN_RUN_SCRIPT
static void SetUpCrossRealmTransformWritable(
436 WritableStream
* aWritable
, MessagePort
* aPort
, ErrorResult
& aRv
) {
437 // (This is only needed for step 12, but let's do this early to fail early
440 if (!jsapi
.Init(aWritable
->GetParentObject())) {
443 JSContext
* cx
= jsapi
.cx();
445 // Step 1: Perform ! InitializeWritableStream(stream).
446 // (Done by the constructor)
448 // Step 2: Let controller be a new WritableStreamDefaultController.
449 auto controller
= MakeRefPtr
<WritableStreamDefaultController
>(
450 aWritable
->GetParentObject(), *aWritable
);
452 // Step 3: Let backpressurePromise be a new promise.
453 RefPtr
<Promise
> backpressurePromise
=
454 Promise::CreateInfallible(aWritable
->GetParentObject());
456 // Step 4: Add a handler for port’s message event with the following steps:
457 auto listener
= MakeRefPtr
<SetUpTransformWritableMessageEventListener
>(
458 controller
, backpressurePromise
);
459 aPort
->AddEventListener(u
"message"_ns
, listener
, false);
461 // Step 5: Add a handler for port’s messageerror event with the following
464 MakeRefPtr
<SetUpTransformWritableMessageErrorEventListener
>(controller
,
466 aPort
->AddEventListener(u
"messageerror"_ns
, errorListener
, false);
468 // Step 6: Enable port’s port message queue.
474 MakeRefPtr
<CrossRealmWritableUnderlyingSinkAlgorithms
>(listener
, aPort
);
476 // Step 11: Let sizeAlgorithm be an algorithm that returns 1.
477 // (nullptr should serve this purpose. See also WritableStream::Constructor)
479 // Step 12: Perform ! SetUpWritableStreamDefaultController(stream, controller,
480 // startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, 1,
482 SetUpWritableStreamDefaultController(cx
, aWritable
, controller
, algorithms
, 1,
483 /* aSizeAlgorithm */ nullptr, aRv
);
486 class SetUpTransformReadableMessageEventListener final
487 : public nsIDOMEventListener
{
489 SetUpTransformReadableMessageEventListener(
490 ReadableStreamDefaultController
* aController
, MessagePort
* aPort
)
491 : mController(aController
), mPort(aPort
) {}
493 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
494 NS_DECL_CYCLE_COLLECTION_CLASS(SetUpTransformReadableMessageEventListener
)
496 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
497 // The handler steps of Step 3.
498 MOZ_CAN_RUN_SCRIPT NS_IMETHOD
HandleEvent(Event
* aEvent
) override
{
500 MakeScopeExit([port
= RefPtr
<MessagePort
>(mPort
)]() { port
->Close(); });
503 if (!jsapi
.Init(mPort
->GetParentObject())) {
506 JSContext
* cx
= jsapi
.cx();
507 MessageEvent
* messageEvent
= aEvent
->AsMessageEvent();
508 if (NS_WARN_IF(!messageEvent
|| !messageEvent
->IsTrusted())) {
512 // Step 1: Let data be the data of the message.
513 JS::Rooted
<JS::Value
> dataValue(cx
);
514 IgnoredErrorResult rv
;
515 messageEvent
->GetData(cx
, &dataValue
, rv
);
520 // Step 2: Assert: Type(data) is Object.
521 // (But we check in runtime instead to avoid potential malicious events from
522 // a compromised process. Same below.)
523 if (NS_WARN_IF(!dataValue
.isObject())) {
526 JS::Rooted
<JSObject
*> data(cx
, JS::ToObject(cx
, dataValue
));
528 // Step 3: Let type be ! Get(data, "type").
529 JS::Rooted
<JS::Value
> type(cx
);
530 if (!JS_GetProperty(cx
, data
, "type", &type
)) {
531 // XXX: See bug 1762233
532 JS_ClearPendingException(cx
);
536 // Step 4: Let value be ! Get(data, "value").
537 JS::Rooted
<JS::Value
> value(cx
);
538 if (!JS_GetProperty(cx
, data
, "value", &value
)) {
539 JS_ClearPendingException(cx
);
543 // Step 5: Assert: Type(type) is String.
544 if (NS_WARN_IF(!type
.isString())) {
548 // Step 6: If type is "chunk",
550 if (!JS_StringEqualsLiteral(cx
, type
.toString(), "chunk", &equals
)) {
551 JS_ClearPendingException(cx
);
555 // Step 6.1: Perform ! ReadableStreamDefaultControllerEnqueue(controller,
557 ReadableStreamDefaultControllerEnqueue(cx
, mController
, value
,
559 cleanupPort
.release();
560 return NS_OK
; // implicit
563 // Step 7: Otherwise, if type is "close",
564 if (!JS_StringEqualsLiteral(cx
, type
.toString(), "close", &equals
)) {
565 JS_ClearPendingException(cx
);
569 // Step 7.1: Perform ! ReadableStreamDefaultControllerClose(controller).
570 ReadableStreamDefaultControllerClose(cx
, mController
, IgnoreErrors());
571 // Step 7.2: Disentangle port.
574 cleanupPort
.release();
575 return NS_OK
; // implicit
578 // Step 8: Otherwise, if type is "error",
579 if (!JS_StringEqualsLiteral(cx
, type
.toString(), "error", &equals
)) {
580 JS_ClearPendingException(cx
);
584 // Step 8.1: Perform ! ReadableStreamDefaultControllerError(controller,
586 ReadableStreamDefaultControllerError(cx
, mController
, value
,
589 // Step 8.2: Disentangle port.
592 cleanupPort
.release();
593 return NS_OK
; // implicit
596 // Logically it should be unreachable here, but we should expect random
597 // malicious messages.
598 NS_WARNING("Got an unexpected type other than chunk/close/error.");
603 ~SetUpTransformReadableMessageEventListener() = default;
605 // mController never changes before CC
606 // TODO: MOZ_IMMUTABLE_OUTSIDE_CC
607 MOZ_KNOWN_LIVE RefPtr
<ReadableStreamDefaultController
> mController
;
608 RefPtr
<MessagePort
> mPort
;
611 NS_IMPL_CYCLE_COLLECTION(SetUpTransformReadableMessageEventListener
,
613 NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformReadableMessageEventListener
)
614 NS_IMPL_CYCLE_COLLECTING_RELEASE(SetUpTransformReadableMessageEventListener
)
615 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
616 SetUpTransformReadableMessageEventListener
)
617 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener
)
620 class SetUpTransformReadableMessageErrorEventListener final
621 : public nsIDOMEventListener
{
623 SetUpTransformReadableMessageErrorEventListener(
624 ReadableStreamDefaultController
* aController
, MessagePort
* aPort
)
625 : mController(aController
), mPort(aPort
) {}
627 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
628 NS_DECL_CYCLE_COLLECTION_CLASS(
629 SetUpTransformReadableMessageErrorEventListener
)
631 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
632 // The handler steps of Step 4.
633 MOZ_CAN_RUN_SCRIPT NS_IMETHOD
HandleEvent(Event
* aEvent
) override
{
635 MakeScopeExit([port
= RefPtr
<MessagePort
>(mPort
)]() { port
->Close(); });
637 if (NS_WARN_IF(!aEvent
->AsMessageEvent() || !aEvent
->IsTrusted())) {
641 // Step 1: Let error be a new "DataCloneError" DOMException.
642 RefPtr
<DOMException
> exception
=
643 DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR
);
646 if (!jsapi
.Init(mPort
->GetParentObject())) {
649 JSContext
* cx
= jsapi
.cx();
650 JS::Rooted
<JS::Value
> error(cx
);
651 if (!ToJSValue(cx
, *exception
, &error
)) {
655 // Step 2: Perform ! CrossRealmTransformSendError(port, error).
656 CrossRealmTransformSendError(cx
, mPort
, error
);
658 // Step 3: Perform ! ReadableStreamDefaultControllerError(controller,
660 ReadableStreamDefaultControllerError(cx
, mController
, error
,
663 // Step 4: Disentangle port.
666 cleanupPort
.release();
672 ~SetUpTransformReadableMessageErrorEventListener() = default;
674 RefPtr
<ReadableStreamDefaultController
> mController
;
675 RefPtr
<MessagePort
> mPort
;
678 NS_IMPL_CYCLE_COLLECTION(SetUpTransformReadableMessageErrorEventListener
,
680 NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformReadableMessageErrorEventListener
)
681 NS_IMPL_CYCLE_COLLECTING_RELEASE(
682 SetUpTransformReadableMessageErrorEventListener
)
683 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
684 SetUpTransformReadableMessageErrorEventListener
)
685 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener
)
688 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
689 class CrossRealmReadableUnderlyingSourceAlgorithms final
690 : public UnderlyingSourceAlgorithmsBase
{
692 NS_DECL_ISUPPORTS_INHERITED
693 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
694 CrossRealmReadableUnderlyingSourceAlgorithms
,
695 UnderlyingSourceAlgorithmsBase
)
697 explicit CrossRealmReadableUnderlyingSourceAlgorithms(MessagePort
* aPort
)
700 void StartCallback(JSContext
* aCx
, ReadableStreamController
& aController
,
701 JS::MutableHandle
<JS::Value
> aRetVal
,
702 ErrorResult
& aRv
) override
{
703 // Step 6. Let startAlgorithm be an algorithm that returns undefined.
704 aRetVal
.setUndefined();
707 already_AddRefed
<Promise
> PullCallback(JSContext
* aCx
,
708 ReadableStreamController
& aController
,
709 ErrorResult
& aRv
) override
{
710 // Step 7: Let pullAlgorithm be the following steps:
712 // Step 7.1: Perform ! PackAndPostMessage(port, "pull", undefined).
713 PackAndPostMessage(aCx
, mPort
, u
"pull"_ns
, JS::UndefinedHandleValue
, aRv
);
718 // Step 7.2: Return a promise resolved with undefined.
719 return Promise::CreateResolvedWithUndefined(mPort
->GetParentObject(), aRv
);
722 already_AddRefed
<Promise
> CancelCallback(
723 JSContext
* aCx
, const Optional
<JS::Handle
<JS::Value
>>& aReason
,
724 ErrorResult
& aRv
) override
{
725 // Step 8: Let cancelAlgorithm be the following steps, taking a reason
728 // Step 8.1: Let result be PackAndPostMessageHandlingError(port, "error",
730 JS::Rooted
<JS::Value
> error(aCx
);
731 bool result
= PackAndPostMessageHandlingError(
732 aCx
, mPort
, u
"error"_ns
,
733 aReason
.WasPassed() ? aReason
.Value() : JS::UndefinedHandleValue
,
736 // Step 8.2: Disentangle port.
740 // Step 8.3: If result is an abrupt completion, return a promise rejected
741 // with result.[[Value]].
743 return Promise::CreateRejected(mPort
->GetParentObject(), error
, aRv
);
746 // Step 8.4: Otherwise, return a promise resolved with undefined.
747 return Promise::CreateResolvedWithUndefined(mPort
->GetParentObject(), aRv
);
751 ~CrossRealmReadableUnderlyingSourceAlgorithms() override
= default;
754 RefPtr
<MessagePort
> mPort
;
757 NS_IMPL_CYCLE_COLLECTION_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms
,
758 UnderlyingSourceAlgorithmsBase
, mPort
)
759 NS_IMPL_ADDREF_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms
,
760 UnderlyingSourceAlgorithmsBase
)
761 NS_IMPL_RELEASE_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms
,
762 UnderlyingSourceAlgorithmsBase
)
763 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
764 CrossRealmReadableUnderlyingSourceAlgorithms
)
765 NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsBase
)
767 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
768 MOZ_CAN_RUN_SCRIPT
static void SetUpCrossRealmTransformReadable(
769 ReadableStream
* aReadable
, MessagePort
* aPort
, ErrorResult
& aRv
) {
770 // (This is only needed for step 10, but let's do this early to fail early
773 if (!jsapi
.Init(aReadable
->GetParentObject())) {
776 JSContext
* cx
= jsapi
.cx();
778 // Step 1: Perform ! InitializeReadableStream(stream).
779 // (This is implicitly done by the constructor)
781 // Step 2: Let controller be a new ReadableStreamDefaultController.
783 MakeRefPtr
<ReadableStreamDefaultController
>(aReadable
->GetParentObject());
785 // Step 3: Add a handler for port’s message event with the following steps:
787 MakeRefPtr
<SetUpTransformReadableMessageEventListener
>(controller
, aPort
);
788 aPort
->AddEventListener(u
"message"_ns
, listener
, false);
790 // Step 4: Add a handler for port’s messageerror event with the following
793 MakeRefPtr
<SetUpTransformReadableMessageErrorEventListener
>(controller
,
795 aPort
->AddEventListener(u
"messageerror"_ns
, errorListener
, false);
797 // Step 5: Enable port’s port message queue.
803 MakeRefPtr
<CrossRealmReadableUnderlyingSourceAlgorithms
>(aPort
);
805 // Step 9: Let sizeAlgorithm be an algorithm that returns 1.
806 // (nullptr should serve this purpose. See also ReadableStream::Constructor)
808 // Step 10: Perform ! SetUpReadableStreamDefaultController(stream, controller,
809 // startAlgorithm, pullAlgorithm, cancelAlgorithm, 0, sizeAlgorithm).
810 SetUpReadableStreamDefaultController(cx
, aReadable
, controller
, algorithms
, 0,
811 /* aSizeAlgorithm */ nullptr, aRv
);
814 // https://streams.spec.whatwg.org/#ref-for-transfer-steps
815 bool ReadableStream::Transfer(JSContext
* aCx
, UniqueMessagePortId
& aPortId
) {
816 // Step 1: If ! IsReadableStreamLocked(value) is true, throw a
817 // "DataCloneError" DOMException.
818 // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
819 // check here as the state might have changed in case this ReadableStream is
820 // created by a TransferStream and being transferred together with the
822 if (IsReadableStreamLocked(this)) {
826 // Step 2: Let port1 be a new MessagePort in the current Realm.
827 // Step 3: Let port2 be a new MessagePort in the current Realm.
828 // Step 4: Entangle port1 and port2.
829 // (The MessageChannel constructor does exactly that.)
830 // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messagechannel
832 RefPtr
<dom::MessageChannel
> channel
=
833 dom::MessageChannel::Constructor(mGlobal
, rv
);
834 if (rv
.MaybeSetPendingException(aCx
)) {
838 // Step 5: Let writable be a new WritableStream in the current Realm.
839 RefPtr
<WritableStream
> writable
= new WritableStream(
840 mGlobal
, WritableStream::HoldDropJSObjectsCaller::Implicit
);
842 // Step 6: Perform ! SetUpCrossRealmTransformWritable(writable, port1).
843 // MOZ_KnownLive because Port1 never changes before CC
844 SetUpCrossRealmTransformWritable(writable
, MOZ_KnownLive(channel
->Port1()),
846 if (rv
.MaybeSetPendingException(aCx
)) {
850 // Step 7: Let promise be ! ReadableStreamPipeTo(value, writable, false,
852 RefPtr
<Promise
> promise
=
853 ReadableStreamPipeTo(this, writable
, false, false, false, nullptr, rv
);
854 if (rv
.MaybeSetPendingException(aCx
)) {
858 // Step 8: Set promise.[[PromiseIsHandled]] to true.
859 MOZ_ALWAYS_TRUE(promise
->SetAnyPromiseIsHandled());
861 // Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
863 channel
->Port2()->CloneAndDisentangle(aPortId
);
868 // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps
869 MOZ_CAN_RUN_SCRIPT already_AddRefed
<ReadableStream
>
870 ReadableStream::ReceiveTransferImpl(JSContext
* aCx
, nsIGlobalObject
* aGlobal
,
871 MessagePort
& aPort
) {
872 // Step 1: Let deserializedRecord be
873 // ! StructuredDeserializeWithTransfer(dataHolder.[[port]], the current
875 // Step 2: Let port be deserializedRecord.[[Deserialized]].
877 // Step 3: Perform ! SetUpCrossRealmTransformReadable(value, port).
878 RefPtr
<ReadableStream
> readable
=
879 new ReadableStream(aGlobal
, HoldDropJSObjectsCaller::Implicit
);
881 SetUpCrossRealmTransformReadable(readable
, &aPort
, rv
);
882 if (rv
.MaybeSetPendingException(aCx
)) {
885 return readable
.forget();
888 bool ReadableStream::ReceiveTransfer(
889 JSContext
* aCx
, nsIGlobalObject
* aGlobal
, MessagePort
& aPort
,
890 JS::MutableHandle
<JSObject
*> aReturnObject
) {
891 RefPtr
<ReadableStream
> readable
=
892 ReadableStream::ReceiveTransferImpl(aCx
, aGlobal
, aPort
);
897 JS::Rooted
<JS::Value
> value(aCx
);
898 if (!GetOrCreateDOMReflector(aCx
, readable
, &value
)) {
899 JS_ClearPendingException(aCx
);
902 aReturnObject
.set(&value
.toObject());
907 // https://streams.spec.whatwg.org/#ref-for-transfer-steps①
908 bool WritableStream::Transfer(JSContext
* aCx
, UniqueMessagePortId
& aPortId
) {
909 // Step 1: If ! IsWritableStreamLocked(value) is true, throw a
910 // "DataCloneError" DOMException.
911 // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
912 // check here as the state might have changed in case this WritableStream is
913 // created by a TransferStream and being transferred together with the
915 if (IsWritableStreamLocked(this)) {
919 // Step 2: Let port1 be a new MessagePort in the current Realm.
920 // Step 3: Let port2 be a new MessagePort in the current Realm.
921 // Step 4: Entangle port1 and port2.
922 // (The MessageChannel constructor does exactly that.)
923 // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messagechannel
925 RefPtr
<dom::MessageChannel
> channel
=
926 dom::MessageChannel::Constructor(mGlobal
, rv
);
927 if (rv
.MaybeSetPendingException(aCx
)) {
931 // Step 5: Let readable be a new ReadableStream in the current Realm.
932 RefPtr
<ReadableStream
> readable
= new ReadableStream(
933 mGlobal
, ReadableStream::HoldDropJSObjectsCaller::Implicit
);
935 // Step 6: Perform ! SetUpCrossRealmTransformReadable(readable, port1).
936 // MOZ_KnownLive because Port1 never changes before CC
937 SetUpCrossRealmTransformReadable(readable
, MOZ_KnownLive(channel
->Port1()),
939 if (rv
.MaybeSetPendingException(aCx
)) {
943 // Step 7: Let promise be ! ReadableStreamPipeTo(readable, value, false,
945 RefPtr
<Promise
> promise
=
946 ReadableStreamPipeTo(readable
, this, false, false, false, nullptr, rv
);
951 // Step 8: Set promise.[[PromiseIsHandled]] to true.
952 MOZ_ALWAYS_TRUE(promise
->SetAnyPromiseIsHandled());
954 // Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
956 channel
->Port2()->CloneAndDisentangle(aPortId
);
961 // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps①
962 MOZ_CAN_RUN_SCRIPT already_AddRefed
<WritableStream
>
963 WritableStream::ReceiveTransferImpl(JSContext
* aCx
, nsIGlobalObject
* aGlobal
,
964 MessagePort
& aPort
) {
965 // Step 1: Let deserializedRecord be !
966 // StructuredDeserializeWithTransfer(dataHolder.[[port]], the current Realm).
967 // Step 2: Let port be a deserializedRecord.[[Deserialized]].
969 // Step 3: Perform ! SetUpCrossRealmTransformWritable(value, port).
970 RefPtr
<WritableStream
> writable
= new WritableStream(
971 aGlobal
, WritableStream::HoldDropJSObjectsCaller::Implicit
);
973 SetUpCrossRealmTransformWritable(writable
, &aPort
, rv
);
974 if (rv
.MaybeSetPendingException(aCx
)) {
977 return writable
.forget();
980 // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps①
981 bool WritableStream::ReceiveTransfer(
982 JSContext
* aCx
, nsIGlobalObject
* aGlobal
, MessagePort
& aPort
,
983 JS::MutableHandle
<JSObject
*> aReturnObject
) {
984 RefPtr
<WritableStream
> writable
=
985 WritableStream::ReceiveTransferImpl(aCx
, aGlobal
, aPort
);
990 JS::Rooted
<JS::Value
> value(aCx
);
991 if (!GetOrCreateDOMReflector(aCx
, writable
, &value
)) {
992 JS_ClearPendingException(aCx
);
995 aReturnObject
.set(&value
.toObject());
1000 // https://streams.spec.whatwg.org/#ref-for-transfer-steps②
1001 bool TransformStream::Transfer(JSContext
* aCx
, UniqueMessagePortId
& aPortId1
,
1002 UniqueMessagePortId
& aPortId2
) {
1003 // Step 1: Let readable be value.[[readable]].
1004 // Step 2: Let writable be value.[[writable]].
1005 // Step 3: If ! IsReadableStreamLocked(readable) is true, throw a
1006 // "DataCloneError" DOMException.
1007 // Step 4: If ! IsWritableStreamLocked(writable) is true, throw a
1008 // "DataCloneError" DOMException.
1009 // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
1010 // check here as the state might have changed by
1011 // Readable/WritableStream::Transfer in case the stream members of this
1012 // TransformStream are being transferred together.)
1013 if (IsReadableStreamLocked(mReadable
) || IsWritableStreamLocked(mWritable
)) {
1017 // Step 5: Set dataHolder.[[readable]] to !
1018 // StructuredSerializeWithTransfer(readable, « readable »).
1019 // TODO: Mark mReadable as MOZ_KNOWN_LIVE again (bug 1769854)
1020 if (!MOZ_KnownLive(mReadable
)->Transfer(aCx
, aPortId1
)) {
1024 // Step 6: Set dataHolder.[[writable]] to !
1025 // StructuredSerializeWithTransfer(writable, « writable »).
1026 // TODO: Mark mReadable as MOZ_KNOWN_LIVE again (bug 1769854)
1027 return MOZ_KnownLive(mWritable
)->Transfer(aCx
, aPortId2
);
1030 // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps②
1031 bool TransformStream::ReceiveTransfer(
1032 JSContext
* aCx
, nsIGlobalObject
* aGlobal
, MessagePort
& aPort1
,
1033 MessagePort
& aPort2
, JS::MutableHandle
<JSObject
*> aReturnObject
) {
1034 // Step 1: Let readableRecord be !
1035 // StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current
1037 RefPtr
<ReadableStream
> readable
=
1038 ReadableStream::ReceiveTransferImpl(aCx
, aGlobal
, aPort1
);
1043 // Step 2: Let writableRecord be !
1044 // StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current
1046 RefPtr
<WritableStream
> writable
=
1047 WritableStream::ReceiveTransferImpl(aCx
, aGlobal
, aPort2
);
1052 // Step 3: Set value.[[readable]] to readableRecord.[[Deserialized]].
1053 // Step 4: Set value.[[writable]] to writableRecord.[[Deserialized]].
1054 // Step 5: Set value.[[backpressure]], value.[[backpressureChangePromise]],
1055 // and value.[[controller]] to undefined.
1056 RefPtr
<TransformStream
> stream
=
1057 new TransformStream(aGlobal
, readable
, writable
);
1058 JS::Rooted
<JS::Value
> value(aCx
);
1059 if (!GetOrCreateDOMReflector(aCx
, stream
, &value
)) {
1060 JS_ClearPendingException(aCx
);
1063 aReturnObject
.set(&value
.toObject());
1068 } // namespace mozilla::dom