diff options
Diffstat (limited to '')
-rw-r--r-- | dom/webtransport/api/WebTransport.cpp | 942 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransport.h | 172 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportBidirectionalStream.cpp | 66 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportBidirectionalStream.h | 64 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportDatagramDuplexStream.cpp | 363 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportDatagramDuplexStream.h | 166 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportError.cpp | 38 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportError.h | 40 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportReceiveStream.cpp | 73 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportReceiveStream.h | 50 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportSendStream.cpp | 83 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportSendStream.h | 50 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportStreams.cpp | 197 | ||||
-rw-r--r-- | dom/webtransport/api/WebTransportStreams.h | 51 | ||||
-rw-r--r-- | dom/webtransport/api/moz.build | 29 |
15 files changed, 2384 insertions, 0 deletions
diff --git a/dom/webtransport/api/WebTransport.cpp b/dom/webtransport/api/WebTransport.cpp new file mode 100644 index 0000000000..acd1d5a5e3 --- /dev/null +++ b/dom/webtransport/api/WebTransport.cpp @@ -0,0 +1,942 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "WebTransport.h" + +#include "WebTransportBidirectionalStream.h" +#include "mozilla/RefPtr.h" +#include "nsUTF8Utils.h" +#include "nsIURL.h" +#include "nsIWebTransportStream.h" +#include "mozilla/Assertions.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PWebTransport.h" +#include "mozilla/dom/ReadableStream.h" +#include "mozilla/dom/ReadableStreamDefaultController.h" +#include "mozilla/dom/RemoteWorkerChild.h" +#include "mozilla/dom/WebTransportDatagramDuplexStream.h" +#include "mozilla/dom/WebTransportError.h" +#include "mozilla/dom/WebTransportLog.h" +#include "mozilla/dom/WindowGlobalChild.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/dom/WritableStream.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/PBackgroundChild.h" + +using namespace mozilla::ipc; + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebTransport) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebTransport) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingUnidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingBidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingUnidirectionalAlgorithm) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingBidirectionalAlgorithm) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDatagrams) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReady) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClosed) + for (const auto& hashEntry : tmp->mSendStreams.Values()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSendStreams entry item"); + cb.NoteXPCOMChild(hashEntry); + } + for (const auto& hashEntry : tmp->mReceiveStreams.Values()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mReceiveStreams entry item"); + cb.NoteXPCOMChild(hashEntry); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTransport) + tmp->mSendStreams.Clear(); + tmp->mReceiveStreams.Clear(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingUnidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingBidirectionalStreams) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingUnidirectionalAlgorithm) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingBidirectionalAlgorithm) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDatagrams) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReady) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mClosed) + if (tmp->mChild) { + tmp->mChild->Shutdown(false); + tmp->mChild = nullptr; + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTransport) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTransport) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransport) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +WebTransport::WebTransport(nsIGlobalObject* aGlobal) + : mGlobal(aGlobal), + mState(WebTransportState::CONNECTING), + mReliability(WebTransportReliabilityMode::Pending) { + LOG(("Creating WebTransport %p", this)); +} + +WebTransport::~WebTransport() { + // Should be empty by this point, because we should always have run cleanup: + // https://w3c.github.io/webtransport/#webtransport-procedures + LOG(("~WebTransport() for %p", this)); + MOZ_ASSERT(mSendStreams.IsEmpty()); + MOZ_ASSERT(mReceiveStreams.IsEmpty()); + // If this WebTransport was destroyed without being closed properly, make + // sure to clean up the channel. + // Since child has a raw ptr to us, we MUST call Shutdown() before we're + // destroyed + if (mChild) { + mChild->Shutdown(true); + } +} + +// From parent +void WebTransport::NewBidirectionalStream( + uint64_t aStreamId, const RefPtr<DataPipeReceiver>& aIncoming, + const RefPtr<DataPipeSender>& aOutgoing) { + LOG_VERBOSE(("NewBidirectionalStream()")); + // Create a Bidirectional stream and push it into the + // IncomingBidirectionalStreams stream. Must be added to the ReceiveStreams + // and SendStreams arrays + + UniquePtr<BidirectionalPair> streams( + new BidirectionalPair(aIncoming, aOutgoing)); + auto tuple = std::tuple<uint64_t, UniquePtr<BidirectionalPair>>( + aStreamId, std::move(streams)); + mBidirectionalStreams.AppendElement(std::move(tuple)); + // We need to delete them all! + + // Notify something to wake up readers of IncomingReceiveStreams + // The callback is always set/used from the same thread (MainThread or a + // Worker thread). + if (mIncomingBidirectionalAlgorithm) { + RefPtr<WebTransportIncomingStreamsAlgorithms> callback = + mIncomingBidirectionalAlgorithm; + LOG(("NotifyIncomingStream")); + callback->NotifyIncomingStream(); + } +} + +void WebTransport::NewUnidirectionalStream( + uint64_t aStreamId, const RefPtr<mozilla::ipc::DataPipeReceiver>& aStream) { + LOG_VERBOSE(("NewUnidirectionalStream()")); + // Create a Unidirectional stream and push it into the + // IncomingUnidirectionalStreams stream. Must be added to the ReceiveStreams + // array + + mUnidirectionalStreams.AppendElement( + std::tuple<uint64_t, RefPtr<mozilla::ipc::DataPipeReceiver>>(aStreamId, + aStream)); + // Notify something to wake up readers of IncomingReceiveStreams + // The callback is always set/used from the same thread (MainThread or a + // Worker thread). + if (mIncomingUnidirectionalAlgorithm) { + RefPtr<WebTransportIncomingStreamsAlgorithms> callback = + mIncomingUnidirectionalAlgorithm; + LOG(("NotifyIncomingStream")); + callback->NotifyIncomingStream(); + } +} + +void WebTransport::NewDatagramReceived(nsTArray<uint8_t>&& aData, + const mozilla::TimeStamp& aTimeStamp) { + mDatagrams->NewDatagramReceived(std::move(aData), aTimeStamp); +} + +// WebIDL Boilerplate + +nsIGlobalObject* WebTransport::GetParentObject() const { return mGlobal; } + +JSObject* WebTransport::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return WebTransport_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +/* static */ +already_AddRefed<WebTransport> WebTransport::Constructor( + const GlobalObject& aGlobal, const nsAString& aURL, + const WebTransportOptions& aOptions, ErrorResult& aError) { + LOG(("Creating WebTransport for %s", NS_ConvertUTF16toUTF8(aURL).get())); + // https://w3c.github.io/webtransport/#webtransport-constructor + + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<WebTransport> result = new WebTransport(global); + result->Init(aGlobal, aURL, aOptions, aError); + if (aError.Failed()) { + return nullptr; + } + + // Don't let this document go into BFCache + result->NotifyToWindow(true); + + // Step 25 Return transport + return result.forget(); +} + +void WebTransport::Init(const GlobalObject& aGlobal, const nsAString& aURL, + const WebTransportOptions& aOptions, + ErrorResult& aError) { + // https://w3c.github.io/webtransport/#webtransport-constructor + // Initiate connection with parent + using mozilla::ipc::BackgroundChild; + using mozilla::ipc::Endpoint; + using mozilla::ipc::PBackgroundChild; + + // https://w3c.github.io/webtransport/#webtransport-constructor + // Steps 1-4: Parse string for validity and Throw a SyntaxError if it isn't + // Let parsedURL be the URL record resulting from parsing url. + // If parsedURL is a failure, throw a SyntaxError exception. + // If parsedURL scheme is not https, throw a SyntaxError exception. + // If parsedURL fragment is not null, throw a SyntaxError exception. + if (!ParseURL(aURL)) { + aError.ThrowSyntaxError("Invalid WebTransport URL"); + return; + } + // Step 5: Let allowPooling be options's allowPooling if it exists, and false + // otherwise. + // Step 6: Let dedicated be the negation of allowPooling. + bool dedicated = !aOptions.mAllowPooling; + // Step 7: Let serverCertificateHashes be options's serverCertificateHashes if + // it exists, and null otherwise. + // Step 8: If dedicated is false and serverCertificateHashes is non-null, + // then throw a TypeError. + if (aOptions.mServerCertificateHashes.WasPassed()) { + // XXX bug 1806693 + aError.ThrowNotSupportedError("No support for serverCertificateHashes yet"); + // XXX if dedicated is false and serverCertificateHashes is non-null, then + // throw a TypeError. Also should enforce in parent + return; + } + // Step 9: Let requireUnreliable be options's requireUnreliable. + bool requireUnreliable = aOptions.mRequireUnreliable; + // Step 10: Let congestionControl be options's congestionControl. + // Step 11: If congestionControl is not "default", and the user agent + // does not support any congestion control algorithms that optimize for + // congestionControl, as allowed by [RFC9002] section 7, then set + // congestionControl to "default". + WebTransportCongestionControl congestionControl = + WebTransportCongestionControl::Default; // aOptions.mCongestionControl; + // Set this to 'default' until we add congestion control setting + + // Setup up WebTransportDatagramDuplexStream + // Step 12: Let incomingDatagrams be a new ReadableStream. + // Step 13: Let outgoingDatagrams be a new WritableStream. + // Step 14: Let datagrams be the result of creating a + // WebTransportDatagramDuplexStream, its readable set to + // incomingDatagrams and its writable set to outgoingDatagrams. + mDatagrams = new WebTransportDatagramDuplexStream(mGlobal, this); + mDatagrams->Init(aError); + if (aError.Failed()) { + return; + } + + // XXX TODO + + // Step 15 Let transport be a newly constructed WebTransport object, with: + // SendStreams: empty ordered set + // ReceiveStreams: empty ordered set + // Ready: new promise + mReady = Promise::CreateInfallible(mGlobal); + + // Closed: new promise + mClosed = Promise::CreateInfallible(mGlobal); + + PBackgroundChild* backgroundChild = + BackgroundChild::GetOrCreateForCurrentThread(); + if (NS_WARN_IF(!backgroundChild)) { + return; + } + + nsCOMPtr<nsIPrincipal> principal = mGlobal->PrincipalOrNull(); + mozilla::Maybe<IPCClientInfo> ipcClientInfo; + + if (mGlobal->GetClientInfo().isSome()) { + ipcClientInfo = mozilla::Some(mGlobal->GetClientInfo().ref().ToIPC()); + } + // Create a new IPC connection + Endpoint<PWebTransportParent> parentEndpoint; + Endpoint<PWebTransportChild> childEndpoint; + MOZ_ALWAYS_SUCCEEDS( + PWebTransport::CreateEndpoints(&parentEndpoint, &childEndpoint)); + + RefPtr<WebTransportChild> child = new WebTransportChild(this); + if (NS_IsMainThread()) { + if (!childEndpoint.Bind(child)) { + return; + } + } else { + if (!childEndpoint.Bind(child, + mGlobal->EventTargetFor(TaskCategory::Other))) { + return; + } + } + + mState = WebTransportState::CONNECTING; + + JSContext* cx = aGlobal.Context(); + // Set up Datagram streams + // Step 16: Let pullDatagramsAlgorithm be an action that runs pullDatagrams + // with transport. + // Step 17: Let writeDatagramsAlgorithm be an action that runs writeDatagrams + // with transport. + // Step 18: Set up incomingDatagrams with pullAlgorithm set to + // pullDatagramsAlgorithm, and highWaterMark set to 0. + // Step 19: Set up outgoingDatagrams with writeAlgorithm set to + // writeDatagramsAlgorithm. + + // XXX TODO + + // Step 20: Let pullBidirectionalStreamAlgorithm be an action that runs + // pullBidirectionalStream with transport. + // Step 21: Set up transport.[[IncomingBidirectionalStreams]] with + // pullAlgorithm set to pullBidirectionalStreamAlgorithm, and highWaterMark + // set to 0. + Optional<JS::Handle<JSObject*>> underlying; + // Suppress warnings about risk of mGlobal getting nulled during script. + // We set the global from the aGlobalObject parameter of the constructor, so + // it must still be set here. + const nsCOMPtr<nsIGlobalObject> global(mGlobal); + + mIncomingBidirectionalAlgorithm = new WebTransportIncomingStreamsAlgorithms( + WebTransportIncomingStreamsAlgorithms::StreamType::Bidirectional, this); + + RefPtr<WebTransportIncomingStreamsAlgorithms> algorithm = + mIncomingBidirectionalAlgorithm; + mIncomingBidirectionalStreams = ReadableStream::CreateNative( + cx, global, *algorithm, Some(0.0), nullptr, aError); + if (aError.Failed()) { + return; + } + // Step 22: Let pullUnidirectionalStreamAlgorithm be an action that runs + // pullUnidirectionalStream with transport. + // Step 23: Set up transport.[[IncomingUnidirectionalStreams]] with + // pullAlgorithm set to pullUnidirectionalStreamAlgorithm, and highWaterMark + // set to 0. + + mIncomingUnidirectionalAlgorithm = new WebTransportIncomingStreamsAlgorithms( + WebTransportIncomingStreamsAlgorithms::StreamType::Unidirectional, this); + + algorithm = mIncomingUnidirectionalAlgorithm; + mIncomingUnidirectionalStreams = ReadableStream::CreateNative( + cx, global, *algorithm, Some(0.0), nullptr, aError); + if (aError.Failed()) { + return; + } + + // Step 24: Initialize WebTransport over HTTP with transport, parsedURL, + // dedicated, requireUnreliable, and congestionControl. + LOG(("Connecting WebTransport to parent for %s", + NS_ConvertUTF16toUTF8(aURL).get())); + + // https://w3c.github.io/webtransport/#webtransport-constructor Spec 5.2 + mChild = child; + backgroundChild + ->SendCreateWebTransportParent(aURL, principal, ipcClientInfo, dedicated, + requireUnreliable, + (uint32_t)congestionControl, + // XXX serverCertHashes, + std::move(parentEndpoint)) + ->Then(GetCurrentSerialEventTarget(), __func__, + [self = RefPtr{this}]( + PBackgroundChild::CreateWebTransportParentPromise:: + ResolveOrRejectValue&& aResult) { + // aResult is a std::tuple<nsresult, uint8_t> + // TODO: is there a better/more-spec-compliant error in the + // reject case? Which begs the question, why would we get a + // reject? + nsresult rv = aResult.IsReject() + ? NS_ERROR_FAILURE + : std::get<0>(aResult.ResolveValue()); + LOG(("isreject: %d nsresult 0x%x", aResult.IsReject(), + (uint32_t)rv)); + if (NS_FAILED(rv)) { + self->RejectWaitingConnection(rv); + } else { + // This will process anything waiting for the connection to + // complete; + + self->ResolveWaitingConnection( + static_cast<WebTransportReliabilityMode>( + std::get<1>(aResult.ResolveValue()))); + } + }); +} + +void WebTransport::ResolveWaitingConnection( + WebTransportReliabilityMode aReliability) { + LOG(("Resolved Connection %p, reliability = %u", this, + (unsigned)aReliability)); + // https://w3c.github.io/webtransport/#webtransport-constructor + // Step 17 of initialize WebTransport over HTTP + // Step 17.1 If transport.[[State]] is not "connecting": + if (mState != WebTransportState::CONNECTING) { + // Step 17.1.1: In parallel, terminate session. + // Step 17.1.2: abort these steps + // Cleanup should have been called, which means Ready has been rejected + return; + } + + // Step 17.2: Set transport.[[State]] to "connected". + mState = WebTransportState::CONNECTED; + // Step 17.3: Set transport.[[Session]] to session. + // Step 17.4: Set transport’s [[Reliability]] to "supports-unreliable". + mReliability = aReliability; + + mChild->SendGetMaxDatagramSize()->Then( + GetCurrentSerialEventTarget(), __func__, + [self = RefPtr{this}](uint64_t&& aMaxDatagramSize) { + MOZ_ASSERT(self->mDatagrams); + self->mDatagrams->SetMaxDatagramSize(aMaxDatagramSize); + LOG(("max datagram size for the session is %" PRIu64, + aMaxDatagramSize)); + }, + [](const mozilla::ipc::ResponseRejectReason& aReason) { + LOG(("WebTransport fetching maxDatagramSize failed")); + }); + + // Step 17.5: Resolve transport.[[Ready]] with undefined. + mReady->MaybeResolveWithUndefined(); + + // We can now release any queued datagrams + mDatagrams->SetChild(mChild); +} + +void WebTransport::RejectWaitingConnection(nsresult aRv) { + LOG(("Rejected connection %p %x", this, (uint32_t)aRv)); + // https://w3c.github.io/webtransport/#initialize-webtransport-over-http + + // Step 10: If connection is failure, then abort the remaining steps and + // queue a network task with transport to run these steps: + // Step 10.1: If transport.[[State]] is "closed" or "failed", then abort + // these steps. + + // Step 14: If the previous step fails, abort the remaining steps and + // queue a network task with transport to run these steps: + // Step 14.1: If transport.[[State]] is "closed" or "failed", then abort + // these steps. + if (mState == WebTransportState::CLOSED || + mState == WebTransportState::FAILED) { + mChild->Shutdown(true); + mChild = nullptr; + // Cleanup should have been called, which means Ready has been + // rejected and pulls resolved + return; + } + + // Step 14.2: Let error be the result of creating a WebTransportError with + // "session". + RefPtr<WebTransportError> error = new WebTransportError( + "WebTransport connection rejected"_ns, WebTransportErrorSource::Session); + // Step 14.3: Cleanup transport with error. + Cleanup(error, nullptr, IgnoreErrors()); + + mChild->Shutdown(true); + mChild = nullptr; +} + +bool WebTransport::ParseURL(const nsAString& aURL) const { + NS_ENSURE_TRUE(!aURL.IsEmpty(), false); + + // 5.4 = https://w3c.github.io/webtransport/#webtransport-constructor + // 5.4 #1 and #2 + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL); + NS_ENSURE_SUCCESS(rv, false); + + // 5.4 #3 + if (!uri->SchemeIs("https")) { + return false; + } + + // 5.4 #4 no fragments + bool hasRef; + rv = uri->GetHasRef(&hasRef); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef, false); + + return true; +} + +already_AddRefed<Promise> WebTransport::GetStats(ErrorResult& aError) { + aError.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; +} + +WebTransportReliabilityMode WebTransport::Reliability() { return mReliability; } + +WebTransportCongestionControl WebTransport::CongestionControl() { + // XXX not implemented + return WebTransportCongestionControl::Default; +} + +void WebTransport::RemoteClosed(bool aCleanly, const uint32_t& aCode, + const nsACString& aReason) { + LOG(("Server closed: cleanly: %d, code %u, reason %s", aCleanly, aCode, + PromiseFlatCString(aReason).get())); + // Step 2 of https://w3c.github.io/webtransport/#web-transport-termination + // We calculate cleanly on the parent + // Step 2.1: If transport.[[State]] is "closed" or "failed", abort these + // steps. + if (mState == WebTransportState::CLOSED || + mState == WebTransportState::FAILED) { + return; + } + // Step 2.2: Let error be the result of creating a WebTransportError with + // "session". + RefPtr<WebTransportError> error = new WebTransportError( + "remote WebTransport close"_ns, WebTransportErrorSource::Session); + // Step 2.3: If cleanly is false, then cleanup transport with error, and + // abort these steps. + ErrorResult errorresult; + if (!aCleanly) { + Cleanup(error, nullptr, errorresult); + return; + } + // Step 2.4: Let closeInfo be a new WebTransportCloseInfo. + // Step 2.5: If code is given, set closeInfo’s closeCode to code. + // Step 2.6: If reasonBytes is given, set closeInfo’s reason to reasonBytes, + // UTF-8 decoded. + WebTransportCloseInfo closeinfo; + closeinfo.mCloseCode = aCode; + closeinfo.mReason = aReason; + + // Step 2.7: Cleanup transport with error and closeInfo. + Cleanup(error, &closeinfo, errorresult); +} + +template <typename Stream> +void WebTransport::PropagateError(Stream* aStream, WebTransportError* aError) { + ErrorResult rv; + AutoJSAPI jsapi; + if (!jsapi.Init(mGlobal)) { + rv.ThrowUnknownError("Internal error"); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> errorValue(cx); + bool ok = ToJSValue(cx, aError, &errorValue); + if (!ok) { + rv.ThrowUnknownError("Internal error"); + return; + } + + aStream->ErrorNative(cx, errorValue, IgnoreErrors()); +} + +void WebTransport::OnStreamResetOrStopSending( + uint64_t aStreamId, const StreamResetOrStopSendingError& aError) { + LOG(("WebTransport::OnStreamResetOrStopSending %p id=%" PRIx64, this, + aStreamId)); + if (aError.type() == StreamResetOrStopSendingError::TStopSendingError) { + RefPtr<WebTransportSendStream> stream = mSendStreams.Get(aStreamId); + if (!stream) { + return; + } + uint8_t errorCode = net::GetWebTransportErrorFromNSResult( + aError.get_StopSendingError().error()); + RefPtr<WebTransportError> error = new WebTransportError( + "WebTransportStream StopSending"_ns, WebTransportErrorSource::Stream, + Nullable<uint8_t>(errorCode)); + PropagateError(stream.get(), error); + } else if (aError.type() == StreamResetOrStopSendingError::TResetError) { + RefPtr<WebTransportReceiveStream> stream = mReceiveStreams.Get(aStreamId); + LOG(("WebTransport::OnStreamResetOrStopSending reset %p stream=%p", this, + stream.get())); + if (!stream) { + return; + } + uint8_t errorCode = + net::GetWebTransportErrorFromNSResult(aError.get_ResetError().error()); + RefPtr<WebTransportError> error = new WebTransportError( + "WebTransportStream Reset"_ns, WebTransportErrorSource::Stream, + Nullable<uint8_t>(errorCode)); + PropagateError(stream.get(), error); + } +} + +void WebTransport::Close(const WebTransportCloseInfo& aOptions, + ErrorResult& aRv) { + LOG(("Close() called")); + // https://w3c.github.io/webtransport/#dom-webtransport-close + // Step 1 and Step 2: If transport.[[State]] is "closed" or "failed", then + // abort these steps. + if (mState == WebTransportState::CLOSED || + mState == WebTransportState::FAILED) { + return; + } + // Step 3: If transport.[[State]] is "connecting": + if (mState == WebTransportState::CONNECTING) { + // Step 3.1: Let error be the result of creating a WebTransportError with + // "session". + RefPtr<WebTransportError> error = new WebTransportError( + "close() called on WebTransport while connecting"_ns, + WebTransportErrorSource::Session); + // Step 3.2: Cleanup transport with error. + Cleanup(error, nullptr, aRv); + // Step 3.3: Abort these steps. + mChild->Shutdown(true); + mChild = nullptr; + return; + } + LOG(("Sending Close")); + MOZ_ASSERT(mChild); + // Step 4: Let session be transport.[[Session]]. + // Step 5: Let code be closeInfo.closeCode. + // Step 6: "Let reasonString be the maximal code unit prefix of + // closeInfo.reason where the length of the UTF-8 encoded prefix + // doesn’t exceed 1024." + // Take the maximal "code unit prefix" of mReason and limit to 1024 bytes + // Step 7: Let reason be reasonString, UTF-8 encoded. + // Step 8: In parallel, terminate session with code and reason. + if (aOptions.mReason.Length() > 1024u) { + // We want to start looking for the previous code point at one past the + // limit, since if a code point ends exactly at the specified length, the + // next byte will be the start of a new code point. Note + // RewindToPriorUTF8Codepoint doesn't reduce the index if it points to the + // start of a code point. We know reason[1024] is accessible since + // Length() > 1024 + mChild->SendClose( + aOptions.mCloseCode, + Substring(aOptions.mReason, 0, + RewindToPriorUTF8Codepoint(aOptions.mReason.get(), 1024u))); + } else { + mChild->SendClose(aOptions.mCloseCode, aOptions.mReason); + LOG(("Close sent")); + } + + // Step 9: Cleanup transport with AbortError and closeInfo. (sets mState to + // Closed) + RefPtr<WebTransportError> error = + new WebTransportError("close()"_ns, WebTransportErrorSource::Session, + DOMException_Binding::ABORT_ERR); + Cleanup(error, &aOptions, aRv); + LOG(("Cleanup done")); + + // The other side will call `Close()` for us now, make sure we don't call it + // in our destructor. + mChild->Shutdown(false); + mChild = nullptr; + LOG(("Close done")); +} + +already_AddRefed<WebTransportDatagramDuplexStream> WebTransport::GetDatagrams( + ErrorResult& aError) { + return do_AddRef(mDatagrams); +} + +already_AddRefed<Promise> WebTransport::CreateBidirectionalStream( + const WebTransportSendStreamOptions& aOptions, ErrorResult& aRv) { + LOG(("CreateBidirectionalStream() called")); + // https://w3c.github.io/webtransport/#dom-webtransport-createbidirectionalstream + RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject()); + + // Step 2: If transport.[[State]] is "closed" or "failed", return a new + // rejected promise with an InvalidStateError. + if (mState == WebTransportState::CLOSED || + mState == WebTransportState::FAILED || !mChild) { + aRv.ThrowInvalidStateError("WebTransport closed or failed"); + return nullptr; + } + + // Step 3: Let sendOrder be options's sendOrder. + Maybe<int64_t> sendOrder; + if (!aOptions.mSendOrder.IsNull()) { + sendOrder = Some(aOptions.mSendOrder.Value()); + } + // Step 4: Let p be a new promise. + // Step 5: Run the following steps in parallel, but abort them whenever + // transport’s [[State]] becomes "closed" or "failed", and instead queue + // a network task with transport to reject p with an InvalidStateError. + + // Ask the parent to create the stream and send us the DataPipeSender/Receiver + // pair + mChild->SendCreateBidirectionalStream( + sendOrder, + [self = RefPtr{this}, promise]( + BidirectionalStreamResponse&& aPipes) MOZ_CAN_RUN_SCRIPT_BOUNDARY { + LOG(("CreateBidirectionalStream response")); + if (BidirectionalStreamResponse::Tnsresult == aPipes.type()) { + promise->MaybeReject(aPipes.get_nsresult()); + return; + } + // Step 5.2.1: If transport.[[State]] is "closed" or "failed", + // reject p with an InvalidStateError and abort these steps. + if (BidirectionalStreamResponse::Tnsresult == aPipes.type()) { + promise->MaybeReject(aPipes.get_nsresult()); + return; + } + if (self->mState == WebTransportState::CLOSED || + self->mState == WebTransportState::FAILED) { + promise->MaybeRejectWithInvalidStateError( + "Transport close/errored before CreateBidirectional finished"); + return; + } + uint64_t id = aPipes.get_BidirectionalStream().streamId(); + LOG(("Create WebTransportBidirectionalStream id=%" PRIx64, id)); + ErrorResult error; + RefPtr<WebTransportBidirectionalStream> newStream = + WebTransportBidirectionalStream::Create( + self, self->mGlobal, id, + aPipes.get_BidirectionalStream().inStream(), + aPipes.get_BidirectionalStream().outStream(), error); + LOG(("Returning a bidirectionalStream")); + promise->MaybeResolve(newStream); + }, + [self = RefPtr{this}, promise](mozilla::ipc::ResponseRejectReason) { + LOG(("CreateBidirectionalStream reject")); + promise->MaybeRejectWithInvalidStateError( + "Transport close/errored before CreateBidirectional started"); + }); + + // Step 6: return p + return promise.forget(); +} + +already_AddRefed<ReadableStream> WebTransport::IncomingBidirectionalStreams() { + return do_AddRef(mIncomingBidirectionalStreams); +} + +already_AddRefed<Promise> WebTransport::CreateUnidirectionalStream( + const WebTransportSendStreamOptions& aOptions, ErrorResult& aRv) { + LOG(("CreateUnidirectionalStream() called")); + // https://w3c.github.io/webtransport/#dom-webtransport-createunidirectionalstream + // Step 2: If transport.[[State]] is "closed" or "failed", return a new + // rejected promise with an InvalidStateError. + if (mState == WebTransportState::CLOSED || + mState == WebTransportState::FAILED || !mChild) { + aRv.ThrowInvalidStateError("WebTransport closed or failed"); + return nullptr; + } + + // Step 3: Let sendOrder be options's sendOrder. + Maybe<int64_t> sendOrder; + if (!aOptions.mSendOrder.IsNull()) { + sendOrder = Some(aOptions.mSendOrder.Value()); + } + // Step 4: Let p be a new promise. + RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject()); + + // Step 5: Run the following steps in parallel, but abort them whenever + // transport’s [[State]] becomes "closed" or "failed", and instead queue + // a network task with transport to reject p with an InvalidStateError. + + // Ask the parent to create the stream and send us the DataPipeSender + mChild->SendCreateUnidirectionalStream( + sendOrder, + [self = RefPtr{this}, promise](UnidirectionalStreamResponse&& aResponse) + MOZ_CAN_RUN_SCRIPT_BOUNDARY { + LOG(("CreateUnidirectionalStream response")); + if (UnidirectionalStreamResponse::Tnsresult == aResponse.type()) { + promise->MaybeReject(aResponse.get_nsresult()); + return; + } + // Step 5.1: Let internalStream be the result of creating an + // outgoing unidirectional stream with transport.[[Session]]. + // Step 5.2: Queue a network task with transport to run the + // following steps: + // Step 5.2.1 If transport.[[State]] is "closed" or "failed", + // reject p with an InvalidStateError and abort these steps. + if (self->mState == WebTransportState::CLOSED || + self->mState == WebTransportState::FAILED || + aResponse.type() != + UnidirectionalStreamResponse::TUnidirectionalStream) { + promise->MaybeRejectWithInvalidStateError( + "Transport close/errored during CreateUnidirectional"); + return; + } + + // Step 5.2.2.: Let stream be the result of creating a + // WebTransportSendStream with internalStream, transport, and + // sendOrder. + ErrorResult error; + uint64_t id = aResponse.get_UnidirectionalStream().streamId(); + LOG(("Create WebTransportSendStream id=%" PRIx64, id)); + RefPtr<WebTransportSendStream> writableStream = + WebTransportSendStream::Create( + self, self->mGlobal, id, + aResponse.get_UnidirectionalStream().outStream(), error); + if (!writableStream) { + promise->MaybeReject(std::move(error)); + return; + } + LOG(("Returning a writableStream")); + // Step 5.2.3: Resolve p with stream. + promise->MaybeResolve(writableStream); + }, + [self = RefPtr{this}, promise](mozilla::ipc::ResponseRejectReason) { + LOG(("CreateUnidirectionalStream reject")); + promise->MaybeRejectWithInvalidStateError( + "Transport close/errored during CreateUnidirectional"); + }); + + // Step 6: return p + return promise.forget(); +} + +already_AddRefed<ReadableStream> WebTransport::IncomingUnidirectionalStreams() { + return do_AddRef(mIncomingUnidirectionalStreams); +} + +// Can be invoked with "error", "error, error, and true/false", or "error and +// closeInfo", but reason and abruptly are never used, and it does use closeinfo +void WebTransport::Cleanup(WebTransportError* aError, + const WebTransportCloseInfo* aCloseInfo, + ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#webtransport-cleanup + // Step 1: Let sendStreams be a copy of transport.[[SendStreams]] + // Step 2: Let receiveStreams be a copy of transport.[[ReceiveStreams]] + // Step 3: Let ready be transport.[[Ready]] -> (mReady) + // Step 4: Let closed be transport.[[Closed]] -> (mClosed) + // Step 5: Let incomingBidirectionalStreams be + // transport.[[IncomingBidirectionalStreams]]. + // Step 6: Let incomingUnidirectionalStreams be + // transport.[[IncomingUnidirectionalStreams]]. + // Step 7: Set transport.[[SendStreams]] to an empty set. + // Step 8: Set transport.[[ReceiveStreams]] to an empty set. + LOG(("Cleanup started")); + nsTHashMap<uint64_t, RefPtr<WebTransportSendStream>> sendStreams; + sendStreams.SwapElements(mSendStreams); + nsTHashMap<uint64_t, RefPtr<WebTransportReceiveStream>> receiveStreams; + receiveStreams.SwapElements(mReceiveStreams); + + // Step 9: If closeInfo is given, then set transport.[[State]] to "closed". + // Otherwise, set transport.[[State]] to "failed". + mState = aCloseInfo ? WebTransportState::CLOSED : WebTransportState::FAILED; + + // Step 10: For each sendStream in sendStreams, error sendStream with error. + AutoJSAPI jsapi; + if (!jsapi.Init(mGlobal)) { + aRv.ThrowUnknownError("Internal error"); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> errorValue(cx); + bool ok = ToJSValue(cx, aError, &errorValue); + if (!ok) { + aRv.ThrowUnknownError("Internal error"); + return; + } + + for (const auto& stream : sendStreams.Values()) { + // This MOZ_KnownLive is redundant, see bug 1620312 + MOZ_KnownLive(stream)->ErrorNative(cx, errorValue, IgnoreErrors()); + } + // Step 11: For each receiveStream in receiveStreams, error receiveStream with + // error. + for (const auto& stream : receiveStreams.Values()) { + stream->ErrorNative(cx, errorValue, IgnoreErrors()); + } + // Step 12: + if (aCloseInfo) { + // 12.1: Resolve closed with closeInfo. + LOG(("Resolving mClosed with closeinfo")); + mClosed->MaybeResolve(*aCloseInfo); + // 12.2: Assert: ready is settled. + MOZ_ASSERT(mReady->State() != Promise::PromiseState::Pending); + // 12.3: Close incomingBidirectionalStreams + // This keeps the clang-plugin happy + RefPtr<ReadableStream> stream = mIncomingBidirectionalStreams; + stream->CloseNative(cx, IgnoreErrors()); + // 12.4: Close incomingUnidirectionalStreams + stream = mIncomingUnidirectionalStreams; + stream->CloseNative(cx, IgnoreErrors()); + } else { + // Step 13 + // 13.1: Reject closed with error + LOG(("Rejecting mClosed")); + mClosed->MaybeReject(errorValue); + // 13.2: Reject ready with error + mReady->MaybeReject(errorValue); + // 13.3: Error incomingBidirectionalStreams with error + mIncomingBidirectionalStreams->ErrorNative(cx, errorValue, IgnoreErrors()); + // 13.4: Error incomingUnidirectionalStreams with error + mIncomingUnidirectionalStreams->ErrorNative(cx, errorValue, IgnoreErrors()); + } + // Let go of the algorithms + mIncomingBidirectionalAlgorithm = nullptr; + mIncomingUnidirectionalAlgorithm = nullptr; + + // We no longer block BFCache + NotifyToWindow(false); +} + +void WebTransport::NotifyBFCacheOnMainThread(nsPIDOMWindowInner* aInner, + bool aCreated) { + AssertIsOnMainThread(); + if (!aInner) { + return; + } + if (aCreated) { + aInner->RemoveFromBFCacheSync(); + } + + uint32_t count = aInner->UpdateWebTransportCount(aCreated); + // It's okay for WindowGlobalChild to not exist, as it should mean it already + // is destroyed and can't enter bfcache anyway. + if (WindowGlobalChild* child = aInner->GetWindowGlobalChild()) { + if (aCreated && count == 1) { + // The first WebTransport is active. + child->BlockBFCacheFor(BFCacheStatus::ACTIVE_WEBTRANSPORT); + } else if (count == 0) { + child->UnblockBFCacheFor(BFCacheStatus::ACTIVE_WEBTRANSPORT); + } + } +} + +class BFCacheNotifyWTRunnable final : public WorkerProxyToMainThreadRunnable { + public: + explicit BFCacheNotifyWTRunnable(bool aCreated) : mCreated(aCreated) {} + + void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override { + MOZ_ASSERT(aWorkerPrivate); + AssertIsOnMainThread(); + if (aWorkerPrivate->IsDedicatedWorker()) { + WebTransport::NotifyBFCacheOnMainThread( + aWorkerPrivate->GetAncestorWindow(), mCreated); + return; + } + if (aWorkerPrivate->IsSharedWorker()) { + aWorkerPrivate->GetRemoteWorkerController()->NotifyWebTransport(mCreated); + return; + } + MOZ_ASSERT_UNREACHABLE("Unexpected worker type"); + } + + void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + } + + private: + bool mCreated; +}; + +void WebTransport::NotifyToWindow(bool aCreated) const { + if (NS_IsMainThread()) { + NotifyBFCacheOnMainThread(GetParentObject()->AsInnerWindow(), aCreated); + return; + } + + WorkerPrivate* wp = GetCurrentThreadWorkerPrivate(); + if (wp->IsDedicatedWorker() || wp->IsSharedWorker()) { + RefPtr<BFCacheNotifyWTRunnable> runnable = + new BFCacheNotifyWTRunnable(aCreated); + + runnable->Dispatch(wp); + } +}; + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransport.h b/dom/webtransport/api/WebTransport.h new file mode 100644 index 0000000000..a72e950931 --- /dev/null +++ b/dom/webtransport/api/WebTransport.h @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef DOM_WEBTRANSPORT_API_WEBTRANSPORT__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORT__H_ + +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsISupports.h" +#include "nsTHashMap.h" +#include "nsWrapperCache.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/WebTransportBinding.h" +#include "mozilla/dom/WebTransportChild.h" +#include "mozilla/dom/WebTransportSendStream.h" +#include "mozilla/dom/WebTransportReceiveStream.h" +#include "mozilla/dom/WebTransportStreams.h" +#include "mozilla/ipc/DataPipe.h" + +namespace mozilla::dom { + +class WebTransportError; +class WebTransportDatagramDuplexStream; +class WebTransportIncomingStreamsAlgorithms; +class ReadableStream; +class WritableStream; +using BidirectionalPair = std::pair<RefPtr<mozilla::ipc::DataPipeReceiver>, + RefPtr<mozilla::ipc::DataPipeSender>>; + +struct DatagramEntry { + DatagramEntry(nsTArray<uint8_t>&& aData, const mozilla::TimeStamp& aTimeStamp) + : mBuffer(std::move(aData)), mTimeStamp(aTimeStamp) {} + DatagramEntry(Span<uint8_t>& aData, const mozilla::TimeStamp& aTimeStamp) + : mBuffer(aData), mTimeStamp(aTimeStamp) {} + + nsTArray<uint8_t> mBuffer; + mozilla::TimeStamp mTimeStamp; +}; + +class WebTransport final : public nsISupports, public nsWrapperCache { + friend class WebTransportIncomingStreamsAlgorithms; + // For mSendStreams/mReceiveStreams + friend class WebTransportSendStream; + friend class WebTransportReceiveStream; + + public: + explicit WebTransport(nsIGlobalObject* aGlobal); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebTransport) + + enum class WebTransportState { CONNECTING, CONNECTED, CLOSED, FAILED }; + + static void NotifyBFCacheOnMainThread(nsPIDOMWindowInner* aInner, + bool aCreated); + void NotifyToWindow(bool aCreated) const; + + void Init(const GlobalObject& aGlobal, const nsAString& aUrl, + const WebTransportOptions& aOptions, ErrorResult& aError); + void ResolveWaitingConnection(WebTransportReliabilityMode aReliability); + void RejectWaitingConnection(nsresult aRv); + bool ParseURL(const nsAString& aURL) const; + // this calls CloseNative(), which doesn't actually run script. See bug + // 1810942 + MOZ_CAN_RUN_SCRIPT_BOUNDARY void Cleanup( + WebTransportError* aError, const WebTransportCloseInfo* aCloseInfo, + ErrorResult& aRv); + + // From Parent + void NewBidirectionalStream( + uint64_t aStreamId, + const RefPtr<mozilla::ipc::DataPipeReceiver>& aIncoming, + const RefPtr<mozilla::ipc::DataPipeSender>& aOutgoing); + + void NewUnidirectionalStream( + uint64_t aStreamId, + const RefPtr<mozilla::ipc::DataPipeReceiver>& aStream); + + void NewDatagramReceived(nsTArray<uint8_t>&& aData, + const mozilla::TimeStamp& aTimeStamp); + + void RemoteClosed(bool aCleanly, const uint32_t& aCode, + const nsACString& aReason); + + void OnStreamResetOrStopSending(uint64_t aStreamId, + const StreamResetOrStopSendingError& aError); + // WebIDL Boilerplate + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + static already_AddRefed<WebTransport> Constructor( + const GlobalObject& aGlobal, const nsAString& aUrl, + const WebTransportOptions& aOptions, ErrorResult& aError); + + already_AddRefed<Promise> GetStats(ErrorResult& aError); + + already_AddRefed<Promise> Ready() { return do_AddRef(mReady); } + WebTransportReliabilityMode Reliability(); + WebTransportCongestionControl CongestionControl(); + already_AddRefed<Promise> Closed() { return do_AddRef(mClosed); } + MOZ_CAN_RUN_SCRIPT void Close(const WebTransportCloseInfo& aOptions, + ErrorResult& aRv); + already_AddRefed<WebTransportDatagramDuplexStream> GetDatagrams( + ErrorResult& aRv); + already_AddRefed<Promise> CreateBidirectionalStream( + const WebTransportSendStreamOptions& aOptions, ErrorResult& aRv); + already_AddRefed<Promise> CreateUnidirectionalStream( + const WebTransportSendStreamOptions& aOptions, ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT_BOUNDARY already_AddRefed<ReadableStream> + IncomingBidirectionalStreams(); + MOZ_CAN_RUN_SCRIPT_BOUNDARY already_AddRefed<ReadableStream> + IncomingUnidirectionalStreams(); + + void Shutdown() {} + + private: + ~WebTransport(); + + template <typename Stream> + MOZ_CAN_RUN_SCRIPT_BOUNDARY void PropagateError(Stream* aStream, + WebTransportError* aError); + + nsCOMPtr<nsIGlobalObject> mGlobal; + // We are the owner of WebTransportChild. We must call Shutdown() on it + // before we're destroyed. + RefPtr<WebTransportChild> mChild; + + // Spec in 5.8 says it can't be GC'd while CONNECTING or CONNECTED. We won't + // hold ref which we drop on CLOSED or FAILED because a reference is also held + // by IPC. We drop the IPC connection and by proxy the reference when it goes + // to FAILED or CLOSED. + + // Spec-defined slots: + // ordered sets, but we can't have duplicates, and this spec only appends. + // Order is visible due to + // https://w3c.github.io/webtransport/#webtransport-procedures step 10: "For + // each sendStream in sendStreams, error sendStream with error." + nsTHashMap<uint64_t, RefPtr<WebTransportSendStream>> mSendStreams; + nsTHashMap<uint64_t, RefPtr<WebTransportReceiveStream>> mReceiveStreams; + + WebTransportState mState; + RefPtr<Promise> mReady; + // XXX may not need to be a RefPtr, since we own it through the Streams + RefPtr<WebTransportIncomingStreamsAlgorithms> mIncomingBidirectionalAlgorithm; + RefPtr<WebTransportIncomingStreamsAlgorithms> + mIncomingUnidirectionalAlgorithm; + WebTransportReliabilityMode mReliability; + // Incoming streams get queued here. Use a TArray though it's working as + // a FIFO - rarely will there be more than one entry in these arrays, so + // the overhead of mozilla::Queue is unneeded + nsTArray<std::tuple<uint64_t, RefPtr<mozilla::ipc::DataPipeReceiver>>> + mUnidirectionalStreams; + nsTArray<std::tuple<uint64_t, UniquePtr<BidirectionalPair>>> + mBidirectionalStreams; + + // These are created in the constructor + RefPtr<ReadableStream> mIncomingUnidirectionalStreams; + RefPtr<ReadableStream> mIncomingBidirectionalStreams; + RefPtr<WebTransportDatagramDuplexStream> mDatagrams; + RefPtr<Promise> mClosed; +}; + +} // namespace mozilla::dom + +#endif // DOM_WEBTRANSPORT_API_WEBTRANSPORT__H_ diff --git a/dom/webtransport/api/WebTransportBidirectionalStream.cpp b/dom/webtransport/api/WebTransportBidirectionalStream.cpp new file mode 100644 index 0000000000..4fd867655e --- /dev/null +++ b/dom/webtransport/api/WebTransportBidirectionalStream.cpp @@ -0,0 +1,66 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "WebTransportBidirectionalStream.h" +#include "mozilla/dom/Promise.h" + +namespace mozilla::dom { + +using namespace mozilla::ipc; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTransportBidirectionalStream, mGlobal, + mReadable, mWritable) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTransportBidirectionalStream) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTransportBidirectionalStream) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransportBidirectionalStream) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +// WebIDL Boilerplate + +nsIGlobalObject* WebTransportBidirectionalStream::GetParentObject() const { + return mGlobal; +} + +JSObject* WebTransportBidirectionalStream::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return WebTransportBidirectionalStream_Binding::Wrap(aCx, this, aGivenProto); +} + +// static +already_AddRefed<WebTransportBidirectionalStream> +WebTransportBidirectionalStream::Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + DataPipeReceiver* receiver, DataPipeSender* sender, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#pullbidirectionalstream (and + // createBidirectionalStream) + + // Step 7.1: Let stream be the result of creating a + // WebTransportBidirectionalStream with internalStream and transport + RefPtr<WebTransportReceiveStream> readableStream = + WebTransportReceiveStream::Create(aWebTransport, aGlobal, aStreamId, + receiver, aRv); + if (!readableStream) { + return nullptr; + } + RefPtr<WebTransportSendStream> writableStream = + WebTransportSendStream::Create(aWebTransport, aGlobal, aStreamId, sender, + aRv); + if (!writableStream) { + return nullptr; + ; + } + + auto stream = MakeRefPtr<WebTransportBidirectionalStream>( + aGlobal, readableStream, writableStream); + return stream.forget(); +} + +// WebIDL Interface + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportBidirectionalStream.h b/dom/webtransport/api/WebTransportBidirectionalStream.h new file mode 100644 index 0000000000..e3735854a2 --- /dev/null +++ b/dom/webtransport/api/WebTransportBidirectionalStream.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef DOM_WEBTRANSPORT_API_WEBTRANSPORTBIDIRECTIONALSTREAM__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTBIDIRECTIONALSTREAM__H_ + +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsWrapperCache.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/ReadableStream.h" +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WritableStream.h" +#include "mozilla/dom/WebTransportSendReceiveStreamBinding.h" +#include "mozilla/ipc/DataPipe.h" + +// #include "mozilla/dom/WebTransportReceiveStream.h" +// #include "mozilla/dom/WebTransportSendStream.h" + +namespace mozilla::dom { +class WebTransportBidirectionalStream final : public nsISupports, + public nsWrapperCache { + public: + explicit WebTransportBidirectionalStream(nsIGlobalObject* aGlobal, + WebTransportReceiveStream* aReadable, + WebTransportSendStream* aWritable) + : mGlobal(aGlobal), mReadable(aReadable), mWritable(aWritable) {} + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebTransportBidirectionalStream) + + static already_AddRefed<WebTransportBidirectionalStream> Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + ::mozilla::ipc::DataPipeReceiver* receiver, + ::mozilla::ipc::DataPipeSender* sender, ErrorResult& aRv); + + // WebIDL Boilerplate + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + already_AddRefed<WebTransportReceiveStream> Readable() const { + return do_AddRef(mReadable); + } + already_AddRefed<WebTransportSendStream> Writable() const { + return do_AddRef(mWritable); + } + + private: + ~WebTransportBidirectionalStream() = default; + + nsCOMPtr<nsIGlobalObject> mGlobal; + RefPtr<WebTransportReceiveStream> mReadable; + RefPtr<WebTransportSendStream> mWritable; +}; + +} // namespace mozilla::dom + +#endif diff --git a/dom/webtransport/api/WebTransportDatagramDuplexStream.cpp b/dom/webtransport/api/WebTransportDatagramDuplexStream.cpp new file mode 100644 index 0000000000..be816c3d80 --- /dev/null +++ b/dom/webtransport/api/WebTransportDatagramDuplexStream.cpp @@ -0,0 +1,363 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "WebTransportDatagramDuplexStream.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/Promise-inl.h" +#include "mozilla/dom/WebTransportLog.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebTransportDatagramDuplexStream, mGlobal, + mReadable, mWritable, mWebTransport, + mIncomingAlgorithms, mOutgoingAlgorithms) +// mIncomingDatagramsQueue can't participate in a cycle +NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTransportDatagramDuplexStream) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WebTransportDatagramDuplexStream) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransportDatagramDuplexStream) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +WebTransportDatagramDuplexStream::WebTransportDatagramDuplexStream( + nsIGlobalObject* aGlobal, WebTransport* aWebTransport) + : mGlobal(aGlobal), mWebTransport(aWebTransport) {} + +void WebTransportDatagramDuplexStream::Init(ErrorResult& aError) { + // https://w3c.github.io/webtransport/#webtransport-constructor + // We are only called synchronously from JS creating a WebTransport object + AutoEntryScript aes(mGlobal, "WebTransportDatagrams"); + JSContext* cx = aes.cx(); + + mIncomingAlgorithms = new IncomingDatagramStreamAlgorithms(this); + nsCOMPtr<nsIGlobalObject> global(mGlobal); + RefPtr<IncomingDatagramStreamAlgorithms> incomingAlgorithms = + mIncomingAlgorithms; + // Step 18: Set up incomingDatagrams with pullAlgorithm set to + // pullDatagramsAlgorithm, and highWaterMark set to 0. + mReadable = ReadableStream::CreateNative(cx, global, *incomingAlgorithms, + Some(0.0), nullptr, aError); + if (aError.Failed()) { + return; + } + + mOutgoingAlgorithms = new OutgoingDatagramStreamAlgorithms(this); + RefPtr<OutgoingDatagramStreamAlgorithms> outgoingAlgorithms = + mOutgoingAlgorithms; + // Step 19: Set up outgoingDatagrams with writeAlgorithm set to + // writeDatagramsAlgorithm. + mWritable = WritableStream::CreateNative(cx, *global, *outgoingAlgorithms, + Nothing(), nullptr, aError); + if (aError.Failed()) { + return; + } + LOG(("Created datagram streams")); +} + +void WebTransportDatagramDuplexStream::SetIncomingMaxAge(double aMaxAge, + ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#dom-webtransportdatagramduplexstream-incomingmaxage + // Step 1 + if (isnan(aMaxAge) || aMaxAge < 0.) { + aRv.ThrowRangeError("Invalid IncomingMaxAge"); + return; + } + // Step 2 + if (aMaxAge == 0) { + aMaxAge = INFINITY; + } + // Step 3 + mIncomingMaxAge = aMaxAge; +} + +void WebTransportDatagramDuplexStream::SetOutgoingMaxAge(double aMaxAge, + ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#dom-webtransportdatagramduplexstream-outgoingmaxage + // Step 1 + if (isnan(aMaxAge) || aMaxAge < 0.) { + aRv.ThrowRangeError("Invalid OutgoingMaxAge"); + return; + } + // Step 2 + if (aMaxAge == 0.) { + aMaxAge = INFINITY; + } + // Step 3 + mOutgoingMaxAge = aMaxAge; +} + +void WebTransportDatagramDuplexStream::SetIncomingHighWaterMark( + double aWaterMark, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#dom-webtransportdatagramduplexstream-incominghighwatermark + // Step 1 + if (isnan(aWaterMark) || aWaterMark < 0.) { + aRv.ThrowRangeError("Invalid OutgoingMaxAge"); + return; + } + // Step 2 + if (aWaterMark < 1.0) { + aWaterMark = 1.0; + } + // Step 3 + mIncomingHighWaterMark = aWaterMark; +} + +void WebTransportDatagramDuplexStream::SetOutgoingHighWaterMark( + double aWaterMark, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#dom-webtransportdatagramduplexstream-outgoinghighwatermark + // Step 1 + if (isnan(aWaterMark) || aWaterMark < 0.) { + aRv.ThrowRangeError("Invalid OutgoingHighWaterMark"); + return; + } + // Step 2 of setter: If value is < 1, set value to 1. + if (aWaterMark < 1.0) { + aWaterMark = 1.0; + } + // Step 3 + mOutgoingHighWaterMark = aWaterMark; +} + +void WebTransportDatagramDuplexStream::NewDatagramReceived( + nsTArray<uint8_t>&& aData, const mozilla::TimeStamp& aTimeStamp) { + LOG(("received Datagram, size = %zu", aData.Length())); + mIncomingDatagramsQueue.Push(UniquePtr<DatagramEntry>( + new DatagramEntry(std::move(aData), aTimeStamp))); + mIncomingAlgorithms->NotifyDatagramAvailable(); +} + +// WebIDL Boilerplate + +nsIGlobalObject* WebTransportDatagramDuplexStream::GetParentObject() const { + return mGlobal; +} + +JSObject* WebTransportDatagramDuplexStream::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return WebTransportDatagramDuplexStream_Binding::Wrap(aCx, this, aGivenProto); +} + +// WebIDL Interface + +using namespace mozilla::ipc; + +NS_IMPL_CYCLE_COLLECTION_INHERITED(IncomingDatagramStreamAlgorithms, + UnderlyingSourceAlgorithmsWrapper, + mDatagrams, mIncomingDatagramsPullPromise) +NS_IMPL_ADDREF_INHERITED(IncomingDatagramStreamAlgorithms, + UnderlyingSourceAlgorithmsWrapper) +NS_IMPL_RELEASE_INHERITED(IncomingDatagramStreamAlgorithms, + UnderlyingSourceAlgorithmsWrapper) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IncomingDatagramStreamAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsWrapper) + +IncomingDatagramStreamAlgorithms::IncomingDatagramStreamAlgorithms( + WebTransportDatagramDuplexStream* aDatagrams) + : mDatagrams(aDatagrams) {} + +IncomingDatagramStreamAlgorithms::~IncomingDatagramStreamAlgorithms() = default; + +already_AddRefed<Promise> IncomingDatagramStreamAlgorithms::PullCallbackImpl( + JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#datagram-duplex-stream-procedures + + RefPtr<Promise> promise = + Promise::CreateInfallible(mDatagrams->GetParentObject()); + // Step 1: Let datagrams be transport.[[Datagrams]]. + // Step 2: Assert: datagrams.[[IncomingDatagramsPullPromise]] is null. + MOZ_ASSERT(!mIncomingDatagramsPullPromise); + + RefPtr<IncomingDatagramStreamAlgorithms> self(this); + // The real work of PullCallback() + // Step 3: Let queue be datagrams.[[IncomingDatagramsQueue]]. + // Step 4: If queue is empty, then: + if (mDatagrams->mIncomingDatagramsQueue.IsEmpty()) { + // We need to wait. + // Per + // https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-pulling + // we can't be called again until the promise is resolved + // Step 4.1: Set datagrams.[[IncomingDatagramsPullPromise]] to a new + // promise. + // Step 4.2: Return datagrams.[[IncomingDatagramsPullPromise]]. + mIncomingDatagramsPullPromise = promise; + + LOG(("Datagrams Pull waiting for a datagram")); + Result<RefPtr<Promise>, nsresult> returnResult = + promise->ThenWithCycleCollectedArgs( + [](JSContext* aCx, JS::Handle<JS::Value>, ErrorResult& aRv, + RefPtr<IncomingDatagramStreamAlgorithms> self, + RefPtr<Promise> aPromise) + MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> already_AddRefed<Promise> { + // https://w3c.github.io/webtransport/#receivedatagrams + // step 10 + self->ReturnDatagram(aCx, aRv); + return nullptr; + }, + self, promise); + if (returnResult.isErr()) { + aRv.Throw(returnResult.unwrapErr()); + return nullptr; + } + // Step 4: Return p and run the remaining steps in parallel. + return returnResult.unwrap().forget(); + } + // Steps 5-7 are covered here: + self->ReturnDatagram(aCx, aRv); + // Step 8: Return a promise resolved with undefined. + promise->MaybeResolveWithUndefined(); + return promise.forget(); +} + +// Note: fallible +void IncomingDatagramStreamAlgorithms::ReturnDatagram(JSContext* aCx, + ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#datagram-duplex-stream-procedures + // Pull and Receive + LOG(("Returning a Datagram")); + + MOZ_ASSERT(!mDatagrams->mIncomingDatagramsQueue.IsEmpty()); + // Pull Step 5: Let bytes and timestamp be the result of dequeuing queue. + UniquePtr<DatagramEntry> entry = mDatagrams->mIncomingDatagramsQueue.Pop(); + + // Pull Step 6: Let chunk be a new Uint8Array object representing bytes. + JSObject* outView = Uint8Array::Create(aCx, entry->mBuffer.Length(), + entry->mBuffer.Elements()); + if (!outView) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + JS::Rooted<JSObject*> chunk(aCx, outView); + + // Pull Step 7: Enqueue chunk to transport.[[Datagrams]].[[Readable]] + JS::Rooted<JS::Value> jsDatagram(aCx, JS::ObjectValue(*chunk)); + // EnqueueNative is CAN_RUN_SCRIPT + RefPtr<ReadableStream> stream = mDatagrams->mReadable; + stream->EnqueueNative(aCx, jsDatagram, aRv); + if (MOZ_UNLIKELY(aRv.Failed())) { + return; + } + // (caller) Step 8: return a promise resolved with Undefined +} + +void IncomingDatagramStreamAlgorithms::NotifyDatagramAvailable() { + if (RefPtr<Promise> promise = mIncomingDatagramsPullPromise.forget()) { + promise->MaybeResolveWithUndefined(); + } +} + +NS_IMPL_CYCLE_COLLECTION_INHERITED(OutgoingDatagramStreamAlgorithms, + UnderlyingSinkAlgorithmsWrapper, mDatagrams, + mWaitConnectPromise) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OutgoingDatagramStreamAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsWrapper) +NS_IMPL_ADDREF_INHERITED(OutgoingDatagramStreamAlgorithms, + UnderlyingSinkAlgorithmsWrapper) +NS_IMPL_RELEASE_INHERITED(OutgoingDatagramStreamAlgorithms, + UnderlyingSinkAlgorithmsWrapper) + +already_AddRefed<Promise> OutgoingDatagramStreamAlgorithms::WriteCallback( + JSContext* aCx, JS::Handle<JS::Value> aChunk, + WritableStreamDefaultController& aController, ErrorResult& aError) { + // https://w3c.github.io/webtransport/#writedatagrams + // Step 1. Let timestamp be a timestamp representing now. + TimeStamp now = TimeStamp::Now(); + + // Step 2: If data is not a BufferSource object, then return a promise + // rejected with a TypeError. { BufferSource == ArrayBuffer/ArrayBufferView } + ArrayBufferViewOrArrayBuffer arrayBuffer; + if (!arrayBuffer.Init(aCx, aChunk)) { + return Promise::CreateRejectedWithTypeError( + mDatagrams->GetParentObject(), + "Wrong type for Datagram stream write"_ns, aError); + } + + // Step 3: Let datagrams be transport.[[Datagrams]]. + // (mDatagrams is transport.[[Datagrams]]) + + // This is a duplicate of dom/encoding/TextDecoderStream.cpp#51-69 + // PeterV will deal with that when he lands his patch for TypedArrays + auto data = [&arrayBuffer]() { + if (arrayBuffer.IsArrayBuffer()) { + const ArrayBuffer& buffer = arrayBuffer.GetAsArrayBuffer(); + buffer.ComputeState(); + return Span{buffer.Data(), buffer.Length()}; + } + MOZ_ASSERT(arrayBuffer.IsArrayBufferView()); + const ArrayBufferView& buffer = arrayBuffer.GetAsArrayBufferView(); + buffer.ComputeState(); + return Span{buffer.Data(), buffer.Length()}; + }(); + + // Step 4: If datagrams.[[OutgoingMaxDatagramSize]] is less than data’s + // [[ByteLength]], return a promise resolved with undefined. + if (mDatagrams->mOutgoingMaxDataSize < static_cast<uint64_t>(data.Length())) { + return Promise::CreateResolvedWithUndefined(mDatagrams->GetParentObject(), + aError); + } + + // Step 5: Let promise be a new Promise + RefPtr<Promise> promise = + Promise::CreateInfallible(mDatagrams->GetParentObject()); + + // mChild is set when we move to Connected + if (mChild) { + // We pass along the datagram to the parent immediately. + // The OutgoingDatagramsQueue lives there, and steps 6-9 generally are + // implemented there + nsTArray<uint8_t> array(data); + LOG(("Sending Datagram, size = %zu", array.Length())); + mChild->SendOutgoingDatagram( + array, now, + [promise](nsresult&&) { + // XXX result + LOG(("Datagram was sent")); + promise->MaybeResolveWithUndefined(); + }, + [promise](mozilla::ipc::ResponseRejectReason&&) { + LOG(("Datagram failed")); + // there's no description in the spec of rejecting if a datagram + // can't be sent; to the contrary, it says we should resolve with + // undefined if we throw the datagram away + promise->MaybeResolveWithUndefined(); + }); + } else { + LOG(("Queuing datagram for connect")); + // Queue locally until we can send it. + // We should be guaranteed that we don't get called again until the + // promise is resolved. + MOZ_ASSERT(mWaitConnect == nullptr); + mWaitConnect.reset(new DatagramEntry(data, now)); + mWaitConnectPromise = promise; + } + + // Step 10: return promise + return promise.forget(); +} + +// XXX should we allow datagrams to be sent before connect? Check IETF spec +void OutgoingDatagramStreamAlgorithms::SetChild(WebTransportChild* aChild) { + LOG(("Setting child in datagrams")); + mChild = aChild; + if (mWaitConnect) { + LOG(("Sending queued datagram")); + mChild->SendOutgoingDatagram( + mWaitConnect->mBuffer, mWaitConnect->mTimeStamp, + [promise = mWaitConnectPromise](nsresult&&) { + LOG_VERBOSE(("Early Datagram was sent")); + promise->MaybeResolveWithUndefined(); + }, + [promise = mWaitConnectPromise](mozilla::ipc::ResponseRejectReason&&) { + LOG(("Early Datagram failed")); + promise->MaybeResolveWithUndefined(); + }); + mWaitConnectPromise = nullptr; + mWaitConnect.reset(nullptr); + } +} + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportDatagramDuplexStream.h b/dom/webtransport/api/WebTransportDatagramDuplexStream.h new file mode 100644 index 0000000000..510cbcac71 --- /dev/null +++ b/dom/webtransport/api/WebTransportDatagramDuplexStream.h @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef DOM_WEBTRANSPORT_API_WEBTRANSPORTDATAGRAMDUPLEXSTREAM__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTDATAGRAMDUPLEXSTREAM__H_ + +#include <utility> +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsWrapperCache.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/ReadableStream.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/dom/WritableStream.h" +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WebTransportDatagramDuplexStreamBinding.h" + +namespace mozilla::dom { + +class IncomingDatagramStreamAlgorithms + : public UnderlyingSourceAlgorithmsWrapper { + public: + explicit IncomingDatagramStreamAlgorithms( + WebTransportDatagramDuplexStream* aDatagrams); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IncomingDatagramStreamAlgorithms, + UnderlyingSourceAlgorithmsWrapper) + + MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PullCallbackImpl( + JSContext* aCx, ReadableStreamController& aController, + ErrorResult& aRv) override; + + MOZ_CAN_RUN_SCRIPT void ReturnDatagram(JSContext* aCx, ErrorResult& aRv); + + void NotifyDatagramAvailable(); + + protected: + ~IncomingDatagramStreamAlgorithms() override; + + private: + RefPtr<WebTransportDatagramDuplexStream> mDatagrams; + RefPtr<Promise> mIncomingDatagramsPullPromise; +}; + +class EarlyDatagram final { + public: + EarlyDatagram(DatagramEntry* aDatagram, Promise* aPromise) + : mDatagram(aDatagram), mWaitConnectPromise(aPromise) {} + + UniquePtr<DatagramEntry> mDatagram; + RefPtr<Promise> mWaitConnectPromise; + + ~EarlyDatagram() = default; +}; + +class OutgoingDatagramStreamAlgorithms final + : public UnderlyingSinkAlgorithmsWrapper { + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OutgoingDatagramStreamAlgorithms, + UnderlyingSinkAlgorithmsWrapper) + + explicit OutgoingDatagramStreamAlgorithms( + WebTransportDatagramDuplexStream* aDatagrams) + : mDatagrams(aDatagrams) {} + + void SetChild(WebTransportChild* aChild); + + // Streams algorithms + + already_AddRefed<Promise> WriteCallback( + JSContext* aCx, JS::Handle<JS::Value> aChunk, + WritableStreamDefaultController& aController, + ErrorResult& aError) override; + + private: + ~OutgoingDatagramStreamAlgorithms() override = default; + + RefPtr<WebTransportDatagramDuplexStream> mDatagrams; + RefPtr<WebTransportChild> mChild; + // only used for datagrams sent before Ready + UniquePtr<DatagramEntry> mWaitConnect; + RefPtr<Promise> mWaitConnectPromise; +}; + +class WebTransportDatagramDuplexStream final : public nsISupports, + public nsWrapperCache { + friend class IncomingDatagramStreamAlgorithms; + friend class OutgoingDatagramStreamAlgorithms; + + public: + WebTransportDatagramDuplexStream(nsIGlobalObject* aGlobal, + WebTransport* aWebTransport); + + void Init(ErrorResult& aError); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebTransportDatagramDuplexStream) + + void SetChild(WebTransportChild* aChild) { + mOutgoingAlgorithms->SetChild(aChild); + } + + void NewDatagramReceived(nsTArray<uint8_t>&& aData, + const mozilla::TimeStamp& aTimeStamp); + + // WebIDL Boilerplate + nsIGlobalObject* GetParentObject() const; + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + already_AddRefed<ReadableStream> Readable() const { + RefPtr<ReadableStream> result(mReadable); + return result.forget(); + } + already_AddRefed<WritableStream> Writable() { + RefPtr<WritableStream> result(mWritable); + return result.forget(); + } + + uint64_t MaxDatagramSize() const { return mOutgoingMaxDataSize; } + void SetMaxDatagramSize(const uint64_t& aMaxDatagramSize) { + mOutgoingMaxDataSize = aMaxDatagramSize; + } + double GetIncomingMaxAge(ErrorResult& aRv) const { return mIncomingMaxAge; } + void SetIncomingMaxAge(double aMaxAge, ErrorResult& aRv); + double GetOutgoingMaxAge(ErrorResult& aRv) const { return mOutgoingMaxAge; } + void SetOutgoingMaxAge(double aMaxAge, ErrorResult& aRv); + double GetIncomingHighWaterMark(ErrorResult& aRv) const { + return mIncomingHighWaterMark; + } + void SetIncomingHighWaterMark(double aWaterMark, ErrorResult& aRv); + double GetOutgoingHighWaterMark(ErrorResult& aRv) const { + return mOutgoingHighWaterMark; + } + void SetOutgoingHighWaterMark(double aWaterMark, ErrorResult& aRv); + + private: + ~WebTransportDatagramDuplexStream() = default; + + nsCOMPtr<nsIGlobalObject> mGlobal; + RefPtr<WebTransport> mWebTransport; + RefPtr<ReadableStream> mReadable; + RefPtr<WritableStream> mWritable; + RefPtr<IncomingDatagramStreamAlgorithms> mIncomingAlgorithms; + RefPtr<OutgoingDatagramStreamAlgorithms> mOutgoingAlgorithms; + + // https://w3c.github.io/webtransport/#webtransportdatagramduplexstream-create + double mIncomingMaxAge = INFINITY; + double mOutgoingMaxAge = INFINITY; + // These are implementation-defined + double mIncomingHighWaterMark = 1.0; + double mOutgoingHighWaterMark = 5.0; + uint64_t mOutgoingMaxDataSize = 1024; // implementation-defined integer + + mozilla::Queue<UniquePtr<DatagramEntry>, 32> mIncomingDatagramsQueue; +}; + +} // namespace mozilla::dom + +#endif diff --git a/dom/webtransport/api/WebTransportError.cpp b/dom/webtransport/api/WebTransportError.cpp new file mode 100644 index 0000000000..8451e3937f --- /dev/null +++ b/dom/webtransport/api/WebTransportError.cpp @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "WebTransportError.h" + +namespace mozilla::dom { + +JSObject* WebTransportError::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return WebTransportError_Binding::Wrap(aCx, this, aGivenProto); +} + +/* static */ +already_AddRefed<WebTransportError> WebTransportError::Constructor( + const GlobalObject& aGlobal, const WebTransportErrorInit& aInit) { + // https://w3c.github.io/webtransport/#web-transport-error-constructor1 + + // Step 2: Let message be init.message if it exists, and "" otherwise. + nsCString message(""_ns); + if (aInit.mMessage.WasPassed()) { + CopyUTF16toUTF8(aInit.mMessage.Value(), message); + } + + // Step 1: Let error be this. + // Step 3: Set up error with message and "stream". + RefPtr<WebTransportError> error(new WebTransportError(message)); + + // Step 4: Set error.[[StreamErrorCode]] to init.streamErrorCode if it exists. + if (aInit.mStreamErrorCode.WasPassed()) { + error->mStreamErrorCode = Nullable(aInit.mStreamErrorCode.Value()); + } + return error.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportError.h b/dom/webtransport/api/WebTransportError.h new file mode 100644 index 0000000000..a4b4e088e3 --- /dev/null +++ b/dom/webtransport/api/WebTransportError.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef DOM_WEBTRANSPORT_API_WEBTRANSPORTERROR__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTERROR__H_ + +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/WebTransportErrorBinding.h" + +namespace mozilla::dom { +class WebTransportError final : public DOMException { + public: + explicit WebTransportError( + const nsACString& aMessage, + WebTransportErrorSource aSource = WebTransportErrorSource::Stream, + Nullable<uint8_t> aCode = Nullable<uint8_t>()) + : DOMException(NS_OK, aMessage, "WebTransportError"_ns, 0), + mStreamErrorCode(aCode), + mSource(aSource) {} + + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + static already_AddRefed<WebTransportError> Constructor( + const GlobalObject& aGlobal, const WebTransportErrorInit& aInit); + + WebTransportErrorSource Source() { return mSource; } + Nullable<uint8_t> GetStreamErrorCode() const { return mStreamErrorCode; } + + private: + Nullable<uint8_t> mStreamErrorCode; + const WebTransportErrorSource mSource; +}; + +} // namespace mozilla::dom + +#endif diff --git a/dom/webtransport/api/WebTransportReceiveStream.cpp b/dom/webtransport/api/WebTransportReceiveStream.cpp new file mode 100644 index 0000000000..ef9eba8d9c --- /dev/null +++ b/dom/webtransport/api/WebTransportReceiveStream.cpp @@ -0,0 +1,73 @@ +/* -*- 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 "mozilla/dom/WebTransportReceiveStream.h" + +#include "mozilla/dom/ReadableByteStreamController.h" +#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h" +#include "mozilla/dom/ReadableStream.h" +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WebTransportSendReceiveStreamBinding.h" +#include "mozilla/ipc/DataPipe.h" + +using namespace mozilla::ipc; + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(WebTransportReceiveStream, ReadableStream, + mTransport) +NS_IMPL_ADDREF_INHERITED(WebTransportReceiveStream, ReadableStream) +NS_IMPL_RELEASE_INHERITED(WebTransportReceiveStream, ReadableStream) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransportReceiveStream) +NS_INTERFACE_MAP_END_INHERITING(ReadableStream) + +WebTransportReceiveStream::WebTransportReceiveStream(nsIGlobalObject* aGlobal, + WebTransport* aTransport) + : ReadableStream(aGlobal, + ReadableStream::HoldDropJSObjectsCaller::Explicit), + mTransport(aTransport) { + mozilla::HoldJSObjects(this); +} + +// WebIDL Boilerplate + +JSObject* WebTransportReceiveStream::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return WebTransportReceiveStream_Binding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<WebTransportReceiveStream> WebTransportReceiveStream::Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + DataPipeReceiver* receiver, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#webtransportreceivestream-create + AutoJSAPI jsapi; + if (!jsapi.Init(aGlobal)) { + return nullptr; + } + JSContext* cx = jsapi.cx(); + + auto stream = MakeRefPtr<WebTransportReceiveStream>(aGlobal, aWebTransport); + + nsCOMPtr<nsIAsyncInputStream> inputStream = receiver; + auto algorithms = MakeRefPtr<InputToReadableStreamAlgorithms>( + cx, inputStream, (ReadableStream*)stream); + + stream->SetUpByteNative(cx, *algorithms, Some(0.0), aRv); + if (aRv.Failed()) { + return nullptr; + } + // Add to ReceiveStreams + aWebTransport->mReceiveStreams.InsertOrUpdate(aStreamId, stream); + return stream.forget(); +} + +already_AddRefed<Promise> WebTransportReceiveStream::GetStats() { + RefPtr<Promise> promise = Promise::CreateInfallible(ReadableStream::mGlobal); + promise->MaybeRejectWithNotSupportedError("GetStats isn't supported yet"); + return promise.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportReceiveStream.h b/dom/webtransport/api/WebTransportReceiveStream.h new file mode 100644 index 0000000000..d27a6673d9 --- /dev/null +++ b/dom/webtransport/api/WebTransportReceiveStream.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef DOM_WEBTRANSPORT_API_WEBTRANSPORTRECEIVESTREAM__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTRECEIVESTREAM__H_ + +#include "mozilla/dom/ReadableStream.h" + +namespace mozilla::ipc { +class DataPipeReceiver; +} + +namespace mozilla::dom { + +class WebTransport; + +class WebTransportReceiveStream final : public ReadableStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WebTransportReceiveStream, + ReadableStream) + + WebTransportReceiveStream(nsIGlobalObject* aGlobal, WebTransport* aTransport); + + static already_AddRefed<WebTransportReceiveStream> Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + mozilla::ipc::DataPipeReceiver* receiver, ErrorResult& aRv); + + // WebIDL Boilerplate + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + already_AddRefed<Promise> GetStats(); + + private: + ~WebTransportReceiveStream() override { mozilla::DropJSObjects(this); } + + // We must hold a reference to the WebTransport so it can't go away on + // us. This forms a cycle with WebTransport that will be broken when the + // CC runs. WebTransport::CleanUp() will destroy all the send and receive + // streams, breaking the cycle. + RefPtr<WebTransport> mTransport; +}; +} // namespace mozilla::dom + +#endif diff --git a/dom/webtransport/api/WebTransportSendStream.cpp b/dom/webtransport/api/WebTransportSendStream.cpp new file mode 100644 index 0000000000..18df0373c8 --- /dev/null +++ b/dom/webtransport/api/WebTransportSendStream.cpp @@ -0,0 +1,83 @@ +/* -*- 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 "mozilla/dom/WebTransportSendStream.h" + +#include "mozilla/dom/UnderlyingSinkCallbackHelpers.h" +#include "mozilla/dom/WritableStream.h" +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WebTransportSendReceiveStreamBinding.h" +#include "mozilla/ipc/DataPipe.h" + +using namespace mozilla::ipc; + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_INHERITED(WebTransportSendStream, WritableStream, + mTransport) +NS_IMPL_ADDREF_INHERITED(WebTransportSendStream, WritableStream) +NS_IMPL_RELEASE_INHERITED(WebTransportSendStream, WritableStream) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransportSendStream) +NS_INTERFACE_MAP_END_INHERITING(WritableStream) + +WebTransportSendStream::WebTransportSendStream(nsIGlobalObject* aGlobal, + WebTransport* aTransport) + : WritableStream(aGlobal, + WritableStream::HoldDropJSObjectsCaller::Explicit), + mTransport(aTransport) { + mozilla::HoldJSObjects(this); +} + +JSObject* WebTransportSendStream::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return WebTransportSendStream_Binding::Wrap(aCx, this, aGivenProto); +} + +// NOTE: this does not yet implement SendOrder; see bug 1816925 +/* static */ +already_AddRefed<WebTransportSendStream> WebTransportSendStream::Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + DataPipeSender* sender, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#webtransportsendstream-create + AutoJSAPI jsapi; + if (!jsapi.Init(aGlobal)) { + return nullptr; + } + JSContext* cx = jsapi.cx(); + + auto stream = MakeRefPtr<WebTransportSendStream>(aGlobal, aWebTransport); + + nsCOMPtr<nsIAsyncOutputStream> outputStream = sender; + auto algorithms = MakeRefPtr<WritableStreamToOutput>( + stream->GetParentObject(), outputStream); + + // Steps 2-5 + RefPtr<QueuingStrategySize> writableSizeAlgorithm; + stream->SetUpNative(cx, *algorithms, Nothing(), writableSizeAlgorithm, aRv); + + // Step 6: Add the following steps to stream’s [[controller]]'s [[signal]]. + // Step 6.1: If stream.[[PendingOperation]] is null, then abort these steps. + // Step 6.2: Let reason be stream’s [[controller]]'s [[signal]]'s abort + // reason. Step 6.3: Let abortPromise be the result of aborting stream with + // reason. Step 6.4: Upon fulfillment of abortPromise, reject promise with + // reason. Step 6.5: Let pendingOperation be stream.[[PendingOperation]]. + // Step 6.6: Set stream.[[PendingOperation]] to null. + // Step 6.7: Resolve pendingOperation with promise. + // XXX TODO + + // Step 7: Append stream to SendStreams + aWebTransport->mSendStreams.InsertOrUpdate(aStreamId, stream); + // Step 8: return stream + return stream.forget(); +} + +already_AddRefed<Promise> WebTransportSendStream::GetStats() { + RefPtr<Promise> promise = Promise::CreateInfallible(WritableStream::mGlobal); + promise->MaybeRejectWithNotSupportedError("GetStats isn't supported yet"); + return promise.forget(); +} + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportSendStream.h b/dom/webtransport/api/WebTransportSendStream.h new file mode 100644 index 0000000000..c31da5d483 --- /dev/null +++ b/dom/webtransport/api/WebTransportSendStream.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef DOM_WEBTRANSPORT_API_WEBTRANSPORTSENDSTREAM__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTSENDSTREAM__H_ + +#include "mozilla/dom/WritableStream.h" + +namespace mozilla::ipc { +class DataPipeSender; +} + +namespace mozilla::dom { + +class WebTransport; + +class WebTransportSendStream final : public WritableStream { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WebTransportSendStream, + WritableStream) + + WebTransportSendStream(nsIGlobalObject* aGlobal, WebTransport* aTransport); + + static already_AddRefed<WebTransportSendStream> Create( + WebTransport* aWebTransport, nsIGlobalObject* aGlobal, uint64_t aStreamId, + mozilla::ipc::DataPipeSender* sender, ErrorResult& aRv); + + // WebIDL Boilerplate + JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL Interface + already_AddRefed<Promise> GetStats(); + + private: + ~WebTransportSendStream() override { mozilla::DropJSObjects(this); }; + + // We must hold a reference to the WebTransport so it can't go away on + // us. This forms a cycle with WebTransport that will be broken when the + // CC runs. WebTransport::CleanUp() will destroy all the send and receive + // streams, breaking the cycle. + RefPtr<WebTransport> mTransport; +}; +} // namespace mozilla::dom + +#endif diff --git a/dom/webtransport/api/WebTransportStreams.cpp b/dom/webtransport/api/WebTransportStreams.cpp new file mode 100644 index 0000000000..82924fa78f --- /dev/null +++ b/dom/webtransport/api/WebTransportStreams.cpp @@ -0,0 +1,197 @@ +/* -*- 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 "mozilla/dom/WebTransportStreams.h" + +#include "mozilla/dom/WebTransportLog.h" +#include "mozilla/dom/Promise-inl.h" +#include "mozilla/dom/WebTransport.h" +#include "mozilla/dom/WebTransportBidirectionalStream.h" +#include "mozilla/dom/WebTransportReceiveStream.h" +#include "mozilla/dom/WebTransportSendStream.h" +#include "mozilla/Result.h" + +using namespace mozilla::ipc; + +namespace mozilla::dom { +NS_IMPL_CYCLE_COLLECTION_INHERITED(WebTransportIncomingStreamsAlgorithms, + UnderlyingSourceAlgorithmsWrapper, + mTransport, mCallback) +NS_IMPL_ADDREF_INHERITED(WebTransportIncomingStreamsAlgorithms, + UnderlyingSourceAlgorithmsWrapper) +NS_IMPL_RELEASE_INHERITED(WebTransportIncomingStreamsAlgorithms, + UnderlyingSourceAlgorithmsWrapper) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebTransportIncomingStreamsAlgorithms) +NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsWrapper) + +WebTransportIncomingStreamsAlgorithms::WebTransportIncomingStreamsAlgorithms( + StreamType aUnidirectional, WebTransport* aTransport) + : mUnidirectional(aUnidirectional), mTransport(aTransport) {} + +WebTransportIncomingStreamsAlgorithms:: + ~WebTransportIncomingStreamsAlgorithms() = default; + +already_AddRefed<Promise> +WebTransportIncomingStreamsAlgorithms::PullCallbackImpl( + JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#pullbidirectionalstream and + // https://w3c.github.io/webtransport/#pullunidirectionalstream + + // Step 1: If transport.[[State]] is "connecting", then return the result + // of performing the following steps upon fulfillment of + // transport.[[Ready]]: + // We don't explicitly check mState here, since we'll reject + // mIncomingStreamPromise if we go to FAILED or CLOSED + // + // Step 2: Let session be transport.[[Session]]. + // Step 3: Let p be a new promise. + RefPtr<Promise> promise = + Promise::CreateInfallible(mTransport->GetParentObject()); + RefPtr<WebTransportIncomingStreamsAlgorithms> self(this); + // The real work of PullCallback() + // Step 5: Wait until there is an available incoming unidirectional stream. + auto length = (mUnidirectional == StreamType::Unidirectional) + ? mTransport->mUnidirectionalStreams.Length() + : mTransport->mBidirectionalStreams.Length(); + if (length == 0) { + // We need to wait. + // Per + // https://streams.spec.whatwg.org/#readablestreamdefaultcontroller-pulling + // we can't be called again until the promise is resolved + MOZ_ASSERT(!mCallback); + mCallback = promise; + + LOG(("Incoming%sDirectionalStreams Pull waiting for a stream", + mUnidirectional == StreamType::Unidirectional ? "Uni" : "Bi")); + Result<RefPtr<Promise>, nsresult> returnResult = + promise->ThenWithCycleCollectedArgs( + [](JSContext* aCx, JS::Handle<JS::Value>, ErrorResult& aRv, + RefPtr<WebTransportIncomingStreamsAlgorithms> self, + RefPtr<Promise> aPromise) -> already_AddRefed<Promise> { + self->BuildStream(aCx, aRv); + return nullptr; + }, + self, promise); + if (returnResult.isErr()) { + // XXX Reject? + aRv.Throw(returnResult.unwrapErr()); + return nullptr; + } + // Step 4: Return p and run the remaining steps in parallel. + return returnResult.unwrap().forget(); + } + self->BuildStream(aCx, aRv); + // Step 4: Return p and run the remaining steps in parallel. + return promise.forget(); +} + +// Note: fallible +void WebTransportIncomingStreamsAlgorithms::BuildStream(JSContext* aCx, + ErrorResult& aRv) { + // https://w3c.github.io/webtransport/#pullbidirectionalstream and + // https://w3c.github.io/webtransport/#pullunidirectionalstream + LOG(("Incoming%sDirectionalStreams Pull building a stream", + mUnidirectional == StreamType::Unidirectional ? "Uni" : "Bi")); + if (mUnidirectional == StreamType::Unidirectional) { + // Step 6: Let internalStream be the result of receiving an incoming + // unidirectional stream. + MOZ_ASSERT(mTransport->mUnidirectionalStreams.Length() > 0); + std::tuple<uint64_t, RefPtr<mozilla::ipc::DataPipeReceiver>> tuple = + mTransport->mUnidirectionalStreams[0]; + mTransport->mUnidirectionalStreams.RemoveElementAt(0); + + // Step 7.1: Let stream be the result of creating a + // WebTransportReceiveStream with internalStream and transport + RefPtr<WebTransportReceiveStream> readableStream = + WebTransportReceiveStream::Create(mTransport, mTransport->mGlobal, + std::get<0>(tuple), + std::get<1>(tuple), aRv); + if (MOZ_UNLIKELY(!readableStream)) { + aRv.ThrowUnknownError("Internal error"); + return; + } + // Step 7.2 Enqueue stream to transport.[[IncomingUnidirectionalStreams]]. + JS::Rooted<JS::Value> jsStream(aCx); + if (MOZ_UNLIKELY(!ToJSValue(aCx, readableStream, &jsStream))) { + aRv.ThrowUnknownError("Internal error"); + return; + } + // EnqueueNative is CAN_RUN_SCRIPT + RefPtr<ReadableStream> incomingStream = + mTransport->mIncomingUnidirectionalStreams; + incomingStream->EnqueueNative(aCx, jsStream, aRv); + if (MOZ_UNLIKELY(aRv.Failed())) { + aRv.ThrowUnknownError("Internal error"); + return; + } + } else { + // Step 6: Let internalStream be the result of receiving a bidirectional + // stream + MOZ_ASSERT(mTransport->mBidirectionalStreams.Length() > 0); + std::tuple<uint64_t, UniquePtr<BidirectionalPair>> tuple = + std::move(mTransport->mBidirectionalStreams.ElementAt(0)); + mTransport->mBidirectionalStreams.RemoveElementAt(0); + RefPtr<DataPipeReceiver> input = std::get<1>(tuple)->first.forget(); + RefPtr<DataPipeSender> output = std::get<1>(tuple)->second.forget(); + + RefPtr<WebTransportBidirectionalStream> stream = + WebTransportBidirectionalStream::Create(mTransport, mTransport->mGlobal, + std::get<0>(tuple), input, + output, aRv); + + // Step 7.2 Enqueue stream to transport.[[IncomingBidirectionalStreams]]. + JS::Rooted<JS::Value> jsStream(aCx); + if (MOZ_UNLIKELY(!ToJSValue(aCx, stream, &jsStream))) { + return; + } + LOG(("Enqueuing bidirectional stream\n")); + // EnqueueNative is CAN_RUN_SCRIPT + RefPtr<ReadableStream> incomingStream = + mTransport->mIncomingBidirectionalStreams; + incomingStream->EnqueueNative(aCx, jsStream, aRv); + if (MOZ_UNLIKELY(aRv.Failed())) { + return; + } + } + // Step 7.3: Resolve p with undefined. +} + +void WebTransportIncomingStreamsAlgorithms::NotifyIncomingStream() { + if (mUnidirectional == StreamType::Unidirectional) { + LOG(("NotifyIncomingStream: %zu Unidirectional ", + mTransport->mUnidirectionalStreams.Length())); +#ifdef DEBUG + auto number = mTransport->mUnidirectionalStreams.Length(); + MOZ_ASSERT(number > 0); +#endif + RefPtr<Promise> promise = mCallback.forget(); + if (promise) { + promise->MaybeResolveWithUndefined(); + } + } else { + LOG(("NotifyIncomingStream: %zu Bidirectional ", + mTransport->mBidirectionalStreams.Length())); +#ifdef DEBUG + auto number = mTransport->mBidirectionalStreams.Length(); + MOZ_ASSERT(number > 0); +#endif + RefPtr<Promise> promise = mCallback.forget(); + if (promise) { + promise->MaybeResolveWithUndefined(); + } + } +} + +void WebTransportIncomingStreamsAlgorithms::NotifyRejectAll() { + // cancel all pulls + LOG(("Cancel all WebTransport Pulls")); + // Ensure we clear the callback before resolving/rejecting it + if (RefPtr<Promise> promise = mCallback.forget()) { + promise->MaybeReject(NS_ERROR_FAILURE); + } +} + +} // namespace mozilla::dom diff --git a/dom/webtransport/api/WebTransportStreams.h b/dom/webtransport/api/WebTransportStreams.h new file mode 100644 index 0000000000..e9cc06176d --- /dev/null +++ b/dom/webtransport/api/WebTransportStreams.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/. */ + +#ifndef DOM_WEBTRANSPORT_API_WEBTRANSPORTSTREAMS__H_ +#define DOM_WEBTRANSPORT_API_WEBTRANSPORTSTREAMS__H_ + +#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h" +#include "mozilla/dom/WritableStream.h" + +namespace mozilla::dom { + +class WebTransport; + +class WebTransportIncomingStreamsAlgorithms + : public UnderlyingSourceAlgorithmsWrapper { + public: + enum class StreamType : uint8_t { Unidirectional, Bidirectional }; + WebTransportIncomingStreamsAlgorithms(StreamType aUnidirectional, + WebTransport* aTransport); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED( + WebTransportIncomingStreamsAlgorithms, UnderlyingSourceAlgorithmsWrapper) + + already_AddRefed<Promise> PullCallbackImpl( + JSContext* aCx, ReadableStreamController& aController, + ErrorResult& aRv) override; + + // We call EnqueueNative, which is MOZ_CAN_RUN_SCRIPT but won't in this case + MOZ_CAN_RUN_SCRIPT_BOUNDARY void BuildStream(JSContext* aCx, + ErrorResult& aRv); + + void NotifyIncomingStream(); + + void NotifyRejectAll(); + + protected: + ~WebTransportIncomingStreamsAlgorithms() override; + + private: + const StreamType mUnidirectional; + RefPtr<WebTransport> mTransport; + RefPtr<Promise> mCallback; +}; + +} // namespace mozilla::dom + +#endif // DOM_WEBTRANSPORT_API_WEBTRANSPORTSTREAMS__H_ diff --git a/dom/webtransport/api/moz.build b/dom/webtransport/api/moz.build new file mode 100644 index 0000000000..7a20c9b501 --- /dev/null +++ b/dom/webtransport/api/moz.build @@ -0,0 +1,29 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS.mozilla.dom += [ + "WebTransport.h", + "WebTransportBidirectionalStream.h", + "WebTransportDatagramDuplexStream.h", + "WebTransportError.h", + "WebTransportReceiveStream.h", + "WebTransportSendStream.h", + "WebTransportStreams.h", +] + +UNIFIED_SOURCES += [ + "WebTransport.cpp", + "WebTransportBidirectionalStream.cpp", + "WebTransportDatagramDuplexStream.cpp", + "WebTransportError.cpp", + "WebTransportReceiveStream.cpp", + "WebTransportSendStream.cpp", + "WebTransportStreams.cpp", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") |