tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit c254549b06daf5f008e96f1628c105a48ba8676a
parent f733bcd005ace3843b9ccae7a93f836db583c545
Author: Dan Baker <dbaker@mozilla.com>
Date:   Mon, 27 Oct 2025 12:23:10 -0600

Bug 1995393 - Vendor libwebrtc from 59f9ca8c73

Upstream commit: https://webrtc.googlesource.com/src/+/59f9ca8c7344a926a5cdb699b00ecb2caad3a4a1
    Make inbound-rtp stats object lifetime spec-compliant (behind flag).

    When field trial "WebRTC-RTP-Lifetime" is enabled, the creation of the
    inbound-rtp stats object is delayed until the first packet is received.
    - This aligns with spec and Firefox behavior.

    To aid testing, PeerConnectionTestWrapper has new negotiation methods
    added to give the test more control without having to write a lot of
    boilerplate code.

    (A separate CL will deal with outbound-rtp which, while the plan is
    they continue to be created before first packet is sent, should be
    delayed until after O/A has completed.)

    Bug: chromium:406585888
    Change-Id: Ibac2128e80e0153659b68cc0f00869e5d1f27a69
    Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/405740
    Commit-Queue: Henrik Boström <hbos@webrtc.org>
    Reviewed-by: Harald Alvestrand <hta@webrtc.org>
    Cr-Commit-Position: refs/heads/main@{#45428}

Diffstat:
Mthird_party/libwebrtc/README.mozilla.last-vendor | 4++--
Mthird_party/libwebrtc/experiments/field_trials.py | 3+++
Mthird_party/libwebrtc/moz-patch-stack/s0102.patch | 2+-
Mthird_party/libwebrtc/pc/BUILD.gn | 1+
Mthird_party/libwebrtc/pc/rtc_stats_collector.cc | 19+++++++++++++++----
Mthird_party/libwebrtc/pc/rtc_stats_collector.h | 2++
Mthird_party/libwebrtc/pc/rtc_stats_integrationtest.cc | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mthird_party/libwebrtc/pc/test/peer_connection_test_wrapper.cc | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mthird_party/libwebrtc/pc/test/peer_connection_test_wrapper.h | 21+++++++++++++++++++++
9 files changed, 231 insertions(+), 17 deletions(-)

diff --git a/third_party/libwebrtc/README.mozilla.last-vendor b/third_party/libwebrtc/README.mozilla.last-vendor @@ -1,4 +1,4 @@ # ./mach python dom/media/webrtc/third_party_build/vendor-libwebrtc.py --from-local /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc --commit mozpatches libwebrtc -libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-10-27T18:19:48.086610+00:00. +libwebrtc updated from /Users/danielbaker/elm/.moz-fast-forward/moz-libwebrtc commit mozpatches on 2025-10-27T18:22:53.208935+00:00. # base of lastest vendoring -a3fbc13447 +59f9ca8c73 diff --git a/third_party/libwebrtc/experiments/field_trials.py b/third_party/libwebrtc/experiments/field_trials.py @@ -221,6 +221,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([ FieldTrial('WebRTC-H265-QualityScaling', 402154973, date(2026, 1, 1)), + FieldTrial('WebRTC-RTP-Lifetime', + 440975167, + date(2026, 1, 1)), # keep-sorted end ]) # yapf: disable diff --git a/third_party/libwebrtc/moz-patch-stack/s0102.patch b/third_party/libwebrtc/moz-patch-stack/s0102.patch @@ -601,7 +601,7 @@ index b7561e53b6..fe7eb57423 100644 import("../../webrtc.gni") diff --git a/pc/BUILD.gn b/pc/BUILD.gn -index f48ba6e784..1122d4b66e 100644 +index 47e5c4d2ca..10fdbd3c1c 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -30,8 +30,8 @@ diff --git a/third_party/libwebrtc/pc/BUILD.gn b/third_party/libwebrtc/pc/BUILD.gn @@ -2841,6 +2841,7 @@ if (rtc_include_tests && !build_with_chromium) { ":rtp_transport_internal", ":sctp_data_channel", ":sctp_utils", + ":sdp_utils", ":session_description", ":simulcast_description", ":stream_collection", diff --git a/third_party/libwebrtc/pc/rtc_stats_collector.cc b/third_party/libwebrtc/pc/rtc_stats_collector.cc @@ -1717,11 +1717,12 @@ void RTCStatsCollector::ProduceRTPStreamStats_n( RTC_DCHECK_RUN_ON(network_thread_); Thread::ScopedDisallowBlockingCalls no_blocking_calls; + bool spec_lifetime = env_.field_trials().IsEnabled("WebRTC-RTP-Lifetime"); for (const RtpTransceiverStatsInfo& stats : transceiver_stats_infos) { if (stats.media_type == MediaType::AUDIO) { - ProduceAudioRTPStreamStats_n(timestamp, stats, report); + ProduceAudioRTPStreamStats_n(timestamp, stats, spec_lifetime, report); } else if (stats.media_type == MediaType::VIDEO) { - ProduceVideoRTPStreamStats_n(timestamp, stats, report); + ProduceVideoRTPStreamStats_n(timestamp, stats, spec_lifetime, report); } else { RTC_DCHECK_NOTREACHED(); } @@ -1731,6 +1732,7 @@ void RTCStatsCollector::ProduceRTPStreamStats_n( void RTCStatsCollector::ProduceAudioRTPStreamStats_n( Timestamp timestamp, const RtpTransceiverStatsInfo& stats, + bool spec_lifetime, RTCStatsReport* report) const { RTC_DCHECK_RUN_ON(network_thread_); Thread::ScopedDisallowBlockingCalls no_blocking_calls; @@ -1747,8 +1749,12 @@ void RTCStatsCollector::ProduceAudioRTPStreamStats_n( // remote endpoint providing metrics about the remote outbound streams. for (const VoiceReceiverInfo& voice_receiver_info : stats.track_media_info_map.voice_media_info()->receivers) { - if (!voice_receiver_info.connected()) + if (!voice_receiver_info.connected()) { continue; + } + if (spec_lifetime && voice_receiver_info.packets_received == 0) { + continue; + } // Inbound. auto inbound_audio = CreateInboundAudioStreamStats( *stats.track_media_info_map.voice_media_info(), voice_receiver_info, @@ -1836,6 +1842,7 @@ void RTCStatsCollector::ProduceAudioRTPStreamStats_n( void RTCStatsCollector::ProduceVideoRTPStreamStats_n( Timestamp timestamp, const RtpTransceiverStatsInfo& stats, + bool spec_lifetime, RTCStatsReport* report) const { RTC_DCHECK_RUN_ON(network_thread_); Thread::ScopedDisallowBlockingCalls no_blocking_calls; @@ -1850,8 +1857,12 @@ void RTCStatsCollector::ProduceVideoRTPStreamStats_n( // Inbound and remote-outbound. for (const VideoReceiverInfo& video_receiver_info : stats.track_media_info_map.video_media_info()->receivers) { - if (!video_receiver_info.connected()) + if (!video_receiver_info.connected()) { continue; + } + if (spec_lifetime && video_receiver_info.packets_received == 0) { + continue; + } auto inbound_video = CreateInboundRTPStreamStatsFromVideoReceiverInfo( transport_id, mid, *stats.track_media_info_map.video_media_info(), video_receiver_info, timestamp, report); diff --git a/third_party/libwebrtc/pc/rtc_stats_collector.h b/third_party/libwebrtc/pc/rtc_stats_collector.h @@ -214,9 +214,11 @@ class RTCStatsCollector : public RefCountInterface { RTCStatsReport* report) const; void ProduceAudioRTPStreamStats_n(Timestamp timestamp, const RtpTransceiverStatsInfo& stats, + bool spec_lifetime, RTCStatsReport* report) const; void ProduceVideoRTPStreamStats_n(Timestamp timestamp, const RtpTransceiverStatsInfo& stats, + bool spec_lifetime, RTCStatsReport* report) const; // Produces `RTCTransportStats`. void ProduceTransportStats_n( diff --git a/third_party/libwebrtc/pc/rtc_stats_integrationtest.cc b/third_party/libwebrtc/pc/rtc_stats_integrationtest.cc @@ -21,7 +21,9 @@ #include "api/audio_codecs/builtin_audio_encoder_factory.h" #include "api/audio_options.h" #include "api/data_channel_interface.h" +#include "api/field_trials.h" #include "api/make_ref_counted.h" +#include "api/media_stream_interface.h" #include "api/peer_connection_interface.h" #include "api/rtp_receiver_interface.h" #include "api/rtp_sender_interface.h" @@ -45,6 +47,8 @@ #include "test/wait_until.h" using ::testing::Contains; +using ::testing::IsTrue; +using ::testing::SizeIs; namespace webrtc { @@ -130,10 +134,10 @@ class RTCStatsIntegrationTest : public ::testing::Test { PeerConnectionInterface* pc) { scoped_refptr<RTCStatsObtainer> stats_obtainer = RTCStatsObtainer::Create(); pc->GetStats(stats_obtainer.get()); - EXPECT_THAT(WaitUntil([&] { return stats_obtainer->report() != nullptr; }, - ::testing::IsTrue(), - {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}), - IsRtcOk()); + EXPECT_THAT( + WaitUntil([&] { return stats_obtainer->report() != nullptr; }, IsTrue(), + {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}), + IsRtcOk()); return stats_obtainer->report(); } @@ -143,10 +147,10 @@ class RTCStatsIntegrationTest : public ::testing::Test { scoped_refptr<T> selector) { scoped_refptr<RTCStatsObtainer> stats_obtainer = RTCStatsObtainer::Create(); pc->GetStats(selector, stats_obtainer); - EXPECT_THAT(WaitUntil([&] { return stats_obtainer->report() != nullptr; }, - ::testing::IsTrue(), - {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}), - IsRtcOk()); + EXPECT_THAT( + WaitUntil([&] { return stats_obtainer->report() != nullptr; }, IsTrue(), + {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}), + IsRtcOk()); return stats_obtainer->report(); } @@ -1061,8 +1065,7 @@ TEST_F(RTCStatsIntegrationTest, GetStatsFromCallee) { }; EXPECT_THAT( WaitUntil([&] { return GetStatsReportAndReturnTrueIfRttIsDefined(); }, - ::testing::IsTrue(), - {.timeout = TimeDelta::Millis(kMaxWaitMs)}), + IsTrue(), {.timeout = TimeDelta::Millis(kMaxWaitMs)}), IsRtcOk()); RTCStatsReportVerifier(report.get()).VerifyReport({}); } @@ -1232,6 +1235,90 @@ TEST_F(RTCStatsIntegrationTest, ExperimentalPsnrStats) { } } } + +class RTCStatsRtpLifetimeTest : public RTCStatsIntegrationTest { + public: + RTCStatsRtpLifetimeTest() : RTCStatsIntegrationTest() { + FieldTrials field_trials = + CreateTestFieldTrials("WebRTC-RTP-Lifetime/Enabled/"); + EXPECT_TRUE(caller_->CreatePc({}, CreateBuiltinAudioEncoderFactory(), + CreateBuiltinAudioDecoderFactory(), + std::make_unique<FieldTrials>(field_trials))); + EXPECT_TRUE(callee_->CreatePc({}, CreateBuiltinAudioEncoderFactory(), + CreateBuiltinAudioDecoderFactory(), + std::make_unique<FieldTrials>(field_trials))); + } +}; + +TEST_F(RTCStatsRtpLifetimeTest, AudioInboundRtpMissingBeforeFirstPacket) { + // Caller to send audio. + scoped_refptr<MediaStreamInterface> stream = caller_->GetUserMedia( + /*audio=*/true, {}, /*video=*/false); + scoped_refptr<AudioTrackInterface> track = stream->GetAudioTracks()[0]; + caller_->pc()->AddTransceiver(track, {}); + + caller_->ListenForRemoteIceCandidates(callee_); + callee_->ListenForRemoteIceCandidates(caller_); + PeerConnectionTestWrapper::AwaitNegotiation(caller_.get(), callee_.get()); + + // The m-section has been negotiated but no inbound-rtp should be present + // since no packets have been received yet. + scoped_refptr<const RTCStatsReport> report = GetStats(callee_->pc()); + std::vector<const RTCInboundRtpStreamStats*> inbound_rtps = + report->GetStatsOfType<RTCInboundRtpStreamStats>(); + EXPECT_THAT(inbound_rtps, SizeIs(0)); + + caller_->AwaitAddRemoteIceCandidates(); + callee_->AwaitAddRemoteIceCandidates(); + + // Nothing is preventing packets from flowing, wait for inbound-rtp to appear. + EXPECT_THAT(WaitUntil( + [&] { + report = GetStats(callee_->pc()); + inbound_rtps = + report->GetStatsOfType<RTCInboundRtpStreamStats>(); + return inbound_rtps.size() > 0; + }, + IsTrue(), {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}), + IsRtcOk()); + ASSERT_THAT(inbound_rtps, SizeIs(1)); + EXPECT_GT(inbound_rtps[0]->packets_received.value_or(0), 0u); +} + +TEST_F(RTCStatsRtpLifetimeTest, VideoInboundRtpMissingBeforeFirstPacket) { + // Caller to send video. + scoped_refptr<MediaStreamInterface> stream = caller_->GetUserMedia( + /*audio=*/false, {}, /*video=*/true); + scoped_refptr<VideoTrackInterface> track = stream->GetVideoTracks()[0]; + caller_->pc()->AddTransceiver(track, {}); + + caller_->ListenForRemoteIceCandidates(callee_); + callee_->ListenForRemoteIceCandidates(caller_); + PeerConnectionTestWrapper::AwaitNegotiation(caller_.get(), callee_.get()); + + // The m-section has been negotiated but no inbound-rtp should be present + // since no packets have been received yet. + scoped_refptr<const RTCStatsReport> report = GetStats(callee_->pc()); + std::vector<const RTCInboundRtpStreamStats*> inbound_rtps = + report->GetStatsOfType<RTCInboundRtpStreamStats>(); + EXPECT_THAT(inbound_rtps, SizeIs(0)); + + caller_->AwaitAddRemoteIceCandidates(); + callee_->AwaitAddRemoteIceCandidates(); + + // Nothing is preventing packets from flowing, wait for inbound-rtp to appear. + EXPECT_THAT(WaitUntil( + [&] { + report = GetStats(callee_->pc()); + inbound_rtps = + report->GetStatsOfType<RTCInboundRtpStreamStats>(); + return inbound_rtps.size() > 0; + }, + IsTrue(), {.timeout = TimeDelta::Millis(kGetStatsTimeoutMs)}), + IsRtcOk()); + ASSERT_THAT(inbound_rtps, SizeIs(1)); + EXPECT_GT(inbound_rtps[0]->packets_received.value_or(0), 0u); +} #endif // WEBRTC_HAVE_SCTP } // namespace diff --git a/third_party/libwebrtc/pc/test/peer_connection_test_wrapper.cc b/third_party/libwebrtc/pc/test/peer_connection_test_wrapper.cc @@ -57,6 +57,7 @@ #include "api/video_codecs/video_encoder_factory_template_open_h264_adapter.h" #include "media/engine/simulcast_encoder_adapter.h" #include "p2p/test/fake_port_allocator.h" +#include "pc/sdp_utils.h" #include "pc/test/fake_audio_capture_module.h" #include "pc/test/fake_periodic_video_source.h" #include "pc/test/fake_periodic_video_track_source.h" @@ -140,6 +141,17 @@ void PeerConnectionTestWrapper::Connect(PeerConnectionTestWrapper* caller, caller, &PeerConnectionTestWrapper::ReceiveAnswerSdp); } +void PeerConnectionTestWrapper::AwaitNegotiation( + PeerConnectionTestWrapper* caller, + PeerConnectionTestWrapper* callee) { + auto offer = caller->AwaitCreateOffer(); + caller->AwaitSetLocalDescription(offer.get()); + callee->AwaitSetRemoteDescription(offer.get()); + auto answer = callee->AwaitCreateAnswer(); + callee->AwaitSetLocalDescription(answer.get()); + caller->AwaitSetRemoteDescription(answer.get()); +} + PeerConnectionTestWrapper::PeerConnectionTestWrapper( const std::string& name, webrtc::SocketServer* socket_server, @@ -262,6 +274,83 @@ void PeerConnectionTestWrapper::WaitForNegotiation() { webrtc::IsRtcOk()); } +std::unique_ptr<webrtc::SessionDescriptionInterface> +PeerConnectionTestWrapper::AwaitCreateOffer() { + auto observer = + webrtc::make_ref_counted<webrtc::MockCreateSessionDescriptionObserver>(); + peer_connection_->CreateOffer(observer.get(), {}); + EXPECT_THAT(webrtc::WaitUntil([&] { return observer->called(); }, + ::testing::IsTrue()), + webrtc::IsRtcOk()); + return observer->MoveDescription(); +} + +std::unique_ptr<webrtc::SessionDescriptionInterface> +PeerConnectionTestWrapper::AwaitCreateAnswer() { + auto observer = + webrtc::make_ref_counted<webrtc::MockCreateSessionDescriptionObserver>(); + peer_connection_->CreateAnswer(observer.get(), {}); + EXPECT_THAT(webrtc::WaitUntil([&] { return observer->called(); }, + ::testing::IsTrue()), + webrtc::IsRtcOk()); + return observer->MoveDescription(); +} + +void PeerConnectionTestWrapper::AwaitSetLocalDescription( + webrtc::SessionDescriptionInterface* sdp) { + auto observer = + webrtc::make_ref_counted<webrtc::MockSetSessionDescriptionObserver>(); + peer_connection_->SetLocalDescription( + observer.get(), webrtc::CloneSessionDescription(sdp).release()); + EXPECT_THAT(webrtc::WaitUntil([&] { return observer->called(); }, + ::testing::IsTrue()), + webrtc::IsRtcOk()); +} + +void PeerConnectionTestWrapper::AwaitSetRemoteDescription( + webrtc::SessionDescriptionInterface* sdp) { + auto observer = + webrtc::make_ref_counted<webrtc::MockSetSessionDescriptionObserver>(); + peer_connection_->SetRemoteDescription( + observer.get(), webrtc::CloneSessionDescription(sdp).release()); + EXPECT_THAT(webrtc::WaitUntil([&] { return observer->called(); }, + ::testing::IsTrue()), + webrtc::IsRtcOk()); +} + +void PeerConnectionTestWrapper::ListenForRemoteIceCandidates( + webrtc::scoped_refptr<PeerConnectionTestWrapper> remote_wrapper) { + remote_wrapper_ = remote_wrapper; + remote_wrapper_->SignalOnIceCandidateReady.connect( + this, &PeerConnectionTestWrapper::OnRemoteIceCandidate); +} + +void PeerConnectionTestWrapper::AwaitAddRemoteIceCandidates() { + EXPECT_TRUE(remote_wrapper_); + EXPECT_THAT( + webrtc::WaitUntil( + [&] { + return remote_wrapper_->pc()->ice_gathering_state() == + webrtc::PeerConnectionInterface::kIceGatheringComplete; + }, + ::testing::IsTrue(), + {.timeout = webrtc::TimeDelta::Millis(kMaxWait)}), + webrtc::IsRtcOk()); + for (const auto& remote_ice_candidate : remote_ice_candidates_) { + peer_connection_->AddIceCandidate(remote_ice_candidate.get()); + } + remote_wrapper_ = nullptr; + remote_ice_candidates_.clear(); +} + +void PeerConnectionTestWrapper::OnRemoteIceCandidate( + const std::string& sdp_mid, + int sdp_mline_index, + const std::string& candidate) { + remote_ice_candidates_.emplace_back( + webrtc::CreateIceCandidate(sdp_mid, sdp_mline_index, candidate, nullptr)); +} + void PeerConnectionTestWrapper::OnSignalingChange( webrtc::PeerConnectionInterface::SignalingState new_state) { if (new_state == webrtc::PeerConnectionInterface::SignalingState::kStable) { diff --git a/third_party/libwebrtc/pc/test/peer_connection_test_wrapper.h b/third_party/libwebrtc/pc/test/peer_connection_test_wrapper.h @@ -46,8 +46,13 @@ class PeerConnectionTestWrapper public webrtc::CreateSessionDescriptionObserver, public sigslot::has_slots<> { public: + // Asynchronously negotiates and exchanges ICE candidates between `caller` and + // `callee`. See also WaitForNegotiation() and other "WaitFor..." methods. static void Connect(PeerConnectionTestWrapper* caller, PeerConnectionTestWrapper* callee); + // Synchronously negotiates. ICE candidates needs to be exchanged separately. + static void AwaitNegotiation(PeerConnectionTestWrapper* caller, + PeerConnectionTestWrapper* callee); PeerConnectionTestWrapper(const std::string& name, webrtc::SocketServer* socket_server, @@ -84,6 +89,17 @@ class PeerConnectionTestWrapper void WaitForNegotiation(); + // Synchronous negotiation methods. + std::unique_ptr<webrtc::SessionDescriptionInterface> AwaitCreateOffer(); + std::unique_ptr<webrtc::SessionDescriptionInterface> AwaitCreateAnswer(); + void AwaitSetLocalDescription(webrtc::SessionDescriptionInterface* sdp); + void AwaitSetRemoteDescription(webrtc::SessionDescriptionInterface* sdp); + // Listen for remote ICE candidates but don't add them until + // AwaitAddRemoteIceCandidates(). + void ListenForRemoteIceCandidates( + webrtc::scoped_refptr<PeerConnectionTestWrapper> remote_wrapper); + void AwaitAddRemoteIceCandidates(); + // Implements PeerConnectionObserver. void OnSignalingChange( webrtc::PeerConnectionInterface::SignalingState new_state) override; @@ -143,6 +159,9 @@ class PeerConnectionTestWrapper bool CheckForConnection(); bool CheckForAudio(); bool CheckForVideo(); + void OnRemoteIceCandidate(const std::string& sdp_mid, + int sdp_mline_index, + const std::string& candidate); std::string name_; webrtc::SocketServer* const socket_server_; @@ -158,6 +177,8 @@ class PeerConnectionTestWrapper bool pending_negotiation_; std::vector<webrtc::scoped_refptr<webrtc::FakePeriodicVideoTrackSource>> fake_video_sources_; + webrtc::scoped_refptr<PeerConnectionTestWrapper> remote_wrapper_; + std::vector<std::unique_ptr<webrtc::IceCandidate>> remote_ice_candidates_; }; #endif // PC_TEST_PEER_CONNECTION_TEST_WRAPPER_H_