Transferable.cpp (39064B)
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/. */ 6 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/ErrorResult.h" 15 #include "mozilla/dom/DOMException.h" 16 #include "mozilla/dom/DOMExceptionBinding.h" 17 #include "mozilla/dom/MessageChannel.h" 18 #include "mozilla/dom/MessageEvent.h" 19 #include "mozilla/dom/MessagePort.h" 20 #include "mozilla/dom/Promise-inl.h" 21 #include "mozilla/dom/Promise.h" 22 #include "mozilla/dom/ReadableStream.h" 23 #include "mozilla/dom/TransformStream.h" 24 #include "mozilla/dom/WritableStream.h" 25 #include "nsCycleCollectionParticipant.h" 26 #include "nsIDOMEventListener.h" 27 #include "nsIGlobalObject.h" 28 #include "nsISupportsImpl.h" 29 30 namespace mozilla::dom { 31 32 using namespace streams_abstract; 33 34 static void PackAndPostMessage(JSContext* aCx, MessagePort* aPort, 35 const nsAString& aType, 36 JS::Handle<JS::Value> aValue, ErrorResult& aRv) { 37 JS::Rooted<JSObject*> obj(aCx, 38 JS_NewObjectWithGivenProto(aCx, nullptr, nullptr)); 39 if (!obj) { 40 // XXX: Should we crash here and there? See also bug 1762233. 41 JS_ClearPendingException(aCx); 42 aRv.Throw(NS_ERROR_UNEXPECTED); 43 return; 44 } 45 46 JS::Rooted<JS::Value> type(aCx); 47 if (!xpc::NonVoidStringToJsval(aCx, aType, &type)) { 48 JS_ClearPendingException(aCx); 49 aRv.Throw(NS_ERROR_UNEXPECTED); 50 return; 51 } 52 if (!JS_DefineProperty(aCx, obj, "type", type, JSPROP_ENUMERATE)) { 53 JS_ClearPendingException(aCx); 54 aRv.Throw(NS_ERROR_UNEXPECTED); 55 return; 56 } 57 JS::Rooted<JS::Value> value(aCx, aValue); 58 if (!JS_WrapValue(aCx, &value)) { 59 JS_ClearPendingException(aCx); 60 aRv.Throw(NS_ERROR_UNEXPECTED); 61 return; 62 } 63 if (!JS_DefineProperty(aCx, obj, "value", value, JSPROP_ENUMERATE)) { 64 JS_ClearPendingException(aCx); 65 aRv.Throw(NS_ERROR_UNEXPECTED); 66 return; 67 } 68 69 Sequence<JSObject*> transferables; // none in this case 70 JS::Rooted<JS::Value> objValue(aCx, JS::ObjectValue(*obj)); 71 aPort->PostMessage(aCx, objValue, transferables, aRv); 72 } 73 74 // https://streams.spec.whatwg.org/#abstract-opdef-crossrealmtransformsenderror 75 static void CrossRealmTransformSendError(JSContext* aCx, MessagePort* aPort, 76 JS::Handle<JS::Value> aError) { 77 // Step 1: Perform PackAndPostMessage(port, "error", error), discarding the 78 // result. 79 PackAndPostMessage(aCx, aPort, u"error"_ns, aError, IgnoreErrors()); 80 } 81 82 class SetUpTransformWritableMessageEventListener final 83 : public nsIDOMEventListener { 84 public: 85 SetUpTransformWritableMessageEventListener( 86 WritableStreamDefaultController* aController, Promise* aPromise) 87 : mController(aController), mBackpressurePromise(aPromise) {} 88 89 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 90 NS_DECL_CYCLE_COLLECTION_CLASS(SetUpTransformWritableMessageEventListener) 91 92 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable 93 // The handler steps of Step 4. 94 MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override { 95 AutoJSAPI jsapi; 96 if (!jsapi.Init(mController->GetParentObject())) { 97 return NS_OK; 98 } 99 JSContext* cx = jsapi.cx(); 100 MessageEvent* messageEvent = aEvent->AsMessageEvent(); 101 if (NS_WARN_IF(!messageEvent || !messageEvent->IsTrusted())) { 102 return NS_OK; 103 } 104 105 // Step 1: Let data be the data of the message. 106 JS::Rooted<JS::Value> dataValue(cx); 107 IgnoredErrorResult rv; 108 messageEvent->GetData(cx, &dataValue, rv); 109 if (rv.Failed()) { 110 return NS_OK; 111 } 112 113 // Step 2: Assert: Type(data) is Object. 114 // (But we check in runtime instead to avoid potential malicious events from 115 // a compromised process. Same below.) 116 if (NS_WARN_IF(!dataValue.isObject())) { 117 return NS_OK; 118 } 119 JS::Rooted<JSObject*> data(cx, &dataValue.toObject()); 120 121 // Step 3: Let type be ! Get(data, "type"). 122 JS::Rooted<JS::Value> type(cx); 123 if (!JS_GetProperty(cx, data, "type", &type)) { 124 // XXX: See bug 1762233 125 JS_ClearPendingException(cx); 126 return NS_OK; 127 } 128 129 // Step 4: Let value be ! Get(data, "value"). 130 JS::Rooted<JS::Value> value(cx); 131 if (!JS_GetProperty(cx, data, "value", &value)) { 132 JS_ClearPendingException(cx); 133 return NS_OK; 134 } 135 136 // Step 5: Assert: Type(type) is String. 137 if (NS_WARN_IF(!type.isString())) { 138 return NS_OK; 139 } 140 141 // Step 6: If type is "pull", 142 bool equals = false; 143 if (!JS_StringEqualsLiteral(cx, type.toString(), "pull", &equals)) { 144 JS_ClearPendingException(cx); 145 return NS_OK; 146 } 147 if (equals) { 148 // Step 6.1: If backpressurePromise is not undefined, 149 MaybeResolveAndClearBackpressurePromise(); 150 return NS_OK; // implicit 151 } 152 153 // Step 7: If type is "error", 154 if (!JS_StringEqualsLiteral(cx, type.toString(), "error", &equals)) { 155 JS_ClearPendingException(cx); 156 return NS_OK; 157 } 158 if (equals) { 159 // Step 7.1: Perform ! 160 // WritableStreamDefaultControllerErrorIfNeeded(controller, value). 161 WritableStreamDefaultControllerErrorIfNeeded(cx, mController, value, rv); 162 if (rv.Failed()) { 163 return NS_OK; 164 } 165 166 // Step 7.2: If backpressurePromise is not undefined, 167 MaybeResolveAndClearBackpressurePromise(); 168 return NS_OK; // implicit 169 } 170 171 // Logically it should be unreachable here, but we should expect random 172 // malicious messages. 173 NS_WARNING("Got an unexpected type other than pull/error."); 174 return NS_OK; 175 } 176 177 void MaybeResolveAndClearBackpressurePromise() { 178 if (mBackpressurePromise) { 179 mBackpressurePromise->MaybeResolveWithUndefined(); 180 mBackpressurePromise = nullptr; 181 } 182 } 183 184 // Note: This promise field is shared with the sink algorithms. 185 Promise* BackpressurePromise() { return mBackpressurePromise; } 186 187 void CreateBackpressurePromise() { 188 mBackpressurePromise = 189 Promise::CreateInfallible(mController->GetParentObject()); 190 } 191 192 private: 193 ~SetUpTransformWritableMessageEventListener() = default; 194 195 // mController never changes before CC 196 // TODO: MOZ_IMMUTABLE_OUTSIDE_CC 197 MOZ_KNOWN_LIVE RefPtr<WritableStreamDefaultController> mController; 198 RefPtr<Promise> mBackpressurePromise; 199 }; 200 201 NS_IMPL_CYCLE_COLLECTION(SetUpTransformWritableMessageEventListener, 202 mController, mBackpressurePromise) 203 NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformWritableMessageEventListener) 204 NS_IMPL_CYCLE_COLLECTING_RELEASE(SetUpTransformWritableMessageEventListener) 205 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( 206 SetUpTransformWritableMessageEventListener) 207 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) 208 NS_INTERFACE_MAP_END 209 210 class SetUpTransformWritableMessageErrorEventListener final 211 : public nsIDOMEventListener { 212 public: 213 SetUpTransformWritableMessageErrorEventListener( 214 WritableStreamDefaultController* aController, MessagePort* aPort) 215 : mController(aController), mPort(aPort) {} 216 217 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 218 NS_DECL_CYCLE_COLLECTION_CLASS( 219 SetUpTransformWritableMessageErrorEventListener) 220 221 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable 222 // The handler steps of Step 5. 223 MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override { 224 auto cleanupPort = 225 MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); }); 226 227 if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) { 228 return NS_OK; 229 } 230 231 // Step 1: Let error be a new "DataCloneError" DOMException. 232 RefPtr<DOMException> exception = 233 DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR); 234 235 AutoJSAPI jsapi; 236 if (!jsapi.Init(mPort->GetParentObject())) { 237 return NS_OK; 238 } 239 JSContext* cx = jsapi.cx(); 240 JS::Rooted<JS::Value> error(cx); 241 if (!ToJSValue(cx, *exception, &error)) { 242 return NS_OK; 243 } 244 245 // Step 2: Perform ! CrossRealmTransformSendError(port, error). 246 CrossRealmTransformSendError(cx, mPort, error); 247 248 // Step 3: Perform 249 // ! WritableStreamDefaultControllerErrorIfNeeded(controller, error). 250 WritableStreamDefaultControllerErrorIfNeeded(cx, mController, error, 251 IgnoreErrors()); 252 253 // Step 4: Disentangle port. 254 // (Close() does it) 255 mPort->Close(); 256 cleanupPort.release(); 257 258 return NS_OK; 259 } 260 261 private: 262 ~SetUpTransformWritableMessageErrorEventListener() = default; 263 264 // mController never changes before CC 265 // TODO: MOZ_IMMUTABLE_OUTSIDE_CC 266 MOZ_KNOWN_LIVE RefPtr<WritableStreamDefaultController> mController; 267 RefPtr<MessagePort> mPort; 268 }; 269 270 NS_IMPL_CYCLE_COLLECTION(SetUpTransformWritableMessageErrorEventListener, 271 mController, mPort) 272 NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformWritableMessageErrorEventListener) 273 NS_IMPL_CYCLE_COLLECTING_RELEASE( 274 SetUpTransformWritableMessageErrorEventListener) 275 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( 276 SetUpTransformWritableMessageErrorEventListener) 277 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) 278 NS_INTERFACE_MAP_END 279 280 // https://streams.spec.whatwg.org/#abstract-opdef-packandpostmessagehandlingerror 281 static bool PackAndPostMessageHandlingError( 282 JSContext* aCx, mozilla::dom::MessagePort* aPort, const nsAString& aType, 283 JS::Handle<JS::Value> aValue, JS::MutableHandle<JS::Value> aError) { 284 // Step 1: Let result be PackAndPostMessage(port, type, value). 285 ErrorResult rv; 286 PackAndPostMessage(aCx, aPort, aType, aValue, rv); 287 288 // Step 2: If result is an abrupt completion, 289 if (rv.Failed()) { 290 // Step 2.2: Perform ! CrossRealmTransformSendError(port, result.[[Value]]). 291 MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), aError)); 292 CrossRealmTransformSendError(aCx, aPort, aError); 293 return false; 294 } 295 296 // Step 3: Return result as a completion record. 297 return true; 298 } 299 300 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable 301 class CrossRealmWritableUnderlyingSinkAlgorithms final 302 : public UnderlyingSinkAlgorithmsBase { 303 public: 304 NS_DECL_ISUPPORTS_INHERITED 305 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( 306 CrossRealmWritableUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase) 307 308 CrossRealmWritableUnderlyingSinkAlgorithms( 309 SetUpTransformWritableMessageEventListener* aListener, MessagePort* aPort) 310 : mListener(aListener), mPort(aPort) {} 311 312 void StartCallback(JSContext* aCx, 313 WritableStreamDefaultController& aController, 314 JS::MutableHandle<JS::Value> aRetVal, 315 ErrorResult& aRv) override { 316 // Step 7. Let startAlgorithm be an algorithm that returns undefined. 317 aRetVal.setUndefined(); 318 } 319 320 already_AddRefed<Promise> WriteCallback( 321 JSContext* aCx, JS::Handle<JS::Value> aChunk, 322 WritableStreamDefaultController& aController, ErrorResult& aRv) override { 323 // Step 1: If backpressurePromise is undefined, set backpressurePromise to a 324 // promise resolved with undefined. 325 // Note: This promise field is shared with the message event listener. 326 if (!mListener->BackpressurePromise()) { 327 mListener->CreateBackpressurePromise(); 328 mListener->BackpressurePromise()->MaybeResolveWithUndefined(); 329 } 330 331 // Step 2: Return the result of reacting to backpressurePromise with the 332 // following fulfillment steps: 333 auto result = 334 mListener->BackpressurePromise()->ThenWithCycleCollectedArgsJS( 335 [](JSContext* aCx, JS::Handle<JS::Value>, ErrorResult& aRv, 336 SetUpTransformWritableMessageEventListener* aListener, 337 MessagePort* aPort, 338 JS::Handle<JS::Value> aChunk) -> already_AddRefed<Promise> { 339 // Step 2.1: Set backpressurePromise to a new promise. 340 aListener->CreateBackpressurePromise(); 341 342 // Step 2.2: Let result be PackAndPostMessageHandlingError(port, 343 // "chunk", chunk). 344 JS::Rooted<JS::Value> error(aCx); 345 bool result = PackAndPostMessageHandlingError( 346 aCx, aPort, u"chunk"_ns, aChunk, &error); 347 348 // Step 2.3: If result is an abrupt completion, 349 if (!result) { 350 // Step 2.3.1: Disentangle port. 351 // (Close() does it) 352 aPort->Close(); 353 354 // Step 2.3.2: Return a promise rejected with result.[[Value]]. 355 return Promise::CreateRejected(aPort->GetParentObject(), error, 356 aRv); 357 } 358 359 // Step 2.4: Otherwise, return a promise resolved with undefined. 360 return Promise::CreateResolvedWithUndefined( 361 aPort->GetParentObject(), aRv); 362 }, 363 std::make_tuple(mListener, mPort), std::make_tuple(aChunk)); 364 if (result.isErr()) { 365 aRv.Throw(result.unwrapErr()); 366 return nullptr; 367 } 368 return result.unwrap().forget(); 369 } 370 371 already_AddRefed<Promise> CloseCallback(JSContext* aCx, 372 ErrorResult& aRv) override { 373 // Step 1: Perform ! PackAndPostMessage(port, "close", undefined). 374 PackAndPostMessage(aCx, mPort, u"close"_ns, JS::UndefinedHandleValue, aRv); 375 // (We'll check the result after step 2) 376 377 // Step 2: Disentangle port. 378 // (Close() will do this) 379 mPort->Close(); 380 381 if (aRv.Failed()) { 382 return nullptr; 383 } 384 385 // Step 3: Return a promise resolved with undefined. 386 return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv); 387 } 388 389 already_AddRefed<Promise> AbortCallback( 390 JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason, 391 ErrorResult& aRv) override { 392 // Step 1: Let result be PackAndPostMessageHandlingError(port, "error", 393 // reason). 394 JS::Rooted<JS::Value> error(aCx); 395 bool result = PackAndPostMessageHandlingError( 396 aCx, mPort, u"error"_ns, 397 aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue, 398 &error); 399 400 // Step 2: Disentangle port. 401 // (Close() will do this) 402 mPort->Close(); 403 404 // Step 3: If result is an abrupt completion, return a promise rejected with 405 // result.[[Value]]. 406 if (!result) { 407 return Promise::CreateRejected(mPort->GetParentObject(), error, aRv); 408 } 409 410 // Step 4: Otherwise, return a promise resolved with undefined. 411 return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv); 412 } 413 414 protected: 415 ~CrossRealmWritableUnderlyingSinkAlgorithms() override = default; 416 417 private: 418 RefPtr<SetUpTransformWritableMessageEventListener> mListener; 419 RefPtr<MessagePort> mPort; 420 }; 421 422 NS_IMPL_CYCLE_COLLECTION_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms, 423 UnderlyingSinkAlgorithmsBase, mListener, 424 mPort) 425 NS_IMPL_ADDREF_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms, 426 UnderlyingSinkAlgorithmsBase) 427 NS_IMPL_RELEASE_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms, 428 UnderlyingSinkAlgorithmsBase) 429 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( 430 CrossRealmWritableUnderlyingSinkAlgorithms) 431 NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsBase) 432 433 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable 434 MOZ_CAN_RUN_SCRIPT static void SetUpCrossRealmTransformWritable( 435 WritableStream* aWritable, MessagePort* aPort, ErrorResult& aRv) { 436 // (This is only needed for step 12, but let's do this early to fail early 437 // enough) 438 AutoJSAPI jsapi; 439 if (!jsapi.Init(aWritable->GetParentObject())) { 440 return; 441 } 442 JSContext* cx = jsapi.cx(); 443 444 // Step 1: Perform ! InitializeWritableStream(stream). 445 // (Done by the constructor) 446 447 // Step 2: Let controller be a new WritableStreamDefaultController. 448 auto controller = MakeRefPtr<WritableStreamDefaultController>( 449 aWritable->GetParentObject(), *aWritable); 450 451 // Step 3: Let backpressurePromise be a new promise. 452 RefPtr<Promise> backpressurePromise = 453 Promise::CreateInfallible(aWritable->GetParentObject()); 454 455 // Step 4: Add a handler for port’s message event with the following steps: 456 auto listener = MakeRefPtr<SetUpTransformWritableMessageEventListener>( 457 controller, backpressurePromise); 458 aPort->AddEventListener(u"message"_ns, listener, false); 459 460 // Step 5: Add a handler for port’s messageerror event with the following 461 // steps: 462 auto errorListener = 463 MakeRefPtr<SetUpTransformWritableMessageErrorEventListener>(controller, 464 aPort); 465 aPort->AddEventListener(u"messageerror"_ns, errorListener, false); 466 467 // Step 6: Enable port’s port message queue. 468 // (Start() does it) 469 aPort->Start(); 470 471 // Step 7 - 10: 472 auto algorithms = 473 MakeRefPtr<CrossRealmWritableUnderlyingSinkAlgorithms>(listener, aPort); 474 475 // Step 11: Let sizeAlgorithm be an algorithm that returns 1. 476 // (nullptr should serve this purpose. See also WritableStream::Constructor) 477 478 // Step 12: Perform ! SetUpWritableStreamDefaultController(stream, controller, 479 // startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, 1, 480 // sizeAlgorithm). 481 SetUpWritableStreamDefaultController(cx, aWritable, controller, algorithms, 1, 482 /* aSizeAlgorithm */ nullptr, aRv); 483 } 484 485 class SetUpTransformReadableMessageEventListener final 486 : public nsIDOMEventListener { 487 public: 488 SetUpTransformReadableMessageEventListener( 489 ReadableStreamDefaultController* aController, MessagePort* aPort) 490 : mController(aController), mPort(aPort) {} 491 492 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 493 NS_DECL_CYCLE_COLLECTION_CLASS(SetUpTransformReadableMessageEventListener) 494 495 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable 496 // The handler steps of Step 3. 497 MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override { 498 auto cleanupPort = 499 MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); }); 500 501 AutoJSAPI jsapi; 502 if (!jsapi.Init(mPort->GetParentObject())) { 503 return NS_OK; 504 } 505 JSContext* cx = jsapi.cx(); 506 MessageEvent* messageEvent = aEvent->AsMessageEvent(); 507 if (NS_WARN_IF(!messageEvent || !messageEvent->IsTrusted())) { 508 return NS_OK; 509 } 510 511 // Step 1: Let data be the data of the message. 512 JS::Rooted<JS::Value> dataValue(cx); 513 IgnoredErrorResult rv; 514 messageEvent->GetData(cx, &dataValue, rv); 515 if (rv.Failed()) { 516 return NS_OK; 517 } 518 519 // Step 2: Assert: Type(data) is Object. 520 // (But we check in runtime instead to avoid potential malicious events from 521 // a compromised process. Same below.) 522 if (NS_WARN_IF(!dataValue.isObject())) { 523 return NS_OK; 524 } 525 JS::Rooted<JSObject*> data(cx, JS::ToObject(cx, dataValue)); 526 527 // Step 3: Let type be ! Get(data, "type"). 528 JS::Rooted<JS::Value> type(cx); 529 if (!JS_GetProperty(cx, data, "type", &type)) { 530 // XXX: See bug 1762233 531 JS_ClearPendingException(cx); 532 return NS_OK; 533 } 534 535 // Step 4: Let value be ! Get(data, "value"). 536 JS::Rooted<JS::Value> value(cx); 537 if (!JS_GetProperty(cx, data, "value", &value)) { 538 JS_ClearPendingException(cx); 539 return NS_OK; 540 } 541 542 // Step 5: Assert: Type(type) is String. 543 if (NS_WARN_IF(!type.isString())) { 544 return NS_OK; 545 } 546 547 // Step 6: If type is "chunk", 548 bool equals = false; 549 if (!JS_StringEqualsLiteral(cx, type.toString(), "chunk", &equals)) { 550 JS_ClearPendingException(cx); 551 return NS_OK; 552 } 553 if (equals) { 554 // Step 6.1: Perform ! ReadableStreamDefaultControllerEnqueue(controller, 555 // value). 556 ReadableStreamDefaultControllerEnqueue(cx, mController, value, 557 IgnoreErrors()); 558 cleanupPort.release(); 559 return NS_OK; // implicit 560 } 561 562 // Step 7: Otherwise, if type is "close", 563 if (!JS_StringEqualsLiteral(cx, type.toString(), "close", &equals)) { 564 JS_ClearPendingException(cx); 565 return NS_OK; 566 } 567 if (equals) { 568 // Step 7.1: Perform ! ReadableStreamDefaultControllerClose(controller). 569 ReadableStreamDefaultControllerClose(cx, mController, IgnoreErrors()); 570 // Step 7.2: Disentangle port. 571 // (Close() does it) 572 mPort->Close(); 573 cleanupPort.release(); 574 return NS_OK; // implicit 575 } 576 577 // Step 8: Otherwise, if type is "error", 578 if (!JS_StringEqualsLiteral(cx, type.toString(), "error", &equals)) { 579 JS_ClearPendingException(cx); 580 return NS_OK; 581 } 582 if (equals) { 583 // Step 8.1: Perform ! ReadableStreamDefaultControllerError(controller, 584 // value). 585 ReadableStreamDefaultControllerError(cx, mController, value, 586 IgnoreErrors()); 587 588 // Step 8.2: Disentangle port. 589 // (Close() does it) 590 mPort->Close(); 591 cleanupPort.release(); 592 return NS_OK; // implicit 593 } 594 595 // Logically it should be unreachable here, but we should expect random 596 // malicious messages. 597 NS_WARNING("Got an unexpected type other than chunk/close/error."); 598 return NS_OK; 599 } 600 601 private: 602 ~SetUpTransformReadableMessageEventListener() = default; 603 604 // mController never changes before CC 605 // TODO: MOZ_IMMUTABLE_OUTSIDE_CC 606 MOZ_KNOWN_LIVE RefPtr<ReadableStreamDefaultController> mController; 607 RefPtr<MessagePort> mPort; 608 }; 609 610 NS_IMPL_CYCLE_COLLECTION(SetUpTransformReadableMessageEventListener, 611 mController, mPort) 612 NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformReadableMessageEventListener) 613 NS_IMPL_CYCLE_COLLECTING_RELEASE(SetUpTransformReadableMessageEventListener) 614 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( 615 SetUpTransformReadableMessageEventListener) 616 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) 617 NS_INTERFACE_MAP_END 618 619 class SetUpTransformReadableMessageErrorEventListener final 620 : public nsIDOMEventListener { 621 public: 622 SetUpTransformReadableMessageErrorEventListener( 623 ReadableStreamDefaultController* aController, MessagePort* aPort) 624 : mController(aController), mPort(aPort) {} 625 626 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 627 NS_DECL_CYCLE_COLLECTION_CLASS( 628 SetUpTransformReadableMessageErrorEventListener) 629 630 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable 631 // The handler steps of Step 4. 632 MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override { 633 auto cleanupPort = 634 MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); }); 635 636 if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) { 637 return NS_OK; 638 } 639 640 // Step 1: Let error be a new "DataCloneError" DOMException. 641 RefPtr<DOMException> exception = 642 DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR); 643 644 AutoJSAPI jsapi; 645 if (!jsapi.Init(mPort->GetParentObject())) { 646 return NS_OK; 647 } 648 JSContext* cx = jsapi.cx(); 649 JS::Rooted<JS::Value> error(cx); 650 if (!ToJSValue(cx, *exception, &error)) { 651 return NS_OK; 652 } 653 654 // Step 2: Perform ! CrossRealmTransformSendError(port, error). 655 CrossRealmTransformSendError(cx, mPort, error); 656 657 // Step 3: Perform ! ReadableStreamDefaultControllerError(controller, 658 // error). 659 ReadableStreamDefaultControllerError(cx, mController, error, 660 IgnoreErrors()); 661 662 // Step 4: Disentangle port. 663 // (Close() does it) 664 mPort->Close(); 665 cleanupPort.release(); 666 667 return NS_OK; 668 } 669 670 private: 671 ~SetUpTransformReadableMessageErrorEventListener() = default; 672 673 RefPtr<ReadableStreamDefaultController> mController; 674 RefPtr<MessagePort> mPort; 675 }; 676 677 NS_IMPL_CYCLE_COLLECTION(SetUpTransformReadableMessageErrorEventListener, 678 mController, mPort) 679 NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformReadableMessageErrorEventListener) 680 NS_IMPL_CYCLE_COLLECTING_RELEASE( 681 SetUpTransformReadableMessageErrorEventListener) 682 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( 683 SetUpTransformReadableMessageErrorEventListener) 684 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) 685 NS_INTERFACE_MAP_END 686 687 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable 688 class CrossRealmReadableUnderlyingSourceAlgorithms final 689 : public UnderlyingSourceAlgorithmsBase { 690 public: 691 NS_DECL_ISUPPORTS_INHERITED 692 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( 693 CrossRealmReadableUnderlyingSourceAlgorithms, 694 UnderlyingSourceAlgorithmsBase) 695 696 explicit CrossRealmReadableUnderlyingSourceAlgorithms(MessagePort* aPort) 697 : mPort(aPort) {} 698 699 void StartCallback(JSContext* aCx, ReadableStreamControllerBase& aController, 700 JS::MutableHandle<JS::Value> aRetVal, 701 ErrorResult& aRv) override { 702 // Step 6. Let startAlgorithm be an algorithm that returns undefined. 703 aRetVal.setUndefined(); 704 } 705 706 already_AddRefed<Promise> PullCallback( 707 JSContext* aCx, ReadableStreamControllerBase& aController, 708 ErrorResult& aRv) override { 709 // Step 7: Let pullAlgorithm be the following steps: 710 711 // Step 7.1: Perform ! PackAndPostMessage(port, "pull", undefined). 712 PackAndPostMessage(aCx, mPort, u"pull"_ns, JS::UndefinedHandleValue, aRv); 713 if (aRv.Failed()) { 714 return nullptr; 715 } 716 717 // Step 7.2: Return a promise resolved with undefined. 718 return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv); 719 } 720 721 already_AddRefed<Promise> CancelCallback( 722 JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason, 723 ErrorResult& aRv) override { 724 // Step 8: Let cancelAlgorithm be the following steps, taking a reason 725 // argument: 726 727 // Step 8.1: Let result be PackAndPostMessageHandlingError(port, "error", 728 // reason). 729 JS::Rooted<JS::Value> error(aCx); 730 bool result = PackAndPostMessageHandlingError( 731 aCx, mPort, u"error"_ns, 732 aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue, 733 &error); 734 735 // Step 8.2: Disentangle port. 736 // (Close() does it) 737 mPort->Close(); 738 739 // Step 8.3: If result is an abrupt completion, return a promise rejected 740 // with result.[[Value]]. 741 if (!result) { 742 return Promise::CreateRejected(mPort->GetParentObject(), error, aRv); 743 } 744 745 // Step 8.4: Otherwise, return a promise resolved with undefined. 746 return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv); 747 } 748 749 protected: 750 ~CrossRealmReadableUnderlyingSourceAlgorithms() override = default; 751 752 private: 753 RefPtr<MessagePort> mPort; 754 }; 755 756 NS_IMPL_CYCLE_COLLECTION_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms, 757 UnderlyingSourceAlgorithmsBase, mPort) 758 NS_IMPL_ADDREF_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms, 759 UnderlyingSourceAlgorithmsBase) 760 NS_IMPL_RELEASE_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms, 761 UnderlyingSourceAlgorithmsBase) 762 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( 763 CrossRealmReadableUnderlyingSourceAlgorithms) 764 NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsBase) 765 766 // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable 767 MOZ_CAN_RUN_SCRIPT static void SetUpCrossRealmTransformReadable( 768 ReadableStream* aReadable, MessagePort* aPort, ErrorResult& aRv) { 769 // (This is only needed for step 10, but let's do this early to fail early 770 // enough) 771 AutoJSAPI jsapi; 772 if (!jsapi.Init(aReadable->GetParentObject())) { 773 return; 774 } 775 JSContext* cx = jsapi.cx(); 776 777 // Step 1: Perform ! InitializeReadableStream(stream). 778 // (This is implicitly done by the constructor) 779 780 // Step 2: Let controller be a new ReadableStreamDefaultController. 781 auto controller = 782 MakeRefPtr<ReadableStreamDefaultController>(aReadable->GetParentObject()); 783 784 // Step 3: Add a handler for port’s message event with the following steps: 785 auto listener = 786 MakeRefPtr<SetUpTransformReadableMessageEventListener>(controller, aPort); 787 aPort->AddEventListener(u"message"_ns, listener, false); 788 789 // Step 4: Add a handler for port’s messageerror event with the following 790 // steps: 791 auto errorListener = 792 MakeRefPtr<SetUpTransformReadableMessageErrorEventListener>(controller, 793 aPort); 794 aPort->AddEventListener(u"messageerror"_ns, errorListener, false); 795 796 // Step 5: Enable port’s port message queue. 797 // (Start() does it) 798 aPort->Start(); 799 800 // Step 6-8: 801 auto algorithms = 802 MakeRefPtr<CrossRealmReadableUnderlyingSourceAlgorithms>(aPort); 803 804 // Step 9: Let sizeAlgorithm be an algorithm that returns 1. 805 // (nullptr should serve this purpose. See also ReadableStream::Constructor) 806 807 // Step 10: Perform ! SetUpReadableStreamDefaultController(stream, controller, 808 // startAlgorithm, pullAlgorithm, cancelAlgorithm, 0, sizeAlgorithm). 809 SetUpReadableStreamDefaultController(cx, aReadable, controller, algorithms, 0, 810 /* aSizeAlgorithm */ nullptr, aRv); 811 } 812 813 // https://streams.spec.whatwg.org/#ref-for-transfer-steps 814 bool ReadableStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId) { 815 // Step 1: If ! IsReadableStreamLocked(value) is true, throw a 816 // "DataCloneError" DOMException. 817 // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double 818 // check here as the state might have changed in case this ReadableStream is 819 // created by a TransferStream and being transferred together with the 820 // parent.) 821 if (IsReadableStreamLocked(this)) { 822 return false; 823 } 824 825 // Step 2: Let port1 be a new MessagePort in the current Realm. 826 // Step 3: Let port2 be a new MessagePort in the current Realm. 827 // Step 4: Entangle port1 and port2. 828 // (The MessageChannel constructor does exactly that.) 829 // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messagechannel 830 ErrorResult rv; 831 RefPtr<dom::MessageChannel> channel = 832 dom::MessageChannel::Constructor(mGlobal, rv); 833 if (rv.MaybeSetPendingException(aCx)) { 834 return false; 835 } 836 837 // Step 5: Let writable be a new WritableStream in the current Realm. 838 RefPtr<WritableStream> writable = new WritableStream( 839 mGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit); 840 841 // Step 6: Perform ! SetUpCrossRealmTransformWritable(writable, port1). 842 // MOZ_KnownLive because Port1 never changes before CC 843 SetUpCrossRealmTransformWritable(writable, MOZ_KnownLive(channel->Port1()), 844 rv); 845 if (rv.MaybeSetPendingException(aCx)) { 846 return false; 847 } 848 849 // Step 7: Let promise be ! ReadableStreamPipeTo(value, writable, false, 850 // false, false). 851 RefPtr<Promise> promise = 852 ReadableStreamPipeTo(this, writable, false, false, false, nullptr, rv); 853 if (rv.MaybeSetPendingException(aCx)) { 854 return false; 855 } 856 857 // Step 8: Set promise.[[PromiseIsHandled]] to true. 858 MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled()); 859 860 // Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, 861 // « port2 »). 862 channel->Port2()->CloneAndDisentangle(aPortId); 863 864 return true; 865 } 866 867 // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps 868 MOZ_CAN_RUN_SCRIPT already_AddRefed<ReadableStream> 869 ReadableStream::ReceiveTransferImpl(JSContext* aCx, nsIGlobalObject* aGlobal, 870 MessagePort& aPort) { 871 // Step 1: Let deserializedRecord be 872 // ! StructuredDeserializeWithTransfer(dataHolder.[[port]], the current 873 // Realm). 874 // Step 2: Let port be deserializedRecord.[[Deserialized]]. 875 876 // Step 3: Perform ! SetUpCrossRealmTransformReadable(value, port). 877 RefPtr<ReadableStream> readable = 878 new ReadableStream(aGlobal, HoldDropJSObjectsCaller::Implicit); 879 ErrorResult rv; 880 SetUpCrossRealmTransformReadable(readable, &aPort, rv); 881 if (rv.MaybeSetPendingException(aCx)) { 882 return nullptr; 883 } 884 return readable.forget(); 885 } 886 887 bool ReadableStream::ReceiveTransfer( 888 JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort, 889 JS::MutableHandle<JSObject*> aReturnObject) { 890 RefPtr<ReadableStream> readable = 891 ReadableStream::ReceiveTransferImpl(aCx, aGlobal, aPort); 892 if (!readable) { 893 return false; 894 } 895 896 JS::Rooted<JS::Value> value(aCx); 897 if (!GetOrCreateDOMReflector(aCx, readable, &value)) { 898 JS_ClearPendingException(aCx); 899 return false; 900 } 901 aReturnObject.set(&value.toObject()); 902 903 return true; 904 } 905 906 // https://streams.spec.whatwg.org/#ref-for-transfer-steps① 907 bool WritableStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId) { 908 // Step 1: If ! IsWritableStreamLocked(value) is true, throw a 909 // "DataCloneError" DOMException. 910 // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double 911 // check here as the state might have changed in case this WritableStream is 912 // created by a TransferStream and being transferred together with the 913 // parent.) 914 if (IsWritableStreamLocked(this)) { 915 return false; 916 } 917 918 // Step 2: Let port1 be a new MessagePort in the current Realm. 919 // Step 3: Let port2 be a new MessagePort in the current Realm. 920 // Step 4: Entangle port1 and port2. 921 // (The MessageChannel constructor does exactly that.) 922 // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messagechannel 923 ErrorResult rv; 924 RefPtr<dom::MessageChannel> channel = 925 dom::MessageChannel::Constructor(mGlobal, rv); 926 if (rv.MaybeSetPendingException(aCx)) { 927 return false; 928 } 929 930 // Step 5: Let readable be a new ReadableStream in the current Realm. 931 RefPtr<ReadableStream> readable = new ReadableStream( 932 mGlobal, ReadableStream::HoldDropJSObjectsCaller::Implicit); 933 934 // Step 6: Perform ! SetUpCrossRealmTransformReadable(readable, port1). 935 // MOZ_KnownLive because Port1 never changes before CC 936 SetUpCrossRealmTransformReadable(readable, MOZ_KnownLive(channel->Port1()), 937 rv); 938 if (rv.MaybeSetPendingException(aCx)) { 939 return false; 940 } 941 942 // Step 7: Let promise be ! ReadableStreamPipeTo(readable, value, false, 943 // false, false). 944 RefPtr<Promise> promise = 945 ReadableStreamPipeTo(readable, this, false, false, false, nullptr, rv); 946 if (rv.Failed()) { 947 return false; 948 } 949 950 // Step 8: Set promise.[[PromiseIsHandled]] to true. 951 MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled()); 952 953 // Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2, 954 // « port2 »). 955 channel->Port2()->CloneAndDisentangle(aPortId); 956 957 return true; 958 } 959 960 // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps① 961 MOZ_CAN_RUN_SCRIPT already_AddRefed<WritableStream> 962 WritableStream::ReceiveTransferImpl(JSContext* aCx, nsIGlobalObject* aGlobal, 963 MessagePort& aPort) { 964 // Step 1: Let deserializedRecord be ! 965 // StructuredDeserializeWithTransfer(dataHolder.[[port]], the current Realm). 966 // Step 2: Let port be a deserializedRecord.[[Deserialized]]. 967 968 // Step 3: Perform ! SetUpCrossRealmTransformWritable(value, port). 969 RefPtr<WritableStream> writable = new WritableStream( 970 aGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit); 971 ErrorResult rv; 972 SetUpCrossRealmTransformWritable(writable, &aPort, rv); 973 if (rv.MaybeSetPendingException(aCx)) { 974 return nullptr; 975 } 976 return writable.forget(); 977 } 978 979 // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps① 980 bool WritableStream::ReceiveTransfer( 981 JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort, 982 JS::MutableHandle<JSObject*> aReturnObject) { 983 RefPtr<WritableStream> writable = 984 WritableStream::ReceiveTransferImpl(aCx, aGlobal, aPort); 985 if (!writable) { 986 return false; 987 } 988 989 JS::Rooted<JS::Value> value(aCx); 990 if (!GetOrCreateDOMReflector(aCx, writable, &value)) { 991 JS_ClearPendingException(aCx); 992 return false; 993 } 994 aReturnObject.set(&value.toObject()); 995 996 return true; 997 } 998 999 // https://streams.spec.whatwg.org/#ref-for-transfer-steps② 1000 bool TransformStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId1, 1001 UniqueMessagePortId& aPortId2) { 1002 // Step 1: Let readable be value.[[readable]]. 1003 // Step 2: Let writable be value.[[writable]]. 1004 // Step 3: If ! IsReadableStreamLocked(readable) is true, throw a 1005 // "DataCloneError" DOMException. 1006 // Step 4: If ! IsWritableStreamLocked(writable) is true, throw a 1007 // "DataCloneError" DOMException. 1008 // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double 1009 // check here as the state might have changed by 1010 // Readable/WritableStream::Transfer in case the stream members of this 1011 // TransformStream are being transferred together.) 1012 if (IsReadableStreamLocked(mReadable) || IsWritableStreamLocked(mWritable)) { 1013 return false; 1014 } 1015 1016 // Step 5: Set dataHolder.[[readable]] to ! 1017 // StructuredSerializeWithTransfer(readable, « readable »). 1018 // TODO: Mark mReadable as MOZ_KNOWN_LIVE again (bug 1769854) 1019 if (!MOZ_KnownLive(mReadable)->Transfer(aCx, aPortId1)) { 1020 return false; 1021 } 1022 1023 // Step 6: Set dataHolder.[[writable]] to ! 1024 // StructuredSerializeWithTransfer(writable, « writable »). 1025 // TODO: Mark mReadable as MOZ_KNOWN_LIVE again (bug 1769854) 1026 return MOZ_KnownLive(mWritable)->Transfer(aCx, aPortId2); 1027 } 1028 1029 // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps② 1030 bool TransformStream::ReceiveTransfer( 1031 JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort1, 1032 MessagePort& aPort2, JS::MutableHandle<JSObject*> aReturnObject) { 1033 // Step 1: Let readableRecord be ! 1034 // StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current 1035 // Realm). 1036 RefPtr<ReadableStream> readable = 1037 ReadableStream::ReceiveTransferImpl(aCx, aGlobal, aPort1); 1038 if (!readable) { 1039 return false; 1040 } 1041 1042 // Step 2: Let writableRecord be ! 1043 // StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current 1044 // Realm). 1045 RefPtr<WritableStream> writable = 1046 WritableStream::ReceiveTransferImpl(aCx, aGlobal, aPort2); 1047 if (!writable) { 1048 return false; 1049 } 1050 1051 // Step 3: Set value.[[readable]] to readableRecord.[[Deserialized]]. 1052 // Step 4: Set value.[[writable]] to writableRecord.[[Deserialized]]. 1053 // Step 5: Set value.[[backpressure]], value.[[backpressureChangePromise]], 1054 // and value.[[controller]] to undefined. 1055 RefPtr<TransformStream> stream = 1056 new TransformStream(aGlobal, readable, writable); 1057 JS::Rooted<JS::Value> value(aCx); 1058 if (!GetOrCreateDOMReflector(aCx, stream, &value)) { 1059 JS_ClearPendingException(aCx); 1060 return false; 1061 } 1062 aReturnObject.set(&value.toObject()); 1063 1064 return true; 1065 } 1066 1067 } // namespace mozilla::dom