From 11452663c19fcae454e756568c00510d6e67e406 Mon Sep 17 00:00:00 2001 From: Randell Jesup Date: Fri, 11 Sep 2026 13:35:49 +0000 Subject: [PATCH] Bug 2007202: Make a scriptable interface for WebTransport stats data for xpcshell tests r=necko-reviewers,valentin Differential Revision: https://phabricator.services.mozilla.com/D324954 --- .../parent/WebTransportParent.cpp | 24 ++++--- .../http/Http2WebTransportSession.cpp | 4 +- .../http/Http3WebTransportSession.cpp | 16 +++-- .../protocol/http/WebTransportSessionBase.cpp | 8 +++ .../protocol/http/WebTransportSessionBase.h | 23 +++++++ .../webtransport/WebTransportSessionProxy.cpp | 67 +++++++++++++------ .../protocol/webtransport/nsIWebTransport.idl | 18 +++-- netwerk/test/gtest/TestHttp2WebTransport.cpp | 4 +- 8 files changed, 122 insertions(+), 42 deletions(-) diff --git a/dom/webtransport/parent/WebTransportParent.cpp b/dom/webtransport/parent/WebTransportParent.cpp index 433f7ac6b6e0..e5aaf4961a11 100644 --- a/dom/webtransport/parent/WebTransportParent.cpp +++ b/dom/webtransport/parent/WebTransportParent.cpp @@ -675,12 +675,16 @@ NS_IMETHODIMP WebTransportParent::OnSessionClosed(const bool aCleanly, const uint32_t aErrorCode, const nsACString& aReason, - WebTransportStatsData* aStats) { + nsIWebTransportSessionStats* aStats) { nsresult rv = NS_OK; MOZ_ASSERT(mOwningEventTarget); MOZ_ASSERT(!mOwningEventTarget->IsOnCurrentThread()); + WebTransportStatsData* rawStats = nullptr; + MOZ_ALWAYS_SUCCEEDS(aStats->GetRawStats(&rawStats)); + MOZ_ASSERT(rawStats); + // 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. @@ -714,7 +718,7 @@ WebTransportParent::OnSessionClosed(const bool aCleanly, LOG(("[%p] NotifyRemoteClosed to be called later", this)); // NotifyRemoteClosed needs to wait until mResolver is invoked. mExecuteAfterResolverCallback = [self = RefPtr{this}, aCleanly, - aErrorCode, statsData = *aStats, + aErrorCode, statsData = *rawStats, reason = nsCString{aReason}]() { self->NotifyRemoteClosed(aCleanly, aErrorCode, reason, statsData); }; @@ -726,7 +730,7 @@ WebTransportParent::OnSessionClosed(const bool aCleanly, // stream associated with the CONNECT request that initiated // transport.[[Session]] is in the "Data Recvd" state. [QUIC] // XXX not calculated yet - NotifyRemoteClosed(aCleanly, aErrorCode, aReason, *aStats); + NotifyRemoteClosed(aCleanly, aErrorCode, aReason, *rawStats); } return NS_OK; @@ -998,15 +1002,19 @@ NS_IMETHODIMP WebTransportParent::OnMaxDatagramSize(uint64_t aSize) { // duration of this synchronous call, or null if stats could not be gathered. // We copy the data into the resolver before returning. NS_IMETHODIMP WebTransportParent::OnStatsAvailable( - WebTransportStatsData* aStats) { + nsIWebTransportSessionStats* aStats) { MOZ_ASSERT(mSocketThread->IsOnCurrentThread()); + WebTransportStatsData* rawStats = nullptr; if (aStats) { + MOZ_ALWAYS_SUCCEEDS(aStats->GetRawStats(&rawStats)); + } + if (rawStats) { LOG( ("Stats available: bytesSent=%llu, bytesReceived=%llu, minRtt=%f, " "smoothedRtt=%f", - (unsigned long long)aStats->bytesSent(), - (unsigned long long)aStats->bytesReceived(), aStats->minRtt(), - aStats->smoothedRtt())); + (unsigned long long)rawStats->bytesSent(), + (unsigned long long)rawStats->bytesReceived(), rawStats->minRtt(), + rawStats->smoothedRtt())); } else { LOG(("Stats unavailable")); } @@ -1018,7 +1026,7 @@ NS_IMETHODIMP WebTransportParent::OnStatsAvailable( return NS_OK; } - ResolvePendingGetStats(aStats ? Some(*aStats) : Nothing()); + ResolvePendingGetStats(rawStats ? Some(*rawStats) : Nothing()); return NS_OK; } diff --git a/netwerk/protocol/http/Http2WebTransportSession.cpp b/netwerk/protocol/http/Http2WebTransportSession.cpp index 92fe4a941a29..0a06121e8749 100644 --- a/netwerk/protocol/http/Http2WebTransportSession.cpp +++ b/netwerk/protocol/http/Http2WebTransportSession.cpp @@ -89,7 +89,9 @@ void Http2WebTransportSessionImpl::GetStats() { // so getStats()'s Promise settles instead of staying pending forever. if (RefPtr listener = GetListener()) { mozilla::dom::WebTransportStatsData stats; - listener->OnStatsAvailable(&stats); + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(stats); + listener->OnStatsAvailable(statsWrapper); } } diff --git a/netwerk/protocol/http/Http3WebTransportSession.cpp b/netwerk/protocol/http/Http3WebTransportSession.cpp index c177a729d0ff..3aa80aca3b80 100644 --- a/netwerk/protocol/http/Http3WebTransportSession.cpp +++ b/netwerk/protocol/http/Http3WebTransportSession.cpp @@ -338,7 +338,9 @@ void Http3WebTransportSession::Close(nsresult aResult) { LOG(("Http3WebTransportSession::Close %p", this)); if (RefPtr listener = TakeListener()) { mozilla::dom::WebTransportStatsData emptyStats; - listener->OnSessionClosed(NS_SUCCEEDED(aResult), 0, ""_ns, &emptyStats); + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(emptyStats); + listener->OnSessionClosed(NS_SUCCEEDED(aResult), 0, ""_ns, statsWrapper); } if (mTransaction) { mTransaction->Close(aResult); @@ -379,7 +381,9 @@ void Http3WebTransportSession::OnSessionClosed(bool aCleanly, uint32_t aStatus, } if (RefPtr listener = TakeListener()) { // Use cached stats captured at close time - listener->OnSessionClosed(aCleanly, aStatus, aReason, &mCachedStats); + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(mCachedStats); + listener->OnSessionClosed(aCleanly, aStatus, aReason, statsWrapper); } mRecvState = RECV_DONE; mSendState = SEND_DONE; @@ -410,7 +414,9 @@ void Http3WebTransportSession::CloseSession(uint32_t aStatus, // Notify listener with cached stats before clearing it RefPtr listener = GetListener(); if (listener) { - listener->OnSessionClosed(true, mStatus, mReason, &mCachedStats); + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(mCachedStats); + listener->OnSessionClosed(true, mStatus, mReason, statsWrapper); } mSession->ConnectSlowConsumer(this); @@ -624,7 +630,9 @@ void Http3WebTransportSession::GetStats() { return; } - listener->OnStatsAvailable(&stats); + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(stats); + listener->OnStatsAvailable(statsWrapper); } void Http3WebTransportSession::GetNegotiatedProtocol(nsACString& aProtocol) { diff --git a/netwerk/protocol/http/WebTransportSessionBase.cpp b/netwerk/protocol/http/WebTransportSessionBase.cpp index d3647292899c..88c8ae932c38 100644 --- a/netwerk/protocol/http/WebTransportSessionBase.cpp +++ b/netwerk/protocol/http/WebTransportSessionBase.cpp @@ -8,6 +8,14 @@ namespace mozilla::net { +NS_IMPL_ISUPPORTS(WebTransportSessionStatsWrapper, nsIWebTransportSessionStats) + +NS_IMETHODIMP WebTransportSessionStatsWrapper::GetRawStats( + mozilla::dom::WebTransportStatsData** aStats) { + *aStats = &mStats; + return NS_OK; +} + void WebTransportSessionBase::SetWebTransportSessionEventListener( WebTransportSessionEventListener* listener) { MutexAutoLock lock(mListenerLock); diff --git a/netwerk/protocol/http/WebTransportSessionBase.h b/netwerk/protocol/http/WebTransportSessionBase.h index f693daa15826..f1e4a0219a4c 100644 --- a/netwerk/protocol/http/WebTransportSessionBase.h +++ b/netwerk/protocol/http/WebTransportSessionBase.h @@ -8,7 +8,9 @@ #include #include "mozilla/Mutex.h" +#include "mozilla/dom/PWebTransport.h" #include "nsISupportsImpl.h" +#include "nsIWebTransport.h" #include "nsTArray.h" class WebTransportSessionEventListener; @@ -18,6 +20,27 @@ namespace mozilla::net { class WebTransportStreamBase; class Http3WebTransportSession; +// Wraps a WebTransportStatsData snapshot so it can be passed through the +// scriptable WebTransportSessionEventListener::OnSessionClosed/ +// OnStatsAvailable methods; the raw data itself is only accessible to +// native (C++) consumers via the noscript GetRawStats(). +class WebTransportSessionStatsWrapper final + : public nsIWebTransportSessionStats { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit WebTransportSessionStatsWrapper( + const mozilla::dom::WebTransportStatsData& aStats) + : mStats(aStats) {} + + NS_IMETHOD GetRawStats(mozilla::dom::WebTransportStatsData** aStats) override; + + private: + ~WebTransportSessionStatsWrapper() = default; + + mozilla::dom::WebTransportStatsData mStats; +}; + class WebTransportSessionBase { public: NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING diff --git a/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp index 92aad82e6113..c356f01ca2f9 100644 --- a/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp +++ b/netwerk/protocol/webtransport/WebTransportSessionProxy.cpp @@ -102,8 +102,11 @@ nsresult WebTransportSessionProxy::AsyncConnectWithClient( auto cleanup = MakeScopeExit([self = RefPtr(this)] { MutexAutoLock lock(self->mMutex); mozilla::dom::WebTransportStatsData stats; // Zero-initialized - self->mListener->OnSessionClosed(false, 0, ""_ns, - &stats); // TODO: find a better error. + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(stats); + self->mListener->OnSessionClosed( + false, 0, ""_ns, + statsWrapper); // TODO: find a better error. self->mChannel = nullptr; self->mListener = nullptr; self->ChangeState(WebTransportSessionProxyState::DONE); @@ -385,7 +388,9 @@ WebTransportSessionProxy::GetStats() { // If we're using cached stats, call OnStatsAvailable directly if (useCachedStats) { - return OnStatsAvailable(&cachedStats); + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(cachedStats); + return OnStatsAvailable(statsWrapper); } if (!OnSocketThread()) { @@ -868,7 +873,9 @@ WebTransportSessionProxy::OnStartRequest(nsIRequest* aRequest) { } if (listener) { mozilla::dom::WebTransportStatsData stats; // Zero-initialized - listener->OnSessionClosed(false, closeStatus, reason, &stats); + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(stats); + listener->OnSessionClosed(false, closeStatus, reason, statsWrapper); } return NS_OK; } @@ -954,9 +961,12 @@ WebTransportSessionProxy::OnStopRequest(nsIRequest* aRequest, listener->OnSessionReady(sessionId); } else { mozilla::dom::WebTransportStatsData stats; // Zero-initialized - listener->OnSessionClosed(false, closeStatus, reason, - &stats); // TODO: find a better error. - // Currently error code 0 is used. + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(stats); + listener->OnSessionClosed( + false, closeStatus, reason, + statsWrapper); // TODO: find a better error. + // Currently error code 0 is used. } } @@ -1208,9 +1218,9 @@ WebTransportSessionProxy::OnSessionReady(uint64_t ready) { // duration of this synchronous call. We copy the data immediately to cache it // and when capturing it in lambdas for deferred execution. NS_IMETHODIMP -WebTransportSessionProxy::OnSessionClosed( - bool aCleanly, uint32_t aStatus, const nsACString& aReason, - mozilla::dom::WebTransportStatsData* aStats) { +WebTransportSessionProxy::OnSessionClosed(bool aCleanly, uint32_t aStatus, + const nsACString& aReason, + nsIWebTransportSessionStats* aStats) { MOZ_ASSERT(OnSocketThread(), "not on socket thread"); MutexAutoLock lock(mMutex); LOG( @@ -1218,10 +1228,14 @@ WebTransportSessionProxy::OnSessionClosed( "mStopRequestCalled=%d", this, mState, mStopRequestCalled)); + mozilla::dom::WebTransportStatsData* rawStats = nullptr; + MOZ_ALWAYS_SUCCEEDS(aStats->GetRawStats(&rawStats)); + MOZ_ASSERT(rawStats); + // Cache stats (spec requirement for GetStats after close) if (!mHasCachedStats) { mHasCachedStats = true; - mCachedStats = *aStats; + mCachedStats = *rawStats; } // Since OnSessionReady on the listener is called on the main thread, @@ -1232,8 +1246,10 @@ WebTransportSessionProxy::OnSessionClosed( mPendingEvents.AppendElement([self = RefPtr{this}, status(aStatus), closeReason(std::move(closeReason)), cleanly(aCleanly), - stats = *aStats]() mutable { - (void)self->OnSessionClosed(cleanly, status, closeReason, &stats); + stats = *rawStats]() mutable { + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(stats); + (void)self->OnSessionClosed(cleanly, status, closeReason, statsWrapper); }); return NS_OK; } @@ -1305,8 +1321,9 @@ void WebTransportSessionProxy::OnStatsAvailableInternal( return; } if (aStats) { - mozilla::dom::WebTransportStatsData stats = *aStats; - listener->OnStatsAvailable(&stats); + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(*aStats); + listener->OnStatsAvailable(statsWrapper); } else { listener->OnStatsAvailable(nullptr); } @@ -1314,20 +1331,24 @@ void WebTransportSessionProxy::OnStatsAvailableInternal( NS_IMETHODIMP WebTransportSessionProxy::OnStatsAvailable( - mozilla::dom::WebTransportStatsData* aStats) { + nsIWebTransportSessionStats* aStats) { MOZ_ASSERT(OnSocketThread(), "not on socket thread"); + mozilla::dom::WebTransportStatsData* rawStats = nullptr; if (aStats) { + MOZ_ALWAYS_SUCCEEDS(aStats->GetRawStats(&rawStats)); + } + if (rawStats) { LOG(("WebTransportSessionProxy::OnStatsAvailable %p - bytesSent=%" PRIu64 ", bytesReceived=%" PRIu64 ", minRtt=%f, smoothedRtt=%f", - this, aStats->bytesSent(), aStats->bytesReceived(), aStats->minRtt(), - aStats->smoothedRtt())); + this, rawStats->bytesSent(), rawStats->bytesReceived(), + rawStats->minRtt(), rawStats->smoothedRtt())); } else { LOG(("WebTransportSessionProxy::OnStatsAvailable %p - stats unavailable", this)); } Maybe stats = - aStats ? Some(*aStats) : Nothing(); + rawStats ? Some(*rawStats) : Nothing(); { MutexAutoLock lock(mMutex); @@ -1336,9 +1357,9 @@ WebTransportSessionProxy::OnStatsAvailable( // the socket thread here. MOZ_ASSERT(mTarget->IsOnCurrentThread()); // Cache stats for use after connection is closed (spec requirement). - if (aStats) { + if (rawStats) { mHasCachedStats = true; - mCachedStats = *aStats; + mCachedStats = *rawStats; } if (!mTarget->IsOnCurrentThread()) { return mTarget->Dispatch( @@ -1402,7 +1423,9 @@ void WebTransportSessionProxy::CallOnSessionClosed() MOZ_REQUIRES(mMutex) { if (listener) { // Don't invoke the callback under the lock. MutexAutoUnlock unlock(mMutex); - listener->OnSessionClosed(cleanly, closeStatus, reason, &stats); + nsCOMPtr statsWrapper = + new WebTransportSessionStatsWrapper(stats); + listener->OnSessionClosed(cleanly, closeStatus, reason, statsWrapper); } } diff --git a/netwerk/protocol/webtransport/nsIWebTransport.idl b/netwerk/protocol/webtransport/nsIWebTransport.idl index 07eac89da588..d62b76d06c67 100644 --- a/netwerk/protocol/webtransport/nsIWebTransport.idl +++ b/netwerk/protocol/webtransport/nsIWebTransport.idl @@ -106,16 +106,24 @@ interface nsIWebTransport : nsISupports { [noscript] void retargetTo(in nsIEventTarget aTarget); }; +// Wraps a WebTransportStatsData snapshot so it can be passed through a +// scriptable interface. The raw data is only accessible to native (C++) +// consumers, since WebTransportStatsData itself is not scriptable. +[builtinclass, scriptable, uuid(bd8549d7-002f-4032-96a4-19430399d081)] +interface nsIWebTransportSessionStats : nsISupports { + [noscript] readonly attribute WebTransportStatsDataPtr rawStats; +}; + // Events related to a WebTransport session. [scriptable, uuid(0e3cb269-f318-43c8-959e-897f57894b71)] interface WebTransportSessionEventListener : nsISupports { // This is used to let the consumer of nsIWebTransport know that the // underlying WebTransportSession object is ready to use. void onSessionReady(in uint64_t aSessionId); - [noscript] void onSessionClosed(in boolean aCleanly, - in uint32_t aErrorCode, - in ACString aReason, - in WebTransportStatsDataPtr aStats); + void onSessionClosed(in boolean aCleanly, + in uint32_t aErrorCode, + in ACString aReason, + in nsIWebTransportSessionStats aStats); void onDraining(); // When a new stream has been received. @@ -141,7 +149,7 @@ interface WebTransportSessionEventListener : nsISupports { in WebTransportSessionEventListener_DatagramOutcome aOutCome); // aStats is null if stats could not be gathered for this session. - [noscript] void onStatsAvailable(in WebTransportStatsDataPtr aStats); + void onStatsAvailable(in nsIWebTransportSessionStats aStats); }; [uuid(8fb30aa9-5163-4eb3-81f3-371e1ccb5b0e)] diff --git a/netwerk/test/gtest/TestHttp2WebTransport.cpp b/netwerk/test/gtest/TestHttp2WebTransport.cpp index 8ab0d40e4788..0cad015bb3fb 100644 --- a/netwerk/test/gtest/TestHttp2WebTransport.cpp +++ b/netwerk/test/gtest/TestHttp2WebTransport.cpp @@ -251,7 +251,7 @@ MockWebTransportSessionEventListener::OnSessionReady(uint64_t ready) { NS_IMETHODIMP MockWebTransportSessionEventListener::OnSessionClosed( bool aCleanly, uint32_t aStatus, const nsACString& aReason, - mozilla::dom::WebTransportStatsData* aStats) { + nsIWebTransportSessionStats* aStats) { return NS_OK; } @@ -293,7 +293,7 @@ NS_IMETHODIMP MockWebTransportSessionEventListener::OnResetReceived( } NS_IMETHODIMP MockWebTransportSessionEventListener::OnStatsAvailable( - mozilla::dom::WebTransportStatsData* aStats) { + nsIWebTransportSessionStats* aStats) { return NS_OK; }