Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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);
}
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)
// 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)
// 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
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;
}
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)
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 port’s 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 port’s messageerror event with the following
// steps:
auto errorListener =
MakeRefPtr<SetUpTransformWritableMessageErrorEventListener>(controller,
aPort);
aPort->AddEventListener(u"messageerror"_ns, errorListener, false);
// Step 6: Enable port’s 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)
// 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)
// 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
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, ReadableStreamController& 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,
ReadableStreamController& 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)
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 port’s 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 port’s messageerror event with the following
// steps:
auto errorListener =
MakeRefPtr<SetUpTransformReadableMessageErrorEventListener>(controller,
aPort);
aPort->AddEventListener(u"messageerror"_ns, errorListener, false);
// Step 5: Enable port’s 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);
}
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.)
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;
}
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;
}
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.)
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;
}
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();
}
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;
}
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);
}
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