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
This commit is contained in:
Randell Jesup
2026-09-11 13:44:20 +00:00
committed by rjesup@mozilla.com
parent dd81880e29
commit 11452663c1
8 changed files with 122 additions and 42 deletions
+16 -8
View File
@@ -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;
}
@@ -89,7 +89,9 @@ void Http2WebTransportSessionImpl::GetStats() {
// so getStats()'s Promise settles instead of staying pending forever.
if (RefPtr<WebTransportSessionEventListener> listener = GetListener()) {
mozilla::dom::WebTransportStatsData stats;
listener->OnStatsAvailable(&stats);
nsCOMPtr<nsIWebTransportSessionStats> statsWrapper =
new WebTransportSessionStatsWrapper(stats);
listener->OnStatsAvailable(statsWrapper);
}
}
@@ -338,7 +338,9 @@ void Http3WebTransportSession::Close(nsresult aResult) {
LOG(("Http3WebTransportSession::Close %p", this));
if (RefPtr<WebTransportSessionEventListener> listener = TakeListener()) {
mozilla::dom::WebTransportStatsData emptyStats;
listener->OnSessionClosed(NS_SUCCEEDED(aResult), 0, ""_ns, &emptyStats);
nsCOMPtr<nsIWebTransportSessionStats> 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<WebTransportSessionEventListener> listener = TakeListener()) {
// Use cached stats captured at close time
listener->OnSessionClosed(aCleanly, aStatus, aReason, &mCachedStats);
nsCOMPtr<nsIWebTransportSessionStats> 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<WebTransportSessionEventListener> listener = GetListener();
if (listener) {
listener->OnSessionClosed(true, mStatus, mReason, &mCachedStats);
nsCOMPtr<nsIWebTransportSessionStats> 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<nsIWebTransportSessionStats> statsWrapper =
new WebTransportSessionStatsWrapper(stats);
listener->OnStatsAvailable(statsWrapper);
}
void Http3WebTransportSession::GetNegotiatedProtocol(nsACString& aProtocol) {
@@ -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);
@@ -8,7 +8,9 @@
#include <functional>
#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
@@ -102,8 +102,11 @@ nsresult WebTransportSessionProxy::AsyncConnectWithClient(
auto cleanup = MakeScopeExit([self = RefPtr<WebTransportSessionProxy>(this)] {
MutexAutoLock lock(self->mMutex);
mozilla::dom::WebTransportStatsData stats; // Zero-initialized
self->mListener->OnSessionClosed(false, 0, ""_ns,
&stats); // TODO: find a better error.
nsCOMPtr<nsIWebTransportSessionStats> 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<nsIWebTransportSessionStats> 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<nsIWebTransportSessionStats> 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<nsIWebTransportSessionStats> 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<nsIWebTransportSessionStats> 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<nsIWebTransportSessionStats> 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<mozilla::dom::WebTransportStatsData> 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<nsIWebTransportSessionStats> statsWrapper =
new WebTransportSessionStatsWrapper(stats);
listener->OnSessionClosed(cleanly, closeStatus, reason, statsWrapper);
}
}
@@ -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)]
+2 -2
View File
@@ -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;
}