Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / streams / Transferable.cpp
blobf3e9504d86a9a2b0ad83f8303191349cd3c27c08
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/. */
7 #include "ErrorList.h"
8 #include "ReadableStreamPipeTo.h"
9 #include "js/RootingAPI.h"
10 #include "js/String.h"
11 #include "js/TypeDecls.h"
12 #include "js/Value.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));
40 if (!obj) {
41 // XXX: Should we crash here and there? See also bug 1762233.
42 JS_ClearPendingException(aCx);
43 aRv.Throw(NS_ERROR_UNEXPECTED);
44 return;
47 JS::Rooted<JS::Value> type(aCx);
48 if (!xpc::NonVoidStringToJsval(aCx, aType, &type)) {
49 JS_ClearPendingException(aCx);
50 aRv.Throw(NS_ERROR_UNEXPECTED);
51 return;
53 if (!JS_DefineProperty(aCx, obj, "type", type, JSPROP_ENUMERATE)) {
54 JS_ClearPendingException(aCx);
55 aRv.Throw(NS_ERROR_UNEXPECTED);
56 return;
58 JS::Rooted<JS::Value> value(aCx, aValue);
59 if (!JS_WrapValue(aCx, &value)) {
60 JS_ClearPendingException(aCx);
61 aRv.Throw(NS_ERROR_UNEXPECTED);
62 return;
64 if (!JS_DefineProperty(aCx, obj, "value", value, JSPROP_ENUMERATE)) {
65 JS_ClearPendingException(aCx);
66 aRv.Throw(NS_ERROR_UNEXPECTED);
67 return;
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
79 // result.
80 PackAndPostMessage(aCx, aPort, u"error"_ns, aError, IgnoreErrors());
83 class SetUpTransformWritableMessageEventListener final
84 : public nsIDOMEventListener {
85 public:
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 {
96 AutoJSAPI jsapi;
97 if (!jsapi.Init(mController->GetParentObject())) {
98 return NS_OK;
100 JSContext* cx = jsapi.cx();
101 MessageEvent* messageEvent = aEvent->AsMessageEvent();
102 if (NS_WARN_IF(!messageEvent || !messageEvent->IsTrusted())) {
103 return NS_OK;
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);
110 if (rv.Failed()) {
111 return NS_OK;
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())) {
118 return NS_OK;
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);
127 return NS_OK;
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);
134 return NS_OK;
137 // Step 5: Assert: Type(type) is String.
138 if (NS_WARN_IF(!type.isString())) {
139 return NS_OK;
142 // Step 6: If type is "pull",
143 bool equals = false;
144 if (!JS_StringEqualsLiteral(cx, type.toString(), "pull", &equals)) {
145 JS_ClearPendingException(cx);
146 return NS_OK;
148 if (equals) {
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);
157 return NS_OK;
159 if (equals) {
160 // Step 7.1: Perform !
161 // WritableStreamDefaultControllerErrorIfNeeded(controller, value).
162 WritableStreamDefaultControllerErrorIfNeeded(cx, mController, value, rv);
163 if (rv.Failed()) {
164 return NS_OK;
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.");
175 return NS_OK;
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());
193 private:
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)
209 NS_INTERFACE_MAP_END
211 class SetUpTransformWritableMessageErrorEventListener final
212 : public nsIDOMEventListener {
213 public:
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 {
225 auto cleanupPort =
226 MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
228 if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) {
229 return NS_OK;
232 // Step 1: Let error be a new "DataCloneError" DOMException.
233 RefPtr<DOMException> exception =
234 DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR);
236 AutoJSAPI jsapi;
237 if (!jsapi.Init(mPort->GetParentObject())) {
238 return NS_OK;
240 JSContext* cx = jsapi.cx();
241 JS::Rooted<JS::Value> error(cx);
242 if (!ToJSValue(cx, *exception, &error)) {
243 return NS_OK;
246 // Step 2: Perform ! CrossRealmTransformSendError(port, error).
247 CrossRealmTransformSendError(cx, mPort, error);
249 // Step 3: Perform
250 // ! WritableStreamDefaultControllerErrorIfNeeded(controller, error).
251 WritableStreamDefaultControllerErrorIfNeeded(cx, mController, error,
252 IgnoreErrors());
254 // Step 4: Disentangle port.
255 // (Close() does it)
256 mPort->Close();
257 cleanupPort.release();
259 return NS_OK;
262 private:
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,
272 mController, mPort)
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)
279 NS_INTERFACE_MAP_END
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).
286 ErrorResult rv;
287 PackAndPostMessage(aCx, aPort, aType, aValue, rv);
289 // Step 2: If result is an abrupt completion,
290 if (rv.Failed()) {
291 // Step 2.2: Perform ! CrossRealmTransformSendError(port, result.[[Value]]).
292 MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), aError));
293 CrossRealmTransformSendError(aCx, aPort, aError);
294 return false;
297 // Step 3: Return result as a completion record.
298 return true;
301 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
302 class CrossRealmWritableUnderlyingSinkAlgorithms final
303 : public UnderlyingSinkAlgorithmsBase {
304 public:
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:
334 auto result =
335 mListener->BackpressurePromise()->ThenWithCycleCollectedArgsJS(
336 [](JSContext* aCx, JS::Handle<JS::Value>, ErrorResult& aRv,
337 SetUpTransformWritableMessageEventListener* aListener,
338 MessagePort* aPort,
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,
344 // "chunk", chunk).
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,
350 if (!result) {
351 // Step 2.3.1: Disentangle port.
352 // (Close() does it)
353 aPort->Close();
355 // Step 2.3.2: Return a promise rejected with result.[[Value]].
356 return Promise::CreateRejected(aPort->GetParentObject(), error,
357 aRv);
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());
367 return nullptr;
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)
380 mPort->Close();
382 if (aRv.Failed()) {
383 return nullptr;
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",
394 // reason).
395 JS::Rooted<JS::Value> error(aCx);
396 bool result = PackAndPostMessageHandlingError(
397 aCx, mPort, u"error"_ns,
398 aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue,
399 &error);
401 // Step 2: Disentangle port.
402 // (Close() will do this)
403 mPort->Close();
405 // Step 3: If result is an abrupt completion, return a promise rejected with
406 // result.[[Value]].
407 if (!result) {
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);
415 protected:
416 ~CrossRealmWritableUnderlyingSinkAlgorithms() override = default;
418 private:
419 RefPtr<SetUpTransformWritableMessageEventListener> mListener;
420 RefPtr<MessagePort> mPort;
423 NS_IMPL_CYCLE_COLLECTION_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms,
424 UnderlyingSinkAlgorithmsBase, mListener,
425 mPort)
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
438 // enough)
439 AutoJSAPI jsapi;
440 if (!jsapi.Init(aWritable->GetParentObject())) {
441 return;
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
462 // steps:
463 auto errorListener =
464 MakeRefPtr<SetUpTransformWritableMessageErrorEventListener>(controller,
465 aPort);
466 aPort->AddEventListener(u"messageerror"_ns, errorListener, false);
468 // Step 6: Enable port’s port message queue.
469 // (Start() does it)
470 aPort->Start();
472 // Step 7 - 10:
473 auto algorithms =
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,
481 // sizeAlgorithm).
482 SetUpWritableStreamDefaultController(cx, aWritable, controller, algorithms, 1,
483 /* aSizeAlgorithm */ nullptr, aRv);
486 class SetUpTransformReadableMessageEventListener final
487 : public nsIDOMEventListener {
488 public:
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 {
499 auto cleanupPort =
500 MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
502 AutoJSAPI jsapi;
503 if (!jsapi.Init(mPort->GetParentObject())) {
504 return NS_OK;
506 JSContext* cx = jsapi.cx();
507 MessageEvent* messageEvent = aEvent->AsMessageEvent();
508 if (NS_WARN_IF(!messageEvent || !messageEvent->IsTrusted())) {
509 return NS_OK;
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);
516 if (rv.Failed()) {
517 return NS_OK;
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())) {
524 return NS_OK;
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);
533 return NS_OK;
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);
540 return NS_OK;
543 // Step 5: Assert: Type(type) is String.
544 if (NS_WARN_IF(!type.isString())) {
545 return NS_OK;
548 // Step 6: If type is "chunk",
549 bool equals = false;
550 if (!JS_StringEqualsLiteral(cx, type.toString(), "chunk", &equals)) {
551 JS_ClearPendingException(cx);
552 return NS_OK;
554 if (equals) {
555 // Step 6.1: Perform ! ReadableStreamDefaultControllerEnqueue(controller,
556 // value).
557 ReadableStreamDefaultControllerEnqueue(cx, mController, value,
558 IgnoreErrors());
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);
566 return NS_OK;
568 if (equals) {
569 // Step 7.1: Perform ! ReadableStreamDefaultControllerClose(controller).
570 ReadableStreamDefaultControllerClose(cx, mController, IgnoreErrors());
571 // Step 7.2: Disentangle port.
572 // (Close() does it)
573 mPort->Close();
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);
581 return NS_OK;
583 if (equals) {
584 // Step 8.1: Perform ! ReadableStreamDefaultControllerError(controller,
585 // value).
586 ReadableStreamDefaultControllerError(cx, mController, value,
587 IgnoreErrors());
589 // Step 8.2: Disentangle port.
590 // (Close() does it)
591 mPort->Close();
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.");
599 return NS_OK;
602 private:
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,
612 mController, mPort)
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)
618 NS_INTERFACE_MAP_END
620 class SetUpTransformReadableMessageErrorEventListener final
621 : public nsIDOMEventListener {
622 public:
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 {
634 auto cleanupPort =
635 MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
637 if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) {
638 return NS_OK;
641 // Step 1: Let error be a new "DataCloneError" DOMException.
642 RefPtr<DOMException> exception =
643 DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR);
645 AutoJSAPI jsapi;
646 if (!jsapi.Init(mPort->GetParentObject())) {
647 return NS_OK;
649 JSContext* cx = jsapi.cx();
650 JS::Rooted<JS::Value> error(cx);
651 if (!ToJSValue(cx, *exception, &error)) {
652 return NS_OK;
655 // Step 2: Perform ! CrossRealmTransformSendError(port, error).
656 CrossRealmTransformSendError(cx, mPort, error);
658 // Step 3: Perform ! ReadableStreamDefaultControllerError(controller,
659 // error).
660 ReadableStreamDefaultControllerError(cx, mController, error,
661 IgnoreErrors());
663 // Step 4: Disentangle port.
664 // (Close() does it)
665 mPort->Close();
666 cleanupPort.release();
668 return NS_OK;
671 private:
672 ~SetUpTransformReadableMessageErrorEventListener() = default;
674 RefPtr<ReadableStreamDefaultController> mController;
675 RefPtr<MessagePort> mPort;
678 NS_IMPL_CYCLE_COLLECTION(SetUpTransformReadableMessageErrorEventListener,
679 mController, mPort)
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)
686 NS_INTERFACE_MAP_END
688 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
689 class CrossRealmReadableUnderlyingSourceAlgorithms final
690 : public UnderlyingSourceAlgorithmsBase {
691 public:
692 NS_DECL_ISUPPORTS_INHERITED
693 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
694 CrossRealmReadableUnderlyingSourceAlgorithms,
695 UnderlyingSourceAlgorithmsBase)
697 explicit CrossRealmReadableUnderlyingSourceAlgorithms(MessagePort* aPort)
698 : mPort(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);
714 if (aRv.Failed()) {
715 return nullptr;
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
726 // argument:
728 // Step 8.1: Let result be PackAndPostMessageHandlingError(port, "error",
729 // reason).
730 JS::Rooted<JS::Value> error(aCx);
731 bool result = PackAndPostMessageHandlingError(
732 aCx, mPort, u"error"_ns,
733 aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue,
734 &error);
736 // Step 8.2: Disentangle port.
737 // (Close() does it)
738 mPort->Close();
740 // Step 8.3: If result is an abrupt completion, return a promise rejected
741 // with result.[[Value]].
742 if (!result) {
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);
750 protected:
751 ~CrossRealmReadableUnderlyingSourceAlgorithms() override = default;
753 private:
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
771 // enough)
772 AutoJSAPI jsapi;
773 if (!jsapi.Init(aReadable->GetParentObject())) {
774 return;
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.
782 auto controller =
783 MakeRefPtr<ReadableStreamDefaultController>(aReadable->GetParentObject());
785 // Step 3: Add a handler for port’s message event with the following steps:
786 auto listener =
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
791 // steps:
792 auto errorListener =
793 MakeRefPtr<SetUpTransformReadableMessageErrorEventListener>(controller,
794 aPort);
795 aPort->AddEventListener(u"messageerror"_ns, errorListener, false);
797 // Step 5: Enable port’s port message queue.
798 // (Start() does it)
799 aPort->Start();
801 // Step 6-8:
802 auto algorithms =
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
821 // parent.)
822 if (IsReadableStreamLocked(this)) {
823 return false;
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
831 ErrorResult rv;
832 RefPtr<dom::MessageChannel> channel =
833 dom::MessageChannel::Constructor(mGlobal, rv);
834 if (rv.MaybeSetPendingException(aCx)) {
835 return false;
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()),
845 rv);
846 if (rv.MaybeSetPendingException(aCx)) {
847 return false;
850 // Step 7: Let promise be ! ReadableStreamPipeTo(value, writable, false,
851 // false, false).
852 RefPtr<Promise> promise =
853 ReadableStreamPipeTo(this, writable, false, false, false, nullptr, rv);
854 if (rv.MaybeSetPendingException(aCx)) {
855 return false;
858 // Step 8: Set promise.[[PromiseIsHandled]] to true.
859 MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
861 // Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
862 // « port2 »).
863 channel->Port2()->CloneAndDisentangle(aPortId);
865 return true;
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
874 // Realm).
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);
880 ErrorResult rv;
881 SetUpCrossRealmTransformReadable(readable, &aPort, rv);
882 if (rv.MaybeSetPendingException(aCx)) {
883 return nullptr;
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);
893 if (!readable) {
894 return false;
897 JS::Rooted<JS::Value> value(aCx);
898 if (!GetOrCreateDOMReflector(aCx, readable, &value)) {
899 JS_ClearPendingException(aCx);
900 return false;
902 aReturnObject.set(&value.toObject());
904 return true;
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
914 // parent.)
915 if (IsWritableStreamLocked(this)) {
916 return false;
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
924 ErrorResult rv;
925 RefPtr<dom::MessageChannel> channel =
926 dom::MessageChannel::Constructor(mGlobal, rv);
927 if (rv.MaybeSetPendingException(aCx)) {
928 return false;
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()),
938 rv);
939 if (rv.MaybeSetPendingException(aCx)) {
940 return false;
943 // Step 7: Let promise be ! ReadableStreamPipeTo(readable, value, false,
944 // false, false).
945 RefPtr<Promise> promise =
946 ReadableStreamPipeTo(readable, this, false, false, false, nullptr, rv);
947 if (rv.Failed()) {
948 return false;
951 // Step 8: Set promise.[[PromiseIsHandled]] to true.
952 MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
954 // Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
955 // « port2 »).
956 channel->Port2()->CloneAndDisentangle(aPortId);
958 return true;
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);
972 ErrorResult rv;
973 SetUpCrossRealmTransformWritable(writable, &aPort, rv);
974 if (rv.MaybeSetPendingException(aCx)) {
975 return nullptr;
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);
986 if (!writable) {
987 return false;
990 JS::Rooted<JS::Value> value(aCx);
991 if (!GetOrCreateDOMReflector(aCx, writable, &value)) {
992 JS_ClearPendingException(aCx);
993 return false;
995 aReturnObject.set(&value.toObject());
997 return true;
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)) {
1014 return false;
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)) {
1021 return false;
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
1036 // Realm).
1037 RefPtr<ReadableStream> readable =
1038 ReadableStream::ReceiveTransferImpl(aCx, aGlobal, aPort1);
1039 if (!readable) {
1040 return false;
1043 // Step 2: Let writableRecord be !
1044 // StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current
1045 // Realm).
1046 RefPtr<WritableStream> writable =
1047 WritableStream::ReceiveTransferImpl(aCx, aGlobal, aPort2);
1048 if (!writable) {
1049 return false;
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);
1061 return false;
1063 aReturnObject.set(&value.toObject());
1065 return true;
1068 } // namespace mozilla::dom