tor-browser

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

commit ebe9410652ec5bc6a50ec025dbb184cca6967ac0
parent 992dd3e291884d283d7543e97f8319282f32fd95
Author: Chun-Min Chang <chun.m.chang@gmail.com>
Date:   Sat, 20 Dec 2025 18:19:44 +0000

Bug 1674892 - Add a crashtest r=webrtc-reviewers,pehrsons

This patch adds a crashtest that verifies cross-graph ports are properly cleaned
up when a MediaStreamTrack connected to a MediaStreamAudioSourceNode is stopped.
The test creates a cross-graph connection by using a different sample ratem,
waits for audio to flow, then stops the track and forces cleanup. Stream count
verification ensures all resources are released without crashes.

Differential Revision: https://phabricator.services.mozilla.com/D272363

Diffstat:
Adom/media/webrtc/tests/crashtests/1674892.html | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdom/media/webrtc/tests/crashtests/crashtests.list | 1+
2 files changed, 127 insertions(+), 0 deletions(-)

diff --git a/dom/media/webrtc/tests/crashtests/1674892.html b/dom/media/webrtc/tests/crashtests/1674892.html @@ -0,0 +1,126 @@ +<!DOCTYPE html> +<!-- +Verifies cross-graph ports are cleaned up when a MediaStreamTrack connected +to MediaStreamAudioSourceNode is stopped. +--> +<html class="reftest-wait"> +<head> + <title>Test bug 1674892 - MediaStreamAudioSourceNode with getUserMedia and different sample rate</title> + <script> + document.addEventListener("DOMContentLoaded", async () => { + const iframe = document.createElement("iframe"); + iframe.srcdoc = ` + <!DOCTYPE html> + <html> + <head> + <script> + let SpecialStream = SpecialPowers.wrap(MediaStream); + let ctx, stream, sourceNode, analyser, dataArray; + + window.addEventListener("message", async (event) => { + if (event.data === "startAudio") { + ctx = new AudioContext({ sampleRate: 32000 }); + + stream = await navigator.mediaDevices.getUserMedia({ + audio: true, + fake: true + }); + + sourceNode = ctx.createMediaStreamSource(stream); + sourceNode.connect(ctx.destination); + + analyser = ctx.createAnalyser(); + analyser.fftSize = 256; + dataArray = new Uint8Array(analyser.frequencyBinCount); + sourceNode.connect(analyser); + + while ((analyser.getByteFrequencyData(dataArray), dataArray.every(v => v == 0))) { + await new Promise(r => requestAnimationFrame(r)); + } + + const count = await SpecialStream.countUnderlyingStreams(); + parent.postMessage({ type: "ready", value: count }, "*"); + } else if (event.data === "destroy") { + // Clean up all resources + if (stream) { + // Stopping the MediaStreamTrack breaks connection to + // CrossGraphTransmitter, which can cause issues if the + // transmitter attempts to read from that connection during + // ::ProcessInput before the CrossGraph pair is fully torn + // down. + stream.getTracks().forEach(track => track.stop()); + stream = null; + } + if (analyser) { + analyser.disconnect(); + analyser = null; + } + if (sourceNode) { + sourceNode.disconnect(); + sourceNode = null; + } + if (ctx) { + await ctx.close(); + ctx = null; + } + dataArray = null; + + // Force garbage collection + await new Promise(r => SpecialPowers.exactGC(r)); + + const count = await SpecialStream.countUnderlyingStreams(); + parent.postMessage({ type: "destroyed", value: count }, "*"); + } + }); + + // Notify parent that iframe is ready with initial stream count + (async () => { + const count = await SpecialStream.countUnderlyingStreams(); + parent.postMessage({ type: "init", value: count }, "*"); + })(); + <\/script> + </head> + <body></body> + </html> + `; + + document.body.appendChild(iframe); + + // Helper to wait for iframe messages + const waitForMessage = (type) => { + return new Promise(resolve => { + const handler = (event) => { + if (event.data.type === type) { + window.removeEventListener("message", handler); + resolve(event.data.value); + } + }; + window.addEventListener("message", handler); + }); + }; + + // Wait for iframe to initialize and get initial stream count + const initialCount = await waitForMessage("init"); + dump(`Initial stream count: ${initialCount}\n`); + + // Start audio and wait for it to be ready + iframe.contentWindow.postMessage("startAudio", "*"); + const readyCount = await waitForMessage("ready"); + dump(`Stream count when audio ready: ${readyCount}\n`); + + // Destroy all resources in the iframe while MediaStream is active + iframe.contentWindow.postMessage("destroy", "*"); + const finalCount = await waitForMessage("destroyed"); + dump(`Final stream count: ${finalCount}\n`); + + // Check if streams were cleaned up + if (finalCount === initialCount) { + document.documentElement.removeAttribute("class"); + } else { + dump("Streams were not cleaned up properly.\n"); + } + }); + </script> +</head> +<body></body> +</html> diff --git a/dom/media/webrtc/tests/crashtests/crashtests.list b/dom/media/webrtc/tests/crashtests/crashtests.list @@ -1,5 +1,6 @@ defaults pref(media.navigator.permission.disabled,true) pref(media.devices.insecure.enabled,true) pref(media.getusermedia.insecure.enabled,true) +load 1674892.html load 1770075.html load 1789908.html load 1799168.html