1
0
Fork 0
firefox/dom/streams/Transferable.cpp
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

1068 lines
38 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ErrorList.h"
#include "ReadableStreamPipeTo.h"
#include "js/RootingAPI.h"
#include "js/String.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/dom/DOMExceptionBinding.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIDOMEventListener.h"
#include "nsIGlobalObject.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/dom/DOMException.h"
#include "mozilla/dom/MessageEvent.h"
#include "mozilla/dom/MessageChannel.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/WritableStream.h"
#include "mozilla/dom/TransformStream.h"
#include "nsISupportsImpl.h"
namespace mozilla::dom {
using namespace streams_abstract;
static void PackAndPostMessage(JSContext* aCx, MessagePort* aPort,
const nsAString& aType,
JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
JS::Rooted<JSObject*> obj(aCx,
JS_NewObjectWithGivenProto(aCx, nullptr, nullptr));
if (!obj) {
// XXX: Should we crash here and there? See also bug 1762233.
JS_ClearPendingException(aCx);
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
JS::Rooted<JS::Value> type(aCx);
if (!xpc::NonVoidStringToJsval(aCx, aType, &type)) {
JS_ClearPendingException(aCx);
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
if (!JS_DefineProperty(aCx, obj, "type", type, JSPROP_ENUMERATE)) {
JS_ClearPendingException(aCx);
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
JS::Rooted<JS::Value> value(aCx, aValue);
if (!JS_WrapValue(aCx, &value)) {
JS_ClearPendingException(aCx);
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
if (!JS_DefineProperty(aCx, obj, "value", value, JSPROP_ENUMERATE)) {
JS_ClearPendingException(aCx);
aRv.Throw(NS_ERROR_UNEXPECTED);
return;
}
Sequence<JSObject*> transferables; // none in this case
JS::Rooted<JS::Value> objValue(aCx, JS::ObjectValue(*obj));
aPort->PostMessage(aCx, objValue, transferables, aRv);
}
// https://streams.spec.whatwg.org/#abstract-opdef-crossrealmtransformsenderror
static void CrossRealmTransformSendError(JSContext* aCx, MessagePort* aPort,
JS::Handle<JS::Value> aError) {
// Step 1: Perform PackAndPostMessage(port, "error", error), discarding the
// result.
PackAndPostMessage(aCx, aPort, u"error"_ns, aError, IgnoreErrors());
}
class SetUpTransformWritableMessageEventListener final
: public nsIDOMEventListener {
public:
SetUpTransformWritableMessageEventListener(
WritableStreamDefaultController* aController, Promise* aPromise)
: mController(aController), mBackpressurePromise(aPromise) {}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(SetUpTransformWritableMessageEventListener)
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
// The handler steps of Step 4.
MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
AutoJSAPI jsapi;
if (!jsapi.Init(mController->GetParentObject())) {
return NS_OK;
}
JSContext* cx = jsapi.cx();
MessageEvent* messageEvent = aEvent->AsMessageEvent();
if (NS_WARN_IF(!messageEvent || !messageEvent->IsTrusted())) {
return NS_OK;
}
// Step 1: Let data be the data of the message.
JS::Rooted<JS::Value> dataValue(cx);
IgnoredErrorResult rv;
messageEvent->GetData(cx, &dataValue, rv);
if (rv.Failed()) {
return NS_OK;
}
// Step 2: Assert: Type(data) is Object.
// (But we check in runtime instead to avoid potential malicious events from
// a compromised process. Same below.)
if (NS_WARN_IF(!dataValue.isObject())) {
return NS_OK;
}
JS::Rooted<JSObject*> data(cx, &dataValue.toObject());
// Step 3: Let type be ! Get(data, "type").
JS::Rooted<JS::Value> type(cx);
if (!JS_GetProperty(cx, data, "type", &type)) {
// XXX: See bug 1762233
JS_ClearPendingException(cx);
return NS_OK;
}
// Step 4: Let value be ! Get(data, "value").
JS::Rooted<JS::Value> value(cx);
if (!JS_GetProperty(cx, data, "value", &value)) {
JS_ClearPendingException(cx);
return NS_OK;
}
// Step 5: Assert: Type(type) is String.
if (NS_WARN_IF(!type.isString())) {
return NS_OK;
}
// Step 6: If type is "pull",
bool equals = false;
if (!JS_StringEqualsLiteral(cx, type.toString(), "pull", &equals)) {
JS_ClearPendingException(cx);
return NS_OK;
}
if (equals) {
// Step 6.1: If backpressurePromise is not undefined,
MaybeResolveAndClearBackpressurePromise();
return NS_OK; // implicit
}
// Step 7: If type is "error",
if (!JS_StringEqualsLiteral(cx, type.toString(), "error", &equals)) {
JS_ClearPendingException(cx);
return NS_OK;
}
if (equals) {
// Step 7.1: Perform !
// WritableStreamDefaultControllerErrorIfNeeded(controller, value).
WritableStreamDefaultControllerErrorIfNeeded(cx, mController, value, rv);
if (rv.Failed()) {
return NS_OK;
}
// Step 7.2: If backpressurePromise is not undefined,
MaybeResolveAndClearBackpressurePromise();
return NS_OK; // implicit
}
// Logically it should be unreachable here, but we should expect random
// malicious messages.
NS_WARNING("Got an unexpected type other than pull/error.");
return NS_OK;
}
void MaybeResolveAndClearBackpressurePromise() {
if (mBackpressurePromise) {
mBackpressurePromise->MaybeResolveWithUndefined();
mBackpressurePromise = nullptr;
}
}
// Note: This promise field is shared with the sink algorithms.
Promise* BackpressurePromise() { return mBackpressurePromise; }
void CreateBackpressurePromise() {
mBackpressurePromise =
Promise::CreateInfallible(mController->GetParentObject());
}
private:
~SetUpTransformWritableMessageEventListener() = default;
// mController never changes before CC
// TODO: MOZ_IMMUTABLE_OUTSIDE_CC
MOZ_KNOWN_LIVE RefPtr<WritableStreamDefaultController> mController;
RefPtr<Promise> mBackpressurePromise;
};
NS_IMPL_CYCLE_COLLECTION(SetUpTransformWritableMessageEventListener,
mController, mBackpressurePromise)
NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformWritableMessageEventListener)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SetUpTransformWritableMessageEventListener)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
SetUpTransformWritableMessageEventListener)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_END
class SetUpTransformWritableMessageErrorEventListener final
: public nsIDOMEventListener {
public:
SetUpTransformWritableMessageErrorEventListener(
WritableStreamDefaultController* aController, MessagePort* aPort)
: mController(aController), mPort(aPort) {}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(
SetUpTransformWritableMessageErrorEventListener)
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
// The handler steps of Step 5.
MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
auto cleanupPort =
MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) {
return NS_OK;
}
// Step 1: Let error be a new "DataCloneError" DOMException.
RefPtr<DOMException> exception =
DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR);
AutoJSAPI jsapi;
if (!jsapi.Init(mPort->GetParentObject())) {
return NS_OK;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JS::Value> error(cx);
if (!ToJSValue(cx, *exception, &error)) {
return NS_OK;
}
// Step 2: Perform ! CrossRealmTransformSendError(port, error).
CrossRealmTransformSendError(cx, mPort, error);
// Step 3: Perform
// ! WritableStreamDefaultControllerErrorIfNeeded(controller, error).
WritableStreamDefaultControllerErrorIfNeeded(cx, mController, error,
IgnoreErrors());
// Step 4: Disentangle port.
// (Close() does it)
mPort->Close();
cleanupPort.release();
return NS_OK;
}
private:
~SetUpTransformWritableMessageErrorEventListener() = default;
// mController never changes before CC
// TODO: MOZ_IMMUTABLE_OUTSIDE_CC
MOZ_KNOWN_LIVE RefPtr<WritableStreamDefaultController> mController;
RefPtr<MessagePort> mPort;
};
NS_IMPL_CYCLE_COLLECTION(SetUpTransformWritableMessageErrorEventListener,
mController, mPort)
NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformWritableMessageErrorEventListener)
NS_IMPL_CYCLE_COLLECTING_RELEASE(
SetUpTransformWritableMessageErrorEventListener)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
SetUpTransformWritableMessageErrorEventListener)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_END
// https://streams.spec.whatwg.org/#abstract-opdef-packandpostmessagehandlingerror
static bool PackAndPostMessageHandlingError(
JSContext* aCx, mozilla::dom::MessagePort* aPort, const nsAString& aType,
JS::Handle<JS::Value> aValue, JS::MutableHandle<JS::Value> aError) {
// Step 1: Let result be PackAndPostMessage(port, type, value).
ErrorResult rv;
PackAndPostMessage(aCx, aPort, aType, aValue, rv);
// Step 2: If result is an abrupt completion,
if (rv.Failed()) {
// Step 2.2: Perform ! CrossRealmTransformSendError(port, result.[[Value]]).
MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), aError));
CrossRealmTransformSendError(aCx, aPort, aError);
return false;
}
// Step 3: Return result as a completion record.
return true;
}
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
class CrossRealmWritableUnderlyingSinkAlgorithms final
: public UnderlyingSinkAlgorithmsBase {
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
CrossRealmWritableUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
CrossRealmWritableUnderlyingSinkAlgorithms(
SetUpTransformWritableMessageEventListener* aListener, MessagePort* aPort)
: mListener(aListener), mPort(aPort) {}
void StartCallback(JSContext* aCx,
WritableStreamDefaultController& aController,
JS::MutableHandle<JS::Value> aRetVal,
ErrorResult& aRv) override {
// Step 7. Let startAlgorithm be an algorithm that returns undefined.
aRetVal.setUndefined();
}
already_AddRefed<Promise> WriteCallback(
JSContext* aCx, JS::Handle<JS::Value> aChunk,
WritableStreamDefaultController& aController, ErrorResult& aRv) override {
// Step 1: If backpressurePromise is undefined, set backpressurePromise to a
// promise resolved with undefined.
// Note: This promise field is shared with the message event listener.
if (!mListener->BackpressurePromise()) {
mListener->CreateBackpressurePromise();
mListener->BackpressurePromise()->MaybeResolveWithUndefined();
}
// Step 2: Return the result of reacting to backpressurePromise with the
// following fulfillment steps:
auto result =
mListener->BackpressurePromise()->ThenWithCycleCollectedArgsJS(
[](JSContext* aCx, JS::Handle<JS::Value>, ErrorResult& aRv,
SetUpTransformWritableMessageEventListener* aListener,
MessagePort* aPort,
JS::Handle<JS::Value> aChunk) -> already_AddRefed<Promise> {
// Step 2.1: Set backpressurePromise to a new promise.
aListener->CreateBackpressurePromise();
// Step 2.2: Let result be PackAndPostMessageHandlingError(port,
// "chunk", chunk).
JS::Rooted<JS::Value> error(aCx);
bool result = PackAndPostMessageHandlingError(
aCx, aPort, u"chunk"_ns, aChunk, &error);
// Step 2.3: If result is an abrupt completion,
if (!result) {
// Step 2.3.1: Disentangle port.
// (Close() does it)
aPort->Close();
// Step 2.3.2: Return a promise rejected with result.[[Value]].
return Promise::CreateRejected(aPort->GetParentObject(), error,
aRv);
}
// Step 2.4: Otherwise, return a promise resolved with undefined.
return Promise::CreateResolvedWithUndefined(
aPort->GetParentObject(), aRv);
},
std::make_tuple(mListener, mPort), std::make_tuple(aChunk));
if (result.isErr()) {
aRv.Throw(result.unwrapErr());
return nullptr;
}
return result.unwrap().forget();
}
already_AddRefed<Promise> CloseCallback(JSContext* aCx,
ErrorResult& aRv) override {
// Step 1: Perform ! PackAndPostMessage(port, "close", undefined).
PackAndPostMessage(aCx, mPort, u"close"_ns, JS::UndefinedHandleValue, aRv);
// (We'll check the result after step 2)
// Step 2: Disentangle port.
// (Close() will do this)
mPort->Close();
if (aRv.Failed()) {
return nullptr;
}
// Step 3: Return a promise resolved with undefined.
return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
}
already_AddRefed<Promise> AbortCallback(
JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
ErrorResult& aRv) override {
// Step 1: Let result be PackAndPostMessageHandlingError(port, "error",
// reason).
JS::Rooted<JS::Value> error(aCx);
bool result = PackAndPostMessageHandlingError(
aCx, mPort, u"error"_ns,
aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue,
&error);
// Step 2: Disentangle port.
// (Close() will do this)
mPort->Close();
// Step 3: If result is an abrupt completion, return a promise rejected with
// result.[[Value]].
if (!result) {
return Promise::CreateRejected(mPort->GetParentObject(), error, aRv);
}
// Step 4: Otherwise, return a promise resolved with undefined.
return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
}
protected:
~CrossRealmWritableUnderlyingSinkAlgorithms() override = default;
private:
RefPtr<SetUpTransformWritableMessageEventListener> mListener;
RefPtr<MessagePort> mPort;
};
NS_IMPL_CYCLE_COLLECTION_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms,
UnderlyingSinkAlgorithmsBase, mListener,
mPort)
NS_IMPL_ADDREF_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms,
UnderlyingSinkAlgorithmsBase)
NS_IMPL_RELEASE_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms,
UnderlyingSinkAlgorithmsBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
CrossRealmWritableUnderlyingSinkAlgorithms)
NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsBase)
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
MOZ_CAN_RUN_SCRIPT static void SetUpCrossRealmTransformWritable(
WritableStream* aWritable, MessagePort* aPort, ErrorResult& aRv) {
// (This is only needed for step 12, but let's do this early to fail early
// enough)
AutoJSAPI jsapi;
if (!jsapi.Init(aWritable->GetParentObject())) {
return;
}
JSContext* cx = jsapi.cx();
// Step 1: Perform ! InitializeWritableStream(stream).
// (Done by the constructor)
// Step 2: Let controller be a new WritableStreamDefaultController.
auto controller = MakeRefPtr<WritableStreamDefaultController>(
aWritable->GetParentObject(), *aWritable);
// Step 3: Let backpressurePromise be a new promise.
RefPtr<Promise> backpressurePromise =
Promise::CreateInfallible(aWritable->GetParentObject());
// Step 4: Add a handler for ports message event with the following steps:
auto listener = MakeRefPtr<SetUpTransformWritableMessageEventListener>(
controller, backpressurePromise);
aPort->AddEventListener(u"message"_ns, listener, false);
// Step 5: Add a handler for ports messageerror event with the following
// steps:
auto errorListener =
MakeRefPtr<SetUpTransformWritableMessageErrorEventListener>(controller,
aPort);
aPort->AddEventListener(u"messageerror"_ns, errorListener, false);
// Step 6: Enable ports port message queue.
// (Start() does it)
aPort->Start();
// Step 7 - 10:
auto algorithms =
MakeRefPtr<CrossRealmWritableUnderlyingSinkAlgorithms>(listener, aPort);
// Step 11: Let sizeAlgorithm be an algorithm that returns 1.
// (nullptr should serve this purpose. See also WritableStream::Constructor)
// Step 12: Perform ! SetUpWritableStreamDefaultController(stream, controller,
// startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, 1,
// sizeAlgorithm).
SetUpWritableStreamDefaultController(cx, aWritable, controller, algorithms, 1,
/* aSizeAlgorithm */ nullptr, aRv);
}
class SetUpTransformReadableMessageEventListener final
: public nsIDOMEventListener {
public:
SetUpTransformReadableMessageEventListener(
ReadableStreamDefaultController* aController, MessagePort* aPort)
: mController(aController), mPort(aPort) {}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(SetUpTransformReadableMessageEventListener)
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
// The handler steps of Step 3.
MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
auto cleanupPort =
MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
AutoJSAPI jsapi;
if (!jsapi.Init(mPort->GetParentObject())) {
return NS_OK;
}
JSContext* cx = jsapi.cx();
MessageEvent* messageEvent = aEvent->AsMessageEvent();
if (NS_WARN_IF(!messageEvent || !messageEvent->IsTrusted())) {
return NS_OK;
}
// Step 1: Let data be the data of the message.
JS::Rooted<JS::Value> dataValue(cx);
IgnoredErrorResult rv;
messageEvent->GetData(cx, &dataValue, rv);
if (rv.Failed()) {
return NS_OK;
}
// Step 2: Assert: Type(data) is Object.
// (But we check in runtime instead to avoid potential malicious events from
// a compromised process. Same below.)
if (NS_WARN_IF(!dataValue.isObject())) {
return NS_OK;
}
JS::Rooted<JSObject*> data(cx, JS::ToObject(cx, dataValue));
// Step 3: Let type be ! Get(data, "type").
JS::Rooted<JS::Value> type(cx);
if (!JS_GetProperty(cx, data, "type", &type)) {
// XXX: See bug 1762233
JS_ClearPendingException(cx);
return NS_OK;
}
// Step 4: Let value be ! Get(data, "value").
JS::Rooted<JS::Value> value(cx);
if (!JS_GetProperty(cx, data, "value", &value)) {
JS_ClearPendingException(cx);
return NS_OK;
}
// Step 5: Assert: Type(type) is String.
if (NS_WARN_IF(!type.isString())) {
return NS_OK;
}
// Step 6: If type is "chunk",
bool equals = false;
if (!JS_StringEqualsLiteral(cx, type.toString(), "chunk", &equals)) {
JS_ClearPendingException(cx);
return NS_OK;
}
if (equals) {
// Step 6.1: Perform ! ReadableStreamDefaultControllerEnqueue(controller,
// value).
ReadableStreamDefaultControllerEnqueue(cx, mController, value,
IgnoreErrors());
cleanupPort.release();
return NS_OK; // implicit
}
// Step 7: Otherwise, if type is "close",
if (!JS_StringEqualsLiteral(cx, type.toString(), "close", &equals)) {
JS_ClearPendingException(cx);
return NS_OK;
}
if (equals) {
// Step 7.1: Perform ! ReadableStreamDefaultControllerClose(controller).
ReadableStreamDefaultControllerClose(cx, mController, IgnoreErrors());
// Step 7.2: Disentangle port.
// (Close() does it)
mPort->Close();
cleanupPort.release();
return NS_OK; // implicit
}
// Step 8: Otherwise, if type is "error",
if (!JS_StringEqualsLiteral(cx, type.toString(), "error", &equals)) {
JS_ClearPendingException(cx);
return NS_OK;
}
if (equals) {
// Step 8.1: Perform ! ReadableStreamDefaultControllerError(controller,
// value).
ReadableStreamDefaultControllerError(cx, mController, value,
IgnoreErrors());
// Step 8.2: Disentangle port.
// (Close() does it)
mPort->Close();
cleanupPort.release();
return NS_OK; // implicit
}
// Logically it should be unreachable here, but we should expect random
// malicious messages.
NS_WARNING("Got an unexpected type other than chunk/close/error.");
return NS_OK;
}
private:
~SetUpTransformReadableMessageEventListener() = default;
// mController never changes before CC
// TODO: MOZ_IMMUTABLE_OUTSIDE_CC
MOZ_KNOWN_LIVE RefPtr<ReadableStreamDefaultController> mController;
RefPtr<MessagePort> mPort;
};
NS_IMPL_CYCLE_COLLECTION(SetUpTransformReadableMessageEventListener,
mController, mPort)
NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformReadableMessageEventListener)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SetUpTransformReadableMessageEventListener)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
SetUpTransformReadableMessageEventListener)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_END
class SetUpTransformReadableMessageErrorEventListener final
: public nsIDOMEventListener {
public:
SetUpTransformReadableMessageErrorEventListener(
ReadableStreamDefaultController* aController, MessagePort* aPort)
: mController(aController), mPort(aPort) {}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(
SetUpTransformReadableMessageErrorEventListener)
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
// The handler steps of Step 4.
MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
auto cleanupPort =
MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) {
return NS_OK;
}
// Step 1: Let error be a new "DataCloneError" DOMException.
RefPtr<DOMException> exception =
DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR);
AutoJSAPI jsapi;
if (!jsapi.Init(mPort->GetParentObject())) {
return NS_OK;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JS::Value> error(cx);
if (!ToJSValue(cx, *exception, &error)) {
return NS_OK;
}
// Step 2: Perform ! CrossRealmTransformSendError(port, error).
CrossRealmTransformSendError(cx, mPort, error);
// Step 3: Perform ! ReadableStreamDefaultControllerError(controller,
// error).
ReadableStreamDefaultControllerError(cx, mController, error,
IgnoreErrors());
// Step 4: Disentangle port.
// (Close() does it)
mPort->Close();
cleanupPort.release();
return NS_OK;
}
private:
~SetUpTransformReadableMessageErrorEventListener() = default;
RefPtr<ReadableStreamDefaultController> mController;
RefPtr<MessagePort> mPort;
};
NS_IMPL_CYCLE_COLLECTION(SetUpTransformReadableMessageErrorEventListener,
mController, mPort)
NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformReadableMessageErrorEventListener)
NS_IMPL_CYCLE_COLLECTING_RELEASE(
SetUpTransformReadableMessageErrorEventListener)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
SetUpTransformReadableMessageErrorEventListener)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_END
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
class CrossRealmReadableUnderlyingSourceAlgorithms final
: public UnderlyingSourceAlgorithmsBase {
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
CrossRealmReadableUnderlyingSourceAlgorithms,
UnderlyingSourceAlgorithmsBase)
explicit CrossRealmReadableUnderlyingSourceAlgorithms(MessagePort* aPort)
: mPort(aPort) {}
void StartCallback(JSContext* aCx, ReadableStreamControllerBase& aController,
JS::MutableHandle<JS::Value> aRetVal,
ErrorResult& aRv) override {
// Step 6. Let startAlgorithm be an algorithm that returns undefined.
aRetVal.setUndefined();
}
already_AddRefed<Promise> PullCallback(
JSContext* aCx, ReadableStreamControllerBase& aController,
ErrorResult& aRv) override {
// Step 7: Let pullAlgorithm be the following steps:
// Step 7.1: Perform ! PackAndPostMessage(port, "pull", undefined).
PackAndPostMessage(aCx, mPort, u"pull"_ns, JS::UndefinedHandleValue, aRv);
if (aRv.Failed()) {
return nullptr;
}
// Step 7.2: Return a promise resolved with undefined.
return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
}
already_AddRefed<Promise> CancelCallback(
JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
ErrorResult& aRv) override {
// Step 8: Let cancelAlgorithm be the following steps, taking a reason
// argument:
// Step 8.1: Let result be PackAndPostMessageHandlingError(port, "error",
// reason).
JS::Rooted<JS::Value> error(aCx);
bool result = PackAndPostMessageHandlingError(
aCx, mPort, u"error"_ns,
aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue,
&error);
// Step 8.2: Disentangle port.
// (Close() does it)
mPort->Close();
// Step 8.3: If result is an abrupt completion, return a promise rejected
// with result.[[Value]].
if (!result) {
return Promise::CreateRejected(mPort->GetParentObject(), error, aRv);
}
// Step 8.4: Otherwise, return a promise resolved with undefined.
return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
}
protected:
~CrossRealmReadableUnderlyingSourceAlgorithms() override = default;
private:
RefPtr<MessagePort> mPort;
};
NS_IMPL_CYCLE_COLLECTION_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms,
UnderlyingSourceAlgorithmsBase, mPort)
NS_IMPL_ADDREF_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms,
UnderlyingSourceAlgorithmsBase)
NS_IMPL_RELEASE_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms,
UnderlyingSourceAlgorithmsBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
CrossRealmReadableUnderlyingSourceAlgorithms)
NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsBase)
// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
MOZ_CAN_RUN_SCRIPT static void SetUpCrossRealmTransformReadable(
ReadableStream* aReadable, MessagePort* aPort, ErrorResult& aRv) {
// (This is only needed for step 10, but let's do this early to fail early
// enough)
AutoJSAPI jsapi;
if (!jsapi.Init(aReadable->GetParentObject())) {
return;
}
JSContext* cx = jsapi.cx();
// Step 1: Perform ! InitializeReadableStream(stream).
// (This is implicitly done by the constructor)
// Step 2: Let controller be a new ReadableStreamDefaultController.
auto controller =
MakeRefPtr<ReadableStreamDefaultController>(aReadable->GetParentObject());
// Step 3: Add a handler for ports message event with the following steps:
auto listener =
MakeRefPtr<SetUpTransformReadableMessageEventListener>(controller, aPort);
aPort->AddEventListener(u"message"_ns, listener, false);
// Step 4: Add a handler for ports messageerror event with the following
// steps:
auto errorListener =
MakeRefPtr<SetUpTransformReadableMessageErrorEventListener>(controller,
aPort);
aPort->AddEventListener(u"messageerror"_ns, errorListener, false);
// Step 5: Enable ports port message queue.
// (Start() does it)
aPort->Start();
// Step 6-8:
auto algorithms =
MakeRefPtr<CrossRealmReadableUnderlyingSourceAlgorithms>(aPort);
// Step 9: Let sizeAlgorithm be an algorithm that returns 1.
// (nullptr should serve this purpose. See also ReadableStream::Constructor)
// Step 10: Perform ! SetUpReadableStreamDefaultController(stream, controller,
// startAlgorithm, pullAlgorithm, cancelAlgorithm, 0, sizeAlgorithm).
SetUpReadableStreamDefaultController(cx, aReadable, controller, algorithms, 0,
/* aSizeAlgorithm */ nullptr, aRv);
}
// https://streams.spec.whatwg.org/#ref-for-transfer-steps
bool ReadableStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId) {
// Step 1: If ! IsReadableStreamLocked(value) is true, throw a
// "DataCloneError" DOMException.
// (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
// check here as the state might have changed in case this ReadableStream is
// created by a TransferStream and being transferred together with the
// parent.)
if (IsReadableStreamLocked(this)) {
return false;
}
// Step 2: Let port1 be a new MessagePort in the current Realm.
// Step 3: Let port2 be a new MessagePort in the current Realm.
// Step 4: Entangle port1 and port2.
// (The MessageChannel constructor does exactly that.)
// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messagechannel
ErrorResult rv;
RefPtr<dom::MessageChannel> channel =
dom::MessageChannel::Constructor(mGlobal, rv);
if (rv.MaybeSetPendingException(aCx)) {
return false;
}
// Step 5: Let writable be a new WritableStream in the current Realm.
RefPtr<WritableStream> writable = new WritableStream(
mGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit);
// Step 6: Perform ! SetUpCrossRealmTransformWritable(writable, port1).
// MOZ_KnownLive because Port1 never changes before CC
SetUpCrossRealmTransformWritable(writable, MOZ_KnownLive(channel->Port1()),
rv);
if (rv.MaybeSetPendingException(aCx)) {
return false;
}
// Step 7: Let promise be ! ReadableStreamPipeTo(value, writable, false,
// false, false).
RefPtr<Promise> promise =
ReadableStreamPipeTo(this, writable, false, false, false, nullptr, rv);
if (rv.MaybeSetPendingException(aCx)) {
return false;
}
// Step 8: Set promise.[[PromiseIsHandled]] to true.
MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
// Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
// « port2 »).
channel->Port2()->CloneAndDisentangle(aPortId);
return true;
}
// https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps
MOZ_CAN_RUN_SCRIPT already_AddRefed<ReadableStream>
ReadableStream::ReceiveTransferImpl(JSContext* aCx, nsIGlobalObject* aGlobal,
MessagePort& aPort) {
// Step 1: Let deserializedRecord be
// ! StructuredDeserializeWithTransfer(dataHolder.[[port]], the current
// Realm).
// Step 2: Let port be deserializedRecord.[[Deserialized]].
// Step 3: Perform ! SetUpCrossRealmTransformReadable(value, port).
RefPtr<ReadableStream> readable =
new ReadableStream(aGlobal, HoldDropJSObjectsCaller::Implicit);
ErrorResult rv;
SetUpCrossRealmTransformReadable(readable, &aPort, rv);
if (rv.MaybeSetPendingException(aCx)) {
return nullptr;
}
return readable.forget();
}
bool ReadableStream::ReceiveTransfer(
JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort,
JS::MutableHandle<JSObject*> aReturnObject) {
RefPtr<ReadableStream> readable =
ReadableStream::ReceiveTransferImpl(aCx, aGlobal, aPort);
if (!readable) {
return false;
}
JS::Rooted<JS::Value> value(aCx);
if (!GetOrCreateDOMReflector(aCx, readable, &value)) {
JS_ClearPendingException(aCx);
return false;
}
aReturnObject.set(&value.toObject());
return true;
}
// https://streams.spec.whatwg.org/#ref-for-transfer-steps①
bool WritableStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId) {
// Step 1: If ! IsWritableStreamLocked(value) is true, throw a
// "DataCloneError" DOMException.
// (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
// check here as the state might have changed in case this WritableStream is
// created by a TransferStream and being transferred together with the
// parent.)
if (IsWritableStreamLocked(this)) {
return false;
}
// Step 2: Let port1 be a new MessagePort in the current Realm.
// Step 3: Let port2 be a new MessagePort in the current Realm.
// Step 4: Entangle port1 and port2.
// (The MessageChannel constructor does exactly that.)
// https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messagechannel
ErrorResult rv;
RefPtr<dom::MessageChannel> channel =
dom::MessageChannel::Constructor(mGlobal, rv);
if (rv.MaybeSetPendingException(aCx)) {
return false;
}
// Step 5: Let readable be a new ReadableStream in the current Realm.
RefPtr<ReadableStream> readable = new ReadableStream(
mGlobal, ReadableStream::HoldDropJSObjectsCaller::Implicit);
// Step 6: Perform ! SetUpCrossRealmTransformReadable(readable, port1).
// MOZ_KnownLive because Port1 never changes before CC
SetUpCrossRealmTransformReadable(readable, MOZ_KnownLive(channel->Port1()),
rv);
if (rv.MaybeSetPendingException(aCx)) {
return false;
}
// Step 7: Let promise be ! ReadableStreamPipeTo(readable, value, false,
// false, false).
RefPtr<Promise> promise =
ReadableStreamPipeTo(readable, this, false, false, false, nullptr, rv);
if (rv.Failed()) {
return false;
}
// Step 8: Set promise.[[PromiseIsHandled]] to true.
MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
// Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
// « port2 »).
channel->Port2()->CloneAndDisentangle(aPortId);
return true;
}
// https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps①
MOZ_CAN_RUN_SCRIPT already_AddRefed<WritableStream>
WritableStream::ReceiveTransferImpl(JSContext* aCx, nsIGlobalObject* aGlobal,
MessagePort& aPort) {
// Step 1: Let deserializedRecord be !
// StructuredDeserializeWithTransfer(dataHolder.[[port]], the current Realm).
// Step 2: Let port be a deserializedRecord.[[Deserialized]].
// Step 3: Perform ! SetUpCrossRealmTransformWritable(value, port).
RefPtr<WritableStream> writable = new WritableStream(
aGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit);
ErrorResult rv;
SetUpCrossRealmTransformWritable(writable, &aPort, rv);
if (rv.MaybeSetPendingException(aCx)) {
return nullptr;
}
return writable.forget();
}
// https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps①
bool WritableStream::ReceiveTransfer(
JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort,
JS::MutableHandle<JSObject*> aReturnObject) {
RefPtr<WritableStream> writable =
WritableStream::ReceiveTransferImpl(aCx, aGlobal, aPort);
if (!writable) {
return false;
}
JS::Rooted<JS::Value> value(aCx);
if (!GetOrCreateDOMReflector(aCx, writable, &value)) {
JS_ClearPendingException(aCx);
return false;
}
aReturnObject.set(&value.toObject());
return true;
}
// https://streams.spec.whatwg.org/#ref-for-transfer-steps②
bool TransformStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId1,
UniqueMessagePortId& aPortId2) {
// Step 1: Let readable be value.[[readable]].
// Step 2: Let writable be value.[[writable]].
// Step 3: If ! IsReadableStreamLocked(readable) is true, throw a
// "DataCloneError" DOMException.
// Step 4: If ! IsWritableStreamLocked(writable) is true, throw a
// "DataCloneError" DOMException.
// (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
// check here as the state might have changed by
// Readable/WritableStream::Transfer in case the stream members of this
// TransformStream are being transferred together.)
if (IsReadableStreamLocked(mReadable) || IsWritableStreamLocked(mWritable)) {
return false;
}
// Step 5: Set dataHolder.[[readable]] to !
// StructuredSerializeWithTransfer(readable, « readable »).
// TODO: Mark mReadable as MOZ_KNOWN_LIVE again (bug 1769854)
if (!MOZ_KnownLive(mReadable)->Transfer(aCx, aPortId1)) {
return false;
}
// Step 6: Set dataHolder.[[writable]] to !
// StructuredSerializeWithTransfer(writable, « writable »).
// TODO: Mark mReadable as MOZ_KNOWN_LIVE again (bug 1769854)
return MOZ_KnownLive(mWritable)->Transfer(aCx, aPortId2);
}
// https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps②
bool TransformStream::ReceiveTransfer(
JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort1,
MessagePort& aPort2, JS::MutableHandle<JSObject*> aReturnObject) {
// Step 1: Let readableRecord be !
// StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current
// Realm).
RefPtr<ReadableStream> readable =
ReadableStream::ReceiveTransferImpl(aCx, aGlobal, aPort1);
if (!readable) {
return false;
}
// Step 2: Let writableRecord be !
// StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current
// Realm).
RefPtr<WritableStream> writable =
WritableStream::ReceiveTransferImpl(aCx, aGlobal, aPort2);
if (!writable) {
return false;
}
// Step 3: Set value.[[readable]] to readableRecord.[[Deserialized]].
// Step 4: Set value.[[writable]] to writableRecord.[[Deserialized]].
// Step 5: Set value.[[backpressure]], value.[[backpressureChangePromise]],
// and value.[[controller]] to undefined.
RefPtr<TransformStream> stream =
new TransformStream(aGlobal, readable, writable);
JS::Rooted<JS::Value> value(aCx);
if (!GetOrCreateDOMReflector(aCx, stream, &value)) {
JS_ClearPendingException(aCx);
return false;
}
aReturnObject.set(&value.toObject());
return true;
}
} // namespace mozilla::dom