summaryrefslogtreecommitdiffstats
path: root/dom/webtransport
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/webtransport
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/webtransport')
-rw-r--r--dom/webtransport/api/WebTransport.cpp942
-rw-r--r--dom/webtransport/api/WebTransport.h172
-rw-r--r--dom/webtransport/api/WebTransportBidirectionalStream.cpp66
-rw-r--r--dom/webtransport/api/WebTransportBidirectionalStream.h64
-rw-r--r--dom/webtransport/api/WebTransportDatagramDuplexStream.cpp363
-rw-r--r--dom/webtransport/api/WebTransportDatagramDuplexStream.h166
-rw-r--r--dom/webtransport/api/WebTransportError.cpp38
-rw-r--r--dom/webtransport/api/WebTransportError.h40
-rw-r--r--dom/webtransport/api/WebTransportReceiveStream.cpp73
-rw-r--r--dom/webtransport/api/WebTransportReceiveStream.h50
-rw-r--r--dom/webtransport/api/WebTransportSendStream.cpp83
-rw-r--r--dom/webtransport/api/WebTransportSendStream.h50
-rw-r--r--dom/webtransport/api/WebTransportStreams.cpp197
-rw-r--r--dom/webtransport/api/WebTransportStreams.h51
-rw-r--r--dom/webtransport/api/moz.build29
-rw-r--r--dom/webtransport/child/WebTransportChild.cpp75
-rw-r--r--dom/webtransport/child/WebTransportChild.h58
-rw-r--r--dom/webtransport/child/moz.build17
-rw-r--r--dom/webtransport/moz.build16
-rw-r--r--dom/webtransport/parent/WebTransportParent.cpp750
-rw-r--r--dom/webtransport/parent/WebTransportParent.h100
-rw-r--r--dom/webtransport/parent/moz.build22
-rw-r--r--dom/webtransport/shared/PWebTransport.ipdl96
-rw-r--r--dom/webtransport/shared/WebTransportLog.cpp13
-rw-r--r--dom/webtransport/shared/WebTransportLog.h25
-rw-r--r--dom/webtransport/shared/moz.build21
-rw-r--r--dom/webtransport/test/moz.build9
-rw-r--r--dom/webtransport/test/xpcshell/moz.build9
-rw-r--r--dom/webtransport/test/xpcshell/test_close.js65
-rw-r--r--dom/webtransport/test/xpcshell/test_simple_conn.js129
-rw-r--r--dom/webtransport/test/xpcshell/test_simple_stream.js168
-rw-r--r--dom/webtransport/test/xpcshell/xpcshell.ini29
32 files changed, 3986 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")
diff --git a/dom/webtransport/child/WebTransportChild.cpp b/dom/webtransport/child/WebTransportChild.cpp
new file mode 100644
index 0000000000..12714b003e
--- /dev/null
+++ b/dom/webtransport/child/WebTransportChild.cpp
@@ -0,0 +1,75 @@
+/* -*- 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 "mozilla/dom/WebTransport.h"
+#include "mozilla/dom/WebTransportChild.h"
+#include "mozilla/dom/WebTransportLog.h"
+
+namespace mozilla::dom {
+
+void WebTransportChild::Shutdown(bool aClose) {
+ LOG(("WebTransportChild::Shutdown() for %p (%p)", this, mTransport));
+ mTransport = nullptr;
+ if (!aClose || !CanSend()) {
+ return;
+ }
+
+ Close();
+}
+
+void WebTransportChild::CloseAll() {
+ // XXX need impl
+}
+
+::mozilla::ipc::IPCResult WebTransportChild::RecvCloseAll(
+ CloseAllResolver&& aResolver) {
+ CloseAll();
+ aResolver(NS_OK);
+ return IPC_OK();
+}
+
+::mozilla::ipc::IPCResult WebTransportChild::RecvRemoteClosed(
+ const bool& aCleanly, const uint32_t& aCode, const nsACString& aReason) {
+ if (mTransport) {
+ mTransport->RemoteClosed(aCleanly, aCode, aReason);
+ }
+ return IPC_OK();
+}
+
+::mozilla::ipc::IPCResult WebTransportChild::RecvIncomingBidirectionalStream(
+ const uint64_t& aStreamId, const RefPtr<DataPipeReceiver>& aIncoming,
+ const RefPtr<DataPipeSender>& aOutgoing) {
+ if (mTransport) {
+ mTransport->NewBidirectionalStream(aStreamId, aIncoming, aOutgoing);
+ }
+ return IPC_OK();
+}
+
+::mozilla::ipc::IPCResult WebTransportChild::RecvIncomingUnidirectionalStream(
+ const uint64_t& aStreamId, const RefPtr<DataPipeReceiver>& aStream) {
+ if (mTransport) {
+ mTransport->NewUnidirectionalStream(aStreamId, aStream);
+ }
+ return IPC_OK();
+}
+
+::mozilla::ipc::IPCResult WebTransportChild::RecvIncomingDatagram(
+ nsTArray<uint8_t>&& aData, const TimeStamp& aRecvTimeStamp) {
+ if (mTransport) {
+ mTransport->NewDatagramReceived(std::move(aData), aRecvTimeStamp);
+ }
+ return IPC_OK();
+}
+
+::mozilla::ipc::IPCResult WebTransportChild::RecvOnStreamResetOrStopSending(
+ const uint64_t& aStreamId, const StreamResetOrStopSendingError& aError) {
+ if (mTransport) {
+ mTransport->OnStreamResetOrStopSending(aStreamId, aError);
+ }
+ return IPC_OK();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/webtransport/child/WebTransportChild.h b/dom/webtransport/child/WebTransportChild.h
new file mode 100644
index 0000000000..7e89896b89
--- /dev/null
+++ b/dom/webtransport/child/WebTransportChild.h
@@ -0,0 +1,58 @@
+/* -*- 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_WEBTRANSPORTCHILD_H_
+#define DOM_WEBTRANSPORT_WEBTRANSPORTCHILD_H_
+
+#include "mozilla/TimeStamp.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/dom/PWebTransportChild.h"
+#include "mozilla/ipc/DataPipe.h"
+
+namespace mozilla::dom {
+
+class WebTransport;
+
+class WebTransportChild : public PWebTransportChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(WebTransportChild)
+ explicit WebTransportChild(WebTransport* aTransport)
+ : mTransport(aTransport) {}
+
+ virtual void CloseAll();
+
+ void Shutdown(bool aClose);
+
+ ::mozilla::ipc::IPCResult RecvCloseAll(CloseAllResolver&& aResolver);
+
+ ::mozilla::ipc::IPCResult RecvRemoteClosed(const bool& aCleanly,
+ const uint32_t& aCode,
+ const nsACString& aReason);
+
+ ::mozilla::ipc::IPCResult RecvIncomingBidirectionalStream(
+ const uint64_t& aStreamId,
+ const RefPtr<mozilla::ipc::DataPipeReceiver>& aIncoming,
+ const RefPtr<mozilla::ipc::DataPipeSender>& aOutgoing);
+
+ ::mozilla::ipc::IPCResult RecvIncomingUnidirectionalStream(
+ const uint64_t& aStreamId,
+ const RefPtr<mozilla::ipc::DataPipeReceiver>& aStream);
+
+ ::mozilla::ipc::IPCResult RecvIncomingDatagram(
+ nsTArray<uint8_t>&& aData, const TimeStamp& aRecvTimeStamp);
+
+ ::mozilla::ipc::IPCResult RecvOnStreamResetOrStopSending(
+ const uint64_t& aStreamId, const StreamResetOrStopSendingError& aError);
+
+ protected:
+ WebTransport* mTransport; // WebTransport holds a strong reference to us, and
+ // calls Shutdown() before releasing it
+ virtual ~WebTransportChild() { MOZ_ASSERT(!mTransport); }
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_WEBTRANSPORT_WEBTRANSPORTCHILD_H_
diff --git a/dom/webtransport/child/moz.build b/dom/webtransport/child/moz.build
new file mode 100644
index 0000000000..21df93c138
--- /dev/null
+++ b/dom/webtransport/child/moz.build
@@ -0,0 +1,17 @@
+# -*- 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 += [
+ "WebTransportChild.h",
+]
+
+UNIFIED_SOURCES += [
+ "WebTransportChild.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/dom/webtransport/moz.build b/dom/webtransport/moz.build
new file mode 100644
index 0000000000..0c825bca62
--- /dev/null
+++ b/dom/webtransport/moz.build
@@ -0,0 +1,16 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Networking")
+
+DIRS += [
+ "api",
+ "child",
+ "parent",
+ "shared",
+ "test",
+]
diff --git a/dom/webtransport/parent/WebTransportParent.cpp b/dom/webtransport/parent/WebTransportParent.cpp
new file mode 100644
index 0000000000..308ac4f867
--- /dev/null
+++ b/dom/webtransport/parent/WebTransportParent.cpp
@@ -0,0 +1,750 @@
+/* -*- 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 "WebTransportParent.h"
+#include "Http3WebTransportSession.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/WebTransportBinding.h"
+#include "mozilla/dom/WebTransportLog.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "nsIEventTarget.h"
+#include "nsIOService.h"
+#include "nsIPrincipal.h"
+#include "nsIWebTransport.h"
+#include "nsStreamUtils.h"
+#include "nsIWebTransportStream.h"
+
+using IPCResult = mozilla::ipc::IPCResult;
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS(WebTransportParent, WebTransportSessionEventListener);
+
+using CreateWebTransportPromise =
+ MozPromise<WebTransportReliabilityMode, nsresult, true>;
+WebTransportParent::~WebTransportParent() {
+ LOG(("Destroying WebTransportParent %p", this));
+}
+
+void WebTransportParent::Create(
+ const nsAString& aURL, nsIPrincipal* aPrincipal,
+ const mozilla::Maybe<IPCClientInfo>& aClientInfo, const bool& aDedicated,
+ const bool& aRequireUnreliable, const uint32_t& aCongestionControl,
+ // Sequence<WebTransportHash>* aServerCertHashes,
+ Endpoint<PWebTransportParent>&& aParentEndpoint,
+ std::function<void(std::tuple<const nsresult&, const uint8_t&>)>&&
+ aResolver) {
+ LOG(("Created WebTransportParent %p %s %s %s congestion=%s", this,
+ NS_ConvertUTF16toUTF8(aURL).get(),
+ aDedicated ? "Dedicated" : "AllowPooling",
+ aRequireUnreliable ? "RequireUnreliable" : "",
+ aCongestionControl ==
+ (uint32_t)dom::WebTransportCongestionControl::Throughput
+ ? "ThroughPut"
+ : (aCongestionControl ==
+ (uint32_t)dom::WebTransportCongestionControl::Low_latency
+ ? "Low-Latency"
+ : "Default")));
+
+ if (!StaticPrefs::network_webtransport_enabled()) {
+ aResolver(ResolveType(
+ NS_ERROR_DOM_NOT_ALLOWED_ERR,
+ static_cast<uint8_t>(WebTransportReliabilityMode::Pending)));
+ return;
+ }
+
+ if (!aParentEndpoint.IsValid()) {
+ aResolver(ResolveType(
+ NS_ERROR_INVALID_ARG,
+ static_cast<uint8_t>(WebTransportReliabilityMode::Pending)));
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mozilla::net::gIOService);
+ nsresult rv =
+ mozilla::net::gIOService->NewWebTransport(getter_AddRefs(mWebTransport));
+ if (NS_FAILED(rv)) {
+ aResolver(ResolveType(
+ rv, static_cast<uint8_t>(WebTransportReliabilityMode::Pending)));
+ return;
+ }
+
+ mOwningEventTarget = GetCurrentSerialEventTarget();
+ MOZ_ASSERT(aPrincipal);
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), aURL);
+ if (NS_FAILED(rv)) {
+ aResolver(ResolveType(
+ NS_ERROR_INVALID_ARG,
+ static_cast<uint8_t>(WebTransportReliabilityMode::Pending)));
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
+ "WebTransport AsyncConnect",
+ [self = RefPtr{this}, uri = std::move(uri),
+ principal = RefPtr{aPrincipal},
+ flags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ clientInfo = aClientInfo] {
+ LOG(("WebTransport %p AsyncConnect", self.get()));
+ if (NS_FAILED(self->mWebTransport->AsyncConnectWithClient(
+ uri, principal, flags, self, clientInfo))) {
+ LOG(("AsyncConnect failure; we should get OnSessionClosed"));
+ }
+ });
+
+ // Bind to SocketThread for IPC - connection creation/destruction must
+ // hit MainThread, but keep all other traffic on SocketThread. Note that
+ // we must call aResolver() on this (PBackground) thread.
+ mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ InvokeAsync(mSocketThread, __func__,
+ [parentEndpoint = std::move(aParentEndpoint), runnable = r,
+ resolver = std::move(aResolver), p = RefPtr{this}]() mutable {
+ {
+ MutexAutoLock lock(p->mMutex);
+ p->mResolver = resolver;
+ }
+
+ LOG(("Binding parent endpoint"));
+ if (!parentEndpoint.Bind(p)) {
+ return CreateWebTransportPromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ }
+ // IPC now holds a ref to parent
+ // Send connection to the server via MainThread
+ NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
+
+ return CreateWebTransportPromise::CreateAndResolve(
+ WebTransportReliabilityMode::Supports_unreliable, __func__);
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [p = RefPtr{this}](
+ const CreateWebTransportPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsReject()) {
+ std::function<void(ResolveType)> resolver;
+ {
+ MutexAutoLock lock(p->mMutex);
+ resolver = std::move(p->mResolver);
+ }
+ if (resolver) {
+ resolver(
+ ResolveType(aValue.RejectValue(),
+ static_cast<uint8_t>(
+ WebTransportReliabilityMode::Pending)));
+ }
+ }
+ });
+}
+
+void WebTransportParent::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("ActorDestroy WebTransportParent %d", aWhy));
+}
+
+// We may not receive this response if the child side is destroyed without
+// `Close` or `Shutdown` being explicitly called.
+IPCResult WebTransportParent::RecvClose(const uint32_t& aCode,
+ const nsACString& aReason) {
+ LOG(("Close for %p received, code = %u, reason = %s", this, aCode,
+ PromiseFlatCString(aReason).get()));
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(!mClosed);
+ mClosed.Flip();
+ }
+ mWebTransport->CloseSession(aCode, aReason);
+ Close();
+ return IPC_OK();
+}
+
+class ReceiveStream final : public nsIWebTransportStreamCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIWEBTRANSPORTSTREAMCALLBACK
+
+ ReceiveStream(
+ WebTransportParent::CreateUnidirectionalStreamResolver&& aResolver,
+ std::function<void(uint64_t,
+ WebTransportParent::OnResetOrStopSendingCallback&&)>&&
+ aStreamCallback,
+ nsCOMPtr<nsISerialEventTarget>& aSocketThread)
+ : mUniResolver(aResolver),
+ mStreamCallback(std::move(aStreamCallback)),
+ mSocketThread(aSocketThread) {}
+ ReceiveStream(
+ WebTransportParent::CreateBidirectionalStreamResolver&& aResolver,
+ std::function<void(uint64_t,
+ WebTransportParent::OnResetOrStopSendingCallback&&)>&&
+ aStreamCallback,
+ nsCOMPtr<nsISerialEventTarget>& aSocketThread)
+ : mBiResolver(aResolver),
+ mStreamCallback(std::move(aStreamCallback)),
+ mSocketThread(aSocketThread) {}
+
+ private:
+ ~ReceiveStream() = default;
+ WebTransportParent::CreateUnidirectionalStreamResolver mUniResolver;
+ WebTransportParent::CreateBidirectionalStreamResolver mBiResolver;
+ std::function<void(uint64_t,
+ WebTransportParent::OnResetOrStopSendingCallback&&)>
+ mStreamCallback;
+ nsCOMPtr<nsISerialEventTarget> mSocketThread;
+};
+
+NS_IMPL_ISUPPORTS(ReceiveStream, nsIWebTransportStreamCallback)
+
+// nsIWebTransportStreamCallback:
+NS_IMETHODIMP ReceiveStream::OnBidirectionalStreamReady(
+ nsIWebTransportBidirectionalStream* aStream) {
+ LOG(("Bidirectional stream ready!"));
+ MOZ_ASSERT(mSocketThread->IsOnCurrentThread());
+
+ RefPtr<mozilla::ipc::DataPipeSender> inputsender;
+ RefPtr<mozilla::ipc::DataPipeReceiver> inputreceiver;
+ nsresult rv =
+ NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity,
+ getter_AddRefs(inputsender), getter_AddRefs(inputreceiver));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mBiResolver(rv);
+ return rv;
+ }
+
+ uint64_t id;
+ Unused << aStream->GetStreamId(&id);
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ aStream->GetInputStream(getter_AddRefs(inputStream));
+ MOZ_ASSERT(inputStream);
+ nsCOMPtr<nsISupports> inputCopyContext;
+ rv = NS_AsyncCopy(inputStream, inputsender, mSocketThread,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS, // can we use READSEGMENTS?
+ mozilla::ipc::kDefaultDataPipeCapacity, nullptr, nullptr,
+ true, true, getter_AddRefs(inputCopyContext));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mBiResolver(rv);
+ return rv;
+ }
+
+ RefPtr<mozilla::ipc::DataPipeSender> outputsender;
+ RefPtr<mozilla::ipc::DataPipeReceiver> outputreceiver;
+ rv =
+ NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity,
+ getter_AddRefs(outputsender), getter_AddRefs(outputreceiver));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mBiResolver(rv);
+ return rv;
+ }
+
+ nsCOMPtr<nsIAsyncOutputStream> outputStream;
+ aStream->GetOutputStream(getter_AddRefs(outputStream));
+ MOZ_ASSERT(outputStream);
+ nsCOMPtr<nsISupports> outputCopyContext;
+ rv = NS_AsyncCopy(outputreceiver, outputStream, mSocketThread,
+ NS_ASYNCCOPY_VIA_READSEGMENTS,
+ mozilla::ipc::kDefaultDataPipeCapacity, nullptr, nullptr,
+ true, true, getter_AddRefs(outputCopyContext));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mBiResolver(rv);
+ return rv;
+ }
+
+ LOG(("Returning BidirectionalStream pipe to content"));
+ mBiResolver(BidirectionalStream(id, inputreceiver, outputsender));
+
+ auto onResetOrStopSending =
+ [inputCopyContext(inputCopyContext), outputCopyContext(outputCopyContext),
+ inputsender(inputsender),
+ outputreceiver(outputreceiver)](nsresult aError) {
+ LOG(("onResetOrStopSending err=%x", static_cast<uint32_t>(aError)));
+ NS_CancelAsyncCopy(inputCopyContext, aError);
+ inputsender->CloseWithStatus(aError);
+ NS_CancelAsyncCopy(outputCopyContext, aError);
+ outputreceiver->CloseWithStatus(aError);
+ };
+
+ // Store onResetOrStopSending in WebTransportParent::mStreamCallbackMap and
+ // onResetOrStopSending will be called when a stream receives STOP_SENDING or
+ // RESET.
+ mStreamCallback(id, WebTransportParent::OnResetOrStopSendingCallback(
+ std::move(onResetOrStopSending)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ReceiveStream::OnUnidirectionalStreamReady(nsIWebTransportSendStream* aStream) {
+ LOG(("Unidirectional stream ready!"));
+ // We should be on the Socket Thread
+ MOZ_ASSERT(mSocketThread->IsOnCurrentThread());
+
+ RefPtr<::mozilla::ipc::DataPipeSender> sender;
+ RefPtr<::mozilla::ipc::DataPipeReceiver> receiver;
+ nsresult rv = NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity,
+ getter_AddRefs(sender), getter_AddRefs(receiver));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mUniResolver(rv);
+ return rv;
+ }
+
+ uint64_t id;
+ Unused << aStream->GetStreamId(&id);
+ nsCOMPtr<nsIAsyncOutputStream> outputStream;
+ aStream->GetOutputStream(getter_AddRefs(outputStream));
+ MOZ_ASSERT(outputStream);
+ nsCOMPtr<nsISupports> copyContext;
+ rv = NS_AsyncCopy(receiver, outputStream, mSocketThread,
+ NS_ASYNCCOPY_VIA_READSEGMENTS,
+ mozilla::ipc::kDefaultDataPipeCapacity, nullptr, nullptr,
+ true, true, getter_AddRefs(copyContext));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mUniResolver(rv);
+ return rv;
+ }
+
+ LOG(("Returning UnidirectionalStream pipe to content"));
+ // pass the DataPipeSender to the content process
+ mUniResolver(UnidirectionalStream(id, sender));
+
+ auto onResetOrStopSending = [copyContext(copyContext),
+ receiver(receiver)](nsresult aError) {
+ LOG(("onResetOrStopSending err=%x", static_cast<uint32_t>(aError)));
+ NS_CancelAsyncCopy(copyContext, aError);
+ receiver->CloseWithStatus(aError);
+ };
+
+ // Store onResetOrStopSending in WebTransportParent::mStreamCallbackMap and
+ // onResetOrStopSending will be called when a stream receives STOP_SENDING.
+ mStreamCallback(id, WebTransportParent::OnResetOrStopSendingCallback(
+ std::move(onResetOrStopSending)));
+ return NS_OK;
+}
+
+JS_HAZ_CAN_RUN_SCRIPT NS_IMETHODIMP ReceiveStream::OnError(uint8_t aError) {
+ nsresult rv = aError == nsIWebTransport::INVALID_STATE_ERROR
+ ? NS_ERROR_DOM_INVALID_STATE_ERR
+ : NS_ERROR_FAILURE;
+ LOG(("CreateStream OnError: %u", aError));
+ if (mUniResolver) {
+ mUniResolver(rv);
+ } else if (mBiResolver) {
+ mBiResolver(rv);
+ }
+ return NS_OK;
+}
+
+IPCResult WebTransportParent::RecvCreateUnidirectionalStream(
+ Maybe<int64_t> aSendOrder, CreateUnidirectionalStreamResolver&& aResolver) {
+ LOG(("%s for %p received, useSendOrder=%d, sendOrder=%" PRIi64, __func__,
+ this, aSendOrder.isSome(),
+ aSendOrder.isSome() ? aSendOrder.value() : 0));
+
+ auto streamCb =
+ [self = RefPtr{this}](
+ uint64_t aStreamId,
+ WebTransportParent::OnResetOrStopSendingCallback&& aCallback) {
+ self->mStreamCallbackMap.InsertOrUpdate(aStreamId,
+ std::move(aCallback));
+ };
+ RefPtr<ReceiveStream> callback = new ReceiveStream(
+ std::move(aResolver), std::move(streamCb), mSocketThread);
+ nsresult rv;
+ rv = mWebTransport->CreateOutgoingUnidirectionalStream(callback);
+ if (NS_FAILED(rv)) {
+ callback->OnError(0); // XXX
+ }
+ return IPC_OK();
+}
+
+IPCResult WebTransportParent::RecvCreateBidirectionalStream(
+ Maybe<int64_t> aSendOrder, CreateBidirectionalStreamResolver&& aResolver) {
+ LOG(("%s for %p received, useSendOrder=%d, sendOrder=%" PRIi64, __func__,
+ this, aSendOrder.isSome(),
+ aSendOrder.isSome() ? aSendOrder.value() : 0));
+
+ auto streamCb =
+ [self = RefPtr{this}](
+ uint64_t aStreamId,
+ WebTransportParent::OnResetOrStopSendingCallback&& aCallback) {
+ self->mStreamCallbackMap.InsertOrUpdate(aStreamId,
+ std::move(aCallback));
+ };
+ RefPtr<ReceiveStream> callback = new ReceiveStream(
+ std::move(aResolver), std::move(streamCb), mSocketThread);
+ nsresult rv;
+ rv = mWebTransport->CreateOutgoingBidirectionalStream(callback);
+ if (NS_FAILED(rv)) {
+ callback->OnError(0); // XXX
+ }
+ return IPC_OK();
+}
+
+// We recieve this notification from the WebTransportSessionProxy if session was
+// successfully created at the end of
+// WebTransportSessionProxy::OnStopRequest
+NS_IMETHODIMP
+WebTransportParent::OnSessionReady(uint64_t aSessionId) {
+ MOZ_ASSERT(mOwningEventTarget);
+ MOZ_ASSERT(!mOwningEventTarget->IsOnCurrentThread());
+
+ LOG(("Created web transport session, sessionID = %" PRIu64 ", for %p",
+ aSessionId, this));
+
+ mSessionReady = true;
+
+ // Retarget to socket thread. After this, WebTransportParent and
+ // |mWebTransport| should be only accessed on the socket thread.
+ nsresult rv = mWebTransport->RetargetTo(mSocketThread);
+ if (NS_FAILED(rv)) {
+ mOwningEventTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportParent::OnSessionReady Failed",
+ [self = RefPtr{this}, result = rv] {
+ MutexAutoLock lock(self->mMutex);
+ if (!self->mClosed && self->mResolver) {
+ self->mResolver(ResolveType(
+ result, static_cast<uint8_t>(
+ WebTransportReliabilityMode::Supports_unreliable)));
+ self->mResolver = nullptr;
+ }
+ }));
+ return NS_OK;
+ }
+
+ mOwningEventTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportParent::OnSessionReady", [self = RefPtr{this}] {
+ MutexAutoLock lock(self->mMutex);
+ if (!self->mClosed && self->mResolver) {
+ self->mResolver(ResolveType(
+ NS_OK, static_cast<uint8_t>(
+ WebTransportReliabilityMode::Supports_unreliable)));
+ self->mResolver = nullptr;
+ if (self->mExecuteAfterResolverCallback) {
+ self->mExecuteAfterResolverCallback();
+ self->mExecuteAfterResolverCallback = nullptr;
+ }
+ } else {
+ if (self->mClosed) {
+ LOG(("Session already closed at OnSessionReady %p", self.get()));
+ } else {
+ LOG(("No resolver at OnSessionReady %p", self.get()));
+ }
+ }
+ }));
+
+ return NS_OK;
+}
+
+// We recieve this notification from the WebTransportSessionProxy if session
+// creation was unsuccessful at the end of
+// WebTransportSessionProxy::OnStopRequest
+NS_IMETHODIMP
+WebTransportParent::OnSessionClosed(const uint32_t aErrorCode,
+ const nsACString& aReason) {
+ nsresult rv = NS_OK;
+
+ MOZ_ASSERT(mOwningEventTarget);
+ MOZ_ASSERT(!mOwningEventTarget->IsOnCurrentThread());
+
+ // currently we just know if session was closed gracefully or not.
+ // we need better error propagation from lower-levels of http3
+ // webtransport session and it's subsequent error mapping to DOM.
+ // XXX See Bug 1806834
+ if (!mSessionReady) {
+ LOG(("webtransport %p session creation failed code= %u, reason= %s", this,
+ aErrorCode, PromiseFlatCString(aReason).get()));
+ // we know we haven't gone Ready yet
+ rv = NS_ERROR_FAILURE;
+ mOwningEventTarget->Dispatch(NS_NewRunnableFunction(
+ "WebTransportParent::OnSessionClosed",
+ [self = RefPtr{this}, result = rv] {
+ MutexAutoLock lock(self->mMutex);
+ if (!self->mClosed && self->mResolver) {
+ self->mResolver(ResolveType(
+ result, static_cast<uint8_t>(
+ WebTransportReliabilityMode::Supports_unreliable)));
+ self->mResolver = nullptr;
+ }
+ }));
+ } else {
+ {
+ MutexAutoLock lock(mMutex);
+ if (mResolver) {
+ LOG(("[%p] NotifyRemoteClosed to be called later", this));
+ // NotifyRemoteClosed needs to wait until mResolver is invoked.
+ mExecuteAfterResolverCallback = [self = RefPtr{this}, aErrorCode,
+ reason = nsCString{aReason}]() {
+ self->NotifyRemoteClosed(aErrorCode, reason);
+ };
+ return NS_OK;
+ }
+ }
+ // https://w3c.github.io/webtransport/#web-transport-termination
+ // Step 1: Let cleanly be a boolean representing whether the HTTP/3
+ // stream associated with the CONNECT request that initiated
+ // transport.[[Session]] is in the "Data Recvd" state. [QUIC]
+ // XXX not calculated yet
+ NotifyRemoteClosed(aErrorCode, aReason);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportParent::OnStopSending(uint64_t aStreamId,
+ nsresult aError) {
+ MOZ_ASSERT(mSocketThread->IsOnCurrentThread());
+ LOG(("WebTransportParent::OnStopSending %p stream id=%" PRIx64, this,
+ aStreamId));
+ if (auto entry = mStreamCallbackMap.Lookup(aStreamId)) {
+ entry->OnResetOrStopSending(aError);
+ mStreamCallbackMap.Remove(aStreamId);
+ }
+ if (CanSend()) {
+ Unused << SendOnStreamResetOrStopSending(aStreamId,
+ StopSendingError(aError));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportParent::OnResetReceived(uint64_t aStreamId,
+ nsresult aError) {
+ MOZ_ASSERT(mSocketThread->IsOnCurrentThread());
+ LOG(("WebTransportParent::OnResetReceived %p stream id=%" PRIx64, this,
+ aStreamId));
+ if (auto entry = mStreamCallbackMap.Lookup(aStreamId)) {
+ entry->OnResetOrStopSending(aError);
+ mStreamCallbackMap.Remove(aStreamId);
+ }
+ if (CanSend()) {
+ Unused << SendOnStreamResetOrStopSending(aStreamId, ResetError(aError));
+ }
+ return NS_OK;
+}
+
+void WebTransportParent::NotifyRemoteClosed(uint32_t aErrorCode,
+ const nsACString& aReason) {
+ LOG(("webtransport %p session remote closed code= %u, reason= %s", this,
+ aErrorCode, PromiseFlatCString(aReason).get()));
+ mSocketThread->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [self = RefPtr{this}, aErrorCode, reason = nsCString{aReason}]() {
+ // Tell the content side we were closed by the server
+ Unused << self->SendRemoteClosed(/*XXX*/ true, aErrorCode, reason);
+ // Let the other end shut down the IPC channel after RecvClose()
+ }));
+}
+
+// This method is currently not used by WebTransportSessionProxy to inform of
+// any session related events. All notification is recieved via
+// WebTransportSessionProxy::OnSessionReady and
+// WebTransportSessionProxy::OnSessionClosed methods
+NS_IMETHODIMP
+WebTransportParent::OnSessionReadyInternal(
+ mozilla::net::Http3WebTransportSession* aSession) {
+ Unused << aSession;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportParent::OnIncomingStreamAvailableInternal(
+ mozilla::net::Http3WebTransportStream* aStream) {
+ // XXX implement once DOM WebAPI supports creation of streams
+ Unused << aStream;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportParent::OnIncomingUnidirectionalStreamAvailable(
+ nsIWebTransportReceiveStream* aStream) {
+ // Note: we need to hold a reference to the stream if we want to get stats,
+ // etc
+ LOG(("%p IncomingUnidirectonalStream available", this));
+
+ // We must be on the Socket Thread
+ MOZ_ASSERT(mSocketThread->IsOnCurrentThread());
+
+ RefPtr<DataPipeSender> sender;
+ RefPtr<DataPipeReceiver> receiver;
+ nsresult rv = NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity,
+ getter_AddRefs(sender), getter_AddRefs(receiver));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ aStream->GetInputStream(getter_AddRefs(inputStream));
+ MOZ_ASSERT(inputStream);
+ rv = NS_AsyncCopy(inputStream, sender, mSocketThread,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS,
+ mozilla::ipc::kDefaultDataPipeCapacity);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ LOG(("%p Sending UnidirectionalStream pipe to content", this));
+ // pass the DataPipeReceiver to the content process
+ uint64_t id;
+ Unused << aStream->GetStreamId(&id);
+ Unused << SendIncomingUnidirectionalStream(id, receiver);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportParent::OnIncomingBidirectionalStreamAvailable(
+ nsIWebTransportBidirectionalStream* aStream) {
+ // Note: we need to hold a reference to the stream if we want to get stats,
+ // etc
+ LOG(("%p IncomingBidirectonalStream available", this));
+
+ // We must be on the Socket Thread
+ MOZ_ASSERT(mSocketThread->IsOnCurrentThread());
+
+ RefPtr<DataPipeSender> inputSender;
+ RefPtr<DataPipeReceiver> inputReceiver;
+ nsresult rv =
+ NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity,
+ getter_AddRefs(inputSender), getter_AddRefs(inputReceiver));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> inputStream;
+ aStream->GetInputStream(getter_AddRefs(inputStream));
+ MOZ_ASSERT(inputStream);
+ rv = NS_AsyncCopy(inputStream, inputSender, mSocketThread,
+ NS_ASYNCCOPY_VIA_WRITESEGMENTS,
+ mozilla::ipc::kDefaultDataPipeCapacity);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<DataPipeSender> outputSender;
+ RefPtr<DataPipeReceiver> outputReceiver;
+ rv =
+ NewDataPipe(mozilla::ipc::kDefaultDataPipeCapacity,
+ getter_AddRefs(outputSender), getter_AddRefs(outputReceiver));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIAsyncOutputStream> outputStream;
+ aStream->GetOutputStream(getter_AddRefs(outputStream));
+ MOZ_ASSERT(outputStream);
+ rv = NS_AsyncCopy(outputReceiver, outputStream, mSocketThread,
+ NS_ASYNCCOPY_VIA_READSEGMENTS,
+ mozilla::ipc::kDefaultDataPipeCapacity);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ LOG(("%p Sending BidirectionalStream pipe to content", this));
+ // pass the DataPipeSender to the content process
+ uint64_t id;
+ Unused << aStream->GetStreamId(&id);
+ Unused << SendIncomingBidirectionalStream(id, inputReceiver, outputSender);
+ return NS_OK;
+}
+
+::mozilla::ipc::IPCResult WebTransportParent::RecvGetMaxDatagramSize(
+ GetMaxDatagramSizeResolver&& aResolver) {
+ LOG(("WebTransportParent RecvGetMaxDatagramSize"));
+ MOZ_ASSERT(mSocketThread->IsOnCurrentThread());
+ MOZ_ASSERT(mWebTransport);
+ MOZ_ASSERT(!mMaxDatagramSizeResolver);
+
+ mMaxDatagramSizeResolver = std::move(aResolver);
+ // maximum datagram size for the session is returned from network stack
+ // synchronously via WebTransportSessionEventListener::OnMaxDatagramSize
+ // interface
+ mWebTransport->GetMaxDatagramSize();
+ return IPC_OK();
+}
+
+// The promise sent in this request will be resolved
+// in OnOutgoingDatagramOutCome which is called synchronously from
+// WebTransportSessionProxy::SendDatagram
+::mozilla::ipc::IPCResult WebTransportParent::RecvOutgoingDatagram(
+ nsTArray<uint8_t>&& aData, const TimeStamp& aExpirationTime,
+ OutgoingDatagramResolver&& aResolver) {
+ LOG(("WebTransportParent sending datagram"));
+ MOZ_ASSERT(mSocketThread->IsOnCurrentThread());
+ MOZ_ASSERT(mWebTransport);
+
+ Unused << aExpirationTime;
+
+ MOZ_ASSERT(!mOutgoingDatagramResolver);
+ mOutgoingDatagramResolver = std::move(aResolver);
+ // XXX we need to forward the timestamps to the necko stack
+ // timestamp should be checked in the necko for expiry
+ // See Bug 1818300
+ // currently this calls OnOutgoingDatagramOutCome synchronously
+ // Neqo won't call us back if the id == 0!
+ // We don't use the ID for anything currently; rework of the stack
+ // to implement proper HighWatermark buffering will require
+ // changes here anyways.
+ static uint64_t sDatagramId = 1;
+ LOG_VERBOSE(("Sending datagram %" PRIu64 ", length %zu", sDatagramId,
+ aData.Length()));
+ Unused << mWebTransport->SendDatagram(aData, sDatagramId++);
+
+ return IPC_OK();
+}
+
+NS_IMETHODIMP WebTransportParent::OnDatagramReceived(
+ const nsTArray<uint8_t>& aData) {
+ // We must be on the Socket Thread
+ MOZ_ASSERT(mSocketThread->IsOnCurrentThread());
+
+ LOG(("WebTransportParent received datagram length = %zu", aData.Length()));
+
+ TimeStamp ts = TimeStamp::Now();
+ Unused << SendIncomingDatagram(aData, ts);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportParent::OnDatagramReceivedInternal(
+ nsTArray<uint8_t>&& aData) {
+ // this method is used only for internal notificaiton within necko
+ // we dont expect to receive any notification with on this interface
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebTransportParent::OnOutgoingDatagramOutCome(
+ uint64_t aId, WebTransportSessionEventListener::DatagramOutcome aOutCome) {
+ MOZ_ASSERT(mSocketThread->IsOnCurrentThread());
+ // XXX - do we need better error mappings for failures?
+ nsresult result = NS_ERROR_FAILURE;
+ Unused << result;
+ Unused << aId;
+
+ if (aOutCome == WebTransportSessionEventListener::DatagramOutcome::SENT) {
+ result = NS_OK;
+ LOG(("Sent datagram id= %" PRIu64, aId));
+ } else {
+ LOG(("Didn't send datagram id= %" PRIu64, aId));
+ }
+
+ // This assumes the stack is calling us back synchronously!
+ MOZ_ASSERT(mOutgoingDatagramResolver);
+ mOutgoingDatagramResolver(result);
+ mOutgoingDatagramResolver = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebTransportParent::OnMaxDatagramSize(uint64_t aSize) {
+ LOG(("Max datagram size is %" PRIu64, aSize));
+ MOZ_ASSERT(mSocketThread->IsOnCurrentThread());
+ MOZ_ASSERT(mMaxDatagramSizeResolver);
+ mMaxDatagramSizeResolver(aSize);
+ mMaxDatagramSizeResolver = nullptr;
+ return NS_OK;
+}
+} // namespace mozilla::dom
diff --git a/dom/webtransport/parent/WebTransportParent.h b/dom/webtransport/parent/WebTransportParent.h
new file mode 100644
index 0000000000..1f38bb6b62
--- /dev/null
+++ b/dom/webtransport/parent/WebTransportParent.h
@@ -0,0 +1,100 @@
+/* -*- 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_PARENT_WEBTRANSPORTPARENT_H_
+#define DOM_WEBTRANSPORT_PARENT_WEBTRANSPORTPARENT_H_
+
+#include "ErrorList.h"
+#include "mozilla/dom/ClientIPCTypes.h"
+#include "mozilla/dom/FlippedOnce.h"
+#include "mozilla/dom/PWebTransportParent.h"
+#include "mozilla/ipc/Endpoint.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "nsISupports.h"
+#include "nsIPrincipal.h"
+#include "nsIWebTransport.h"
+#include "nsTHashMap.h"
+
+namespace mozilla::dom {
+
+enum class WebTransportReliabilityMode : uint8_t;
+
+class WebTransportParent : public PWebTransportParent,
+ public WebTransportSessionEventListener {
+ using IPCResult = mozilla::ipc::IPCResult;
+
+ public:
+ WebTransportParent() = default;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_WEBTRANSPORTSESSIONEVENTLISTENER
+
+ void Create(const nsAString& aURL, nsIPrincipal* aPrincipal,
+ const mozilla::Maybe<IPCClientInfo>& aClientInfo,
+ const bool& aDedicated, const bool& aRequireUnreliable,
+ const uint32_t& aCongestionControl,
+ // Sequence<WebTransportHash>* aServerCertHashes,
+ Endpoint<PWebTransportParent>&& aParentEndpoint,
+ std::function<void(std::tuple<const nsresult&, const uint8_t&>)>&&
+ aResolver);
+
+ IPCResult RecvClose(const uint32_t& aCode, const nsACString& aReason);
+
+ IPCResult RecvCreateUnidirectionalStream(
+ Maybe<int64_t> aSendOrder,
+ CreateUnidirectionalStreamResolver&& aResolver);
+ IPCResult RecvCreateBidirectionalStream(
+ Maybe<int64_t> aSendOrder, CreateBidirectionalStreamResolver&& aResolver);
+
+ ::mozilla::ipc::IPCResult RecvOutgoingDatagram(
+ nsTArray<uint8_t>&& aData, const TimeStamp& aExpirationTime,
+ OutgoingDatagramResolver&& aResolver);
+
+ ::mozilla::ipc::IPCResult RecvGetMaxDatagramSize(
+ GetMaxDatagramSizeResolver&& aResolver);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ class OnResetOrStopSendingCallback final {
+ public:
+ explicit OnResetOrStopSendingCallback(
+ std::function<void(nsresult)>&& aCallback)
+ : mCallback(std::move(aCallback)) {}
+ ~OnResetOrStopSendingCallback() = default;
+
+ void OnResetOrStopSending(nsresult aError) { mCallback(aError); }
+
+ private:
+ std::function<void(nsresult)> mCallback;
+ };
+
+ protected:
+ virtual ~WebTransportParent();
+
+ private:
+ void NotifyRemoteClosed(uint32_t aErrorCode, const nsACString& aReason);
+
+ using ResolveType = std::tuple<const nsresult&, const uint8_t&>;
+ nsCOMPtr<nsISerialEventTarget> mSocketThread;
+ Atomic<bool> mSessionReady{false};
+
+ mozilla::Mutex mMutex{"WebTransportParent::mMutex"};
+ std::function<void(ResolveType)> mResolver MOZ_GUARDED_BY(mMutex);
+ // This is needed because mResolver is resolved on the background thread and
+ // OnSessionClosed is called on the socket thread.
+ std::function<void()> mExecuteAfterResolverCallback MOZ_GUARDED_BY(mMutex);
+ OutgoingDatagramResolver mOutgoingDatagramResolver;
+ GetMaxDatagramSizeResolver mMaxDatagramSizeResolver;
+ FlippedOnce<false> mClosed MOZ_GUARDED_BY(mMutex);
+
+ nsCOMPtr<nsIWebTransport> mWebTransport;
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+ nsTHashMap<nsUint64HashKey, OnResetOrStopSendingCallback> mStreamCallbackMap;
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_WEBTRANSPORT_PARENT_WEBTRANSPORTPARENT_H_
diff --git a/dom/webtransport/parent/moz.build b/dom/webtransport/parent/moz.build
new file mode 100644
index 0000000000..f3486cc8c7
--- /dev/null
+++ b/dom/webtransport/parent/moz.build
@@ -0,0 +1,22 @@
+# -*- 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 += [
+ "WebTransportParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "WebTransportParent.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/netwerk/base",
+ "/netwerk/protocol/http",
+]
diff --git a/dom/webtransport/shared/PWebTransport.ipdl b/dom/webtransport/shared/PWebTransport.ipdl
new file mode 100644
index 0000000000..56033ec189
--- /dev/null
+++ b/dom/webtransport/shared/PWebTransport.ipdl
@@ -0,0 +1,96 @@
+/* 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 PBackgroundSharedTypes;
+
+[RefCounted] using class mozilla::ipc::DataPipeReceiver from "mozilla/ipc/DataPipe.h";
+[RefCounted] using class mozilla::ipc::DataPipeSender from "mozilla/ipc/DataPipe.h";
+using mozilla::TimeStamp from "mozilla/TimeStamp.h";
+
+namespace mozilla {
+namespace dom {
+
+struct UnidirectionalStream {
+ uint64_t streamId;
+ nullable DataPipeSender outStream;
+};
+
+union UnidirectionalStreamResponse {
+ nsresult;
+ UnidirectionalStream;
+};
+
+struct BidirectionalStream {
+ uint64_t streamId;
+ nullable DataPipeReceiver inStream;
+ nullable DataPipeSender outStream;
+};
+
+union BidirectionalStreamResponse {
+ nsresult;
+ BidirectionalStream;
+};
+
+struct ResetError {
+ nsresult error;
+};
+
+struct StopSendingError {
+ nsresult error;
+};
+
+union StreamResetOrStopSendingError {
+ ResetError;
+ StopSendingError;
+};
+
+async protocol PWebTransport
+{
+ parent:
+ /**
+ * TODO: documentation
+ */
+ async Close(uint32_t code, nsCString reason);
+ async CreateUnidirectionalStream(int64_t? sendOrder)
+ returns(UnidirectionalStreamResponse response);
+ async CreateBidirectionalStream(int64_t? sendOrder)
+ returns(BidirectionalStreamResponse response);
+
+ /**
+ * IPC for sending webtransport datagrams
+ * @param expirationTime time at which the datagram expires
+ * @param data represents the datagram to be transferred
+ */
+ async OutgoingDatagram(uint8_t[] data, TimeStamp expirationTime)
+ returns(nsresult response);
+
+ /**
+ * Get the maximum supported datagram size from necko stack
+ */
+ async GetMaxDatagramSize()
+ returns(uint64_t maxDatagramSize);
+
+ child:
+
+ async IncomingUnidirectionalStream(uint64_t streamId, nullable DataPipeReceiver receive);
+ async IncomingBidirectionalStream(uint64_t streamId, nullable DataPipeReceiver receive, nullable DataPipeSender send);
+
+ /**
+ * IPC for receiving webtransport datagrams
+ * @param receivedTime is the time at which the parent received the datagram
+ * @param data is the datagram received
+ */
+ async IncomingDatagram(uint8_t[] data, TimeStamp receivedTime);
+
+ async RemoteClosed(bool cleanly, uint32_t code, nsCString reason);
+
+ async OnStreamResetOrStopSending(uint64_t streamId,
+ StreamResetOrStopSendingError error);
+
+ async CloseAll()
+ returns(nsresult rv);
+};
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/webtransport/shared/WebTransportLog.cpp b/dom/webtransport/shared/WebTransportLog.cpp
new file mode 100644
index 0000000000..0193921fec
--- /dev/null
+++ b/dom/webtransport/shared/WebTransportLog.cpp
@@ -0,0 +1,13 @@
+/* -*- 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 "WebTransportLog.h"
+
+namespace mozilla {
+
+LazyLogModule gWebTransportLog("WebTransport");
+
+}
diff --git a/dom/webtransport/shared/WebTransportLog.h b/dom/webtransport/shared/WebTransportLog.h
new file mode 100644
index 0000000000..5bd6da29bd
--- /dev/null
+++ b/dom/webtransport/shared/WebTransportLog.h
@@ -0,0 +1,25 @@
+/* -*- 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_SHARED_WEBTRANSPORTLOG_H_
+#define DOM_WEBTRANSPORT_SHARED_WEBTRANSPORTLOG_H_
+
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+extern LazyLogModule gWebTransportLog;
+}
+
+#define LOG(args) \
+ MOZ_LOG(mozilla::gWebTransportLog, mozilla::LogLevel::Debug, args)
+
+#define LOG_VERBOSE(args) \
+ MOZ_LOG(mozilla::gWebTransportLog, mozilla::LogLevel::Verbose, args)
+
+#define LOG_ENABLED() \
+ MOZ_LOG_TEST(mozilla::gWebTransportLog, mozilla::LogLevel::Debug)
+
+#endif // DOM_WEBTRANSPORT_SHARED_WEBTRANSPORTLOG_H
diff --git a/dom/webtransport/shared/moz.build b/dom/webtransport/shared/moz.build
new file mode 100644
index 0000000000..2992b7dbeb
--- /dev/null
+++ b/dom/webtransport/shared/moz.build
@@ -0,0 +1,21 @@
+# -*- 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 += [
+ "WebTransportLog.h",
+]
+
+UNIFIED_SOURCES += [
+ "WebTransportLog.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+IPDL_SOURCES += [
+ "PWebTransport.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/dom/webtransport/test/moz.build b/dom/webtransport/test/moz.build
new file mode 100644
index 0000000000..ddf7e1f9a9
--- /dev/null
+++ b/dom/webtransport/test/moz.build
@@ -0,0 +1,9 @@
+# -*- 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/.
+
+TEST_DIRS += [
+ "xpcshell",
+]
diff --git a/dom/webtransport/test/xpcshell/moz.build b/dom/webtransport/test/xpcshell/moz.build
new file mode 100644
index 0000000000..3d85532034
--- /dev/null
+++ b/dom/webtransport/test/xpcshell/moz.build
@@ -0,0 +1,9 @@
+# -*- 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/.
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "xpcshell.ini",
+]
diff --git a/dom/webtransport/test/xpcshell/test_close.js b/dom/webtransport/test/xpcshell/test_close.js
new file mode 100644
index 0000000000..5c0aa0017c
--- /dev/null
+++ b/dom/webtransport/test/xpcshell/test_close.js
@@ -0,0 +1,65 @@
+//
+// Simple WebTransport test
+//
+// keep eslint happy until it knows about WebTransport
+/* global WebTransport:false */
+
+"use strict";
+
+var h3Port;
+var host;
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.dns.localDomains");
+});
+
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+function readFile(file) {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fstream.init(file, -1, 0, 0);
+ let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+ fstream.close();
+ return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+ let certFile = do_get_file(filename, false);
+ let pem = readFile(certFile)
+ .replace(/-----BEGIN CERTIFICATE-----/, "")
+ .replace(/-----END CERTIFICATE-----/, "")
+ .replace(/[\r\n]/g, "");
+ certdb.addCertFromBase64(pem, trustString);
+}
+
+add_task(async function setup() {
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+
+ h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ host = "foo.example.com:" + h3Port;
+ do_get_profile();
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ // `../unit/` so that unit_ipc tests can use as well
+ addCertFromFile(
+ certdb,
+ "../../../../netwerk/test/unit/http2-ca.pem",
+ "CTu,u,u"
+ );
+});
+
+add_task(async function test_webtransport_create() {
+ Services.prefs.setBoolPref("network.webtransport.enabled", true);
+
+ const wt = new WebTransport("https://" + host + "/success");
+ await wt.ready;
+ dump("**** ready\n");
+
+ wt.close();
+});
diff --git a/dom/webtransport/test/xpcshell/test_simple_conn.js b/dom/webtransport/test/xpcshell/test_simple_conn.js
new file mode 100644
index 0000000000..e5711c0baf
--- /dev/null
+++ b/dom/webtransport/test/xpcshell/test_simple_conn.js
@@ -0,0 +1,129 @@
+/* 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/. */
+
+// Some basic WebTransport tests for:
+// * session rejection and redirection
+// * session and stream creation
+// * reading from incoming streams (uni)
+//
+// keep eslint happy until it knows about WebTransport
+/* global WebTransport:false */
+
+"use strict";
+
+var h3Port;
+var host;
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.webtransport.enabled");
+ Services.prefs.clearUserPref("network.webtransport.datagrams.enabled");
+ Services.prefs.clearUserPref("network.webtransport.redirect.enabled");
+});
+
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+function readFile(file) {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fstream.init(file, -1, 0, 0);
+ let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+ fstream.close();
+ return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+ let certFile = do_get_file(filename, false);
+ let pem = readFile(certFile)
+ .replace(/-----BEGIN CERTIFICATE-----/, "")
+ .replace(/-----END CERTIFICATE-----/, "")
+ .replace(/[\r\n]/g, "");
+ certdb.addCertFromBase64(pem, trustString);
+}
+
+add_setup(async function setup() {
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ Services.prefs.setBoolPref("network.webtransport.enabled", true);
+ Services.prefs.setBoolPref("network.webtransport.datagrams.enabled", true);
+ Services.prefs.setBoolPref("network.webtransport.redirect.enabled", true);
+
+ h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ host = "foo.example.com:" + h3Port;
+ do_get_profile();
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ // `../unit/` so that unit_ipc tests can use as well
+ addCertFromFile(
+ certdb,
+ "../../../../netwerk/test/unit/http2-ca.pem",
+ "CTu,u,u"
+ );
+});
+
+add_task(async function test_webtransport_create() {
+ const wt = new WebTransport("https://" + host + "/success");
+ await wt.ready;
+ wt.close();
+});
+
+add_task(async function test_redirect_wt() {
+ let wt = new WebTransport("https://" + host + "/redirect");
+ const e1 = await wt.ready.catch(e => e);
+ const e2 = await wt.closed.catch(e => e);
+
+ Assert.equal(e1, "WebTransportError: WebTransport connection rejected");
+ Assert.equal(e2, "WebTransportError: WebTransport connection rejected");
+});
+
+add_task(async function test_reject_wt() {
+ let wt = new WebTransport("https://" + host + "/reject");
+ const e1 = await wt.ready.catch(e => e);
+ const e2 = await wt.closed.catch(e => e);
+ Assert.equal(e1, "WebTransportError: WebTransport connection rejected");
+ Assert.equal(e2, "WebTransportError: WebTransport connection rejected");
+});
+
+add_task(async function test_immediate_server_close() {
+ let wt = new WebTransport("https://" + host + "/closeafter0ms");
+ await wt.ready;
+ await wt.closed;
+ Assert.ok(true);
+});
+
+add_task(async function test_delayed_server_close() {
+ let wt = new WebTransport("https://" + host + "/closeafter100ms");
+ await wt.ready;
+ await wt.closed;
+ Assert.ok(true);
+});
+
+add_task(async function test_wt_stream_create_bidi() {
+ let wt = new WebTransport("https://" + host + "/success");
+ await wt.ready;
+
+ let bds = await wt.createBidirectionalStream();
+ await bds.writable.close();
+ await bds.readable.cancel();
+ Assert.notEqual(bds, null);
+ wt.close();
+});
+
+add_task(async function test_wt_stream_create_uni() {
+ let wt = new WebTransport("https://" + host + "/success");
+ await wt.ready;
+
+ let uds = await wt.createUnidirectionalStream();
+ Assert.notEqual(uds, null);
+ await uds.close();
+ wt.close();
+});
+
+// TODO: datagram test
+// TODO: getStats tests
+// TODO: fix the crash discussed in bug 1822154
diff --git a/dom/webtransport/test/xpcshell/test_simple_stream.js b/dom/webtransport/test/xpcshell/test_simple_stream.js
new file mode 100644
index 0000000000..4efbfe2539
--- /dev/null
+++ b/dom/webtransport/test/xpcshell/test_simple_stream.js
@@ -0,0 +1,168 @@
+/* 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/. */
+
+// keep eslint happy until it knows about WebTransport
+/* global WebTransport:false */
+/* global TextDecoderStream:false */
+
+// Using multiple files to reduce racing
+// This file tests reading/writing to incoming/outgoing streams (uni & bidi)
+//
+"use strict";
+
+var h3Port;
+var host;
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("network.dns.localDomains");
+ Services.prefs.clearUserPref("network.webtransport.enabled");
+ Services.prefs.clearUserPref("network.webtransport.datagrams.enabled");
+ Services.prefs.clearUserPref("network.webtransport.redirect.enabled");
+});
+
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+function readFile(file) {
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fstream.init(file, -1, 0, 0);
+ let data = NetUtil.readInputStreamToString(fstream, fstream.available());
+ fstream.close();
+ return data;
+}
+
+function addCertFromFile(certdb, filename, trustString) {
+ let certFile = do_get_file(filename, false);
+ let pem = readFile(certFile)
+ .replace(/-----BEGIN CERTIFICATE-----/, "")
+ .replace(/-----END CERTIFICATE-----/, "")
+ .replace(/[\r\n]/g, "");
+ certdb.addCertFromBase64(pem, trustString);
+}
+
+add_setup(async function setup() {
+ Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com");
+ Services.prefs.setBoolPref("network.webtransport.enabled", true);
+ Services.prefs.setBoolPref("network.webtransport.datagrams.enabled", true);
+ Services.prefs.setBoolPref("network.webtransport.redirect.enabled", true);
+
+ h3Port = Services.env.get("MOZHTTP3_PORT");
+ Assert.notEqual(h3Port, null);
+ Assert.notEqual(h3Port, "");
+ host = "foo.example.com:" + h3Port;
+ do_get_profile();
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ // `../unit/` so that unit_ipc tests can use as well
+ addCertFromFile(
+ certdb,
+ "../../../../netwerk/test/unit/http2-ca.pem",
+ "CTu,u,u"
+ );
+});
+
+// Read all chunks from |readable_stream|, decode chunks to a utf-8 string, then
+// return the string. (borrowed from wpt tests)
+async function read_stream_as_string(readable_stream) {
+ const decoder = new TextDecoderStream();
+ const decode_stream = readable_stream.pipeThrough(decoder);
+ const reader = decode_stream.getReader();
+
+ let chunks = "";
+ while (true) {
+ const { value: chunk, done } = await reader.read();
+ if (done) {
+ break;
+ }
+ chunks += chunk;
+ }
+ reader.releaseLock();
+ return chunks;
+}
+
+add_task(async function test_wt_incoming_unidi_stream() {
+ // trigger stream creation server side and default echo
+ let wt = new WebTransport(
+ "https://" + host + "/create_unidi_stream_and_hello"
+ );
+ await wt.ready;
+
+ const streams = await wt.incomingUnidirectionalStreams;
+ const stream_reader = streams.getReader();
+ const { value: recv_stream } = await stream_reader.read();
+ let str = await read_stream_as_string(recv_stream);
+ stream_reader.releaseLock();
+ Assert.equal(str, "qwerty");
+
+ wt.close();
+});
+
+add_task(async function test_wt_incoming_and_outgoing_unidi_stream() {
+ // create the client's incoming stream from the server side
+ // we need it to listen to the echo back
+ let wt = new WebTransport("https://" + host + "/create_unidi_stream");
+ await wt.ready;
+
+ // send hello to server
+ let expected = "uni_hello";
+ let writableStream = await wt.createUnidirectionalStream(); // only triggers NewStream OnWrite
+ let wsDefaultWriter = writableStream.getWriter();
+ await wsDefaultWriter.ready;
+ let data = new TextEncoder().encode(expected);
+ await wsDefaultWriter.write(data); // triggers Http3ServerEvent::Data
+ await wsDefaultWriter.close();
+ wsDefaultWriter.releaseLock();
+
+ // read the echo
+ const streams = await wt.incomingUnidirectionalStreams;
+ const stream_reader = streams.getReader();
+ const { value: recv_stream } = await stream_reader.read();
+ let str = await read_stream_as_string(recv_stream);
+ Assert.equal(str, expected);
+ stream_reader.releaseLock();
+ await recv_stream.closed;
+
+ wt.close();
+});
+
+add_task(async function test_wt_outgoing_bidi_stream() {
+ let wt = new WebTransport("https://" + host + "/success");
+ await wt.ready;
+
+ // write to server
+ let wtbds = await wt.createBidirectionalStream();
+ let writableStream = wtbds.writable;
+ let wsDefaultWriter = writableStream.getWriter();
+ await wsDefaultWriter.ready;
+ let expected = "xyzhello";
+ let data = new TextEncoder().encode(expected);
+ await wsDefaultWriter.write(data);
+ await wsDefaultWriter.close();
+ wsDefaultWriter.releaseLock();
+
+ // string goes through server and is echoed back here
+ const str = await read_stream_as_string(wtbds.readable);
+ Assert.equal(str, expected);
+
+ wt.close();
+});
+
+add_task(async function test_wt_incoming_bidi_stream() {
+ let wt = new WebTransport(
+ "https://" + host + "/create_bidi_stream_and_hello"
+ );
+ // await wt.ready; // causes occasional hang on release --verify
+
+ const stream_reader = wt.incomingBidirectionalStreams.getReader();
+ const { value: bidi_stream } = await stream_reader.read();
+ stream_reader.releaseLock();
+
+ const str = await read_stream_as_string(bidi_stream.readable);
+ Assert.equal(str, "asdfg");
+
+ wt.close();
+});
diff --git a/dom/webtransport/test/xpcshell/xpcshell.ini b/dom/webtransport/test/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..97fe9a39e4
--- /dev/null
+++ b/dom/webtransport/test/xpcshell/xpcshell.ini
@@ -0,0 +1,29 @@
+# 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/.
+
+[DEFAULT]
+
+# XXX figure out why android doesn't get MOZHTTP3_PORT
+[test_close.js]
+skip-if =
+ os == 'android' || socketprocess_networking
+ os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1807925
+[test_simple_conn.js]
+skip-if =
+ (!fission && debug)
+ (verify && tsan)
+ (verify && debug)
+ os == 'android'
+ socketprocess_networking
+ os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1807925
+run-sequentially = http3server
+[test_simple_stream.js]
+skip-if =
+ (!fission && debug)
+ (verify && tsan)
+ (verify && debug)
+ os == 'android'
+ socketprocess_networking
+ os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1807925
+run-sequentially = http3server