1068 lines
38 KiB
C++
1068 lines
38 KiB
C++
/* -*- 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 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)
|
||
|
||
// 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 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);
|
||
}
|
||
|
||
// 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
|