tor-browser

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

commit fe3adabeb5c7c69b5ff429eb2d68a13c1157fbac
parent 0cdb254af13418c56f612696d1efd7d83fa52cd9
Author: Valentin Gosu <valentin.gosu@gmail.com>
Date:   Fri,  7 Nov 2025 15:08:51 +0000

Bug 1958291 - Make sure WebTransport CYCLE_COLLECTION calls UNLINK_PRESERVED_WRAPPER r=necko-reviewers,kershaw

The absence of NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER meant that
even when GCd, the WebTransport object was still retrievable when calling
weakref.deref(), but all its members were garbage collected already.
This lead to a null deref when trying to access target.ready

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

Diffstat:
Mdom/webtransport/api/WebTransport.cpp | 1+
Atesting/web-platform/mozilla/tests/webtransport/weak-promise.https.any.js | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 65 insertions(+), 0 deletions(-)

diff --git a/dom/webtransport/api/WebTransport.cpp b/dom/webtransport/api/WebTransport.cpp @@ -71,6 +71,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebTransport) tmp->mChild->Shutdown(false); tmp->mChild = nullptr; } + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTING_ADDREF(WebTransport) diff --git a/testing/web-platform/mozilla/tests/webtransport/weak-promise.https.any.js b/testing/web-platform/mozilla/tests/webtransport/weak-promise.https.any.js @@ -0,0 +1,64 @@ +// META: global=window,worker +// META: script=/common/get-host-info.sub.js +// META: script=/webtransport/resources/webtransport-test-helpers.sub.js +// META: script=/common/utils.js +// META: script=/common/gc.js + +'use strict'; + +async function timeout(cmd) { + const timer = new Promise((resolve, reject) => { + const id = setTimeout(() => { + clearTimeout(id) + reject(new Error("Promise timed out!")) + }, 750) + }) + return Promise.race([cmd, timer]) +} + +// This is a test for bug 1958291 +// A weakref should deref to null if the object has been GCd +// but a bug in the implementation of NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN +// made it so that only its members were GCd, leading to target.ready dereferencing +// a null pointer. +promise_test(async t => { + // Create first WebTransport connection and a WeakRef to it + let wt1 = new WebTransport( + webtransport_url(`query.py?token=${token()}`) + ); + const weakref = new WeakRef(wt1); + + // Create second WebTransport connection + const wt2 = new WebTransport( + webtransport_url(`query.py?token=${token()}`) + ); + t.add_cleanup(() => wt2.close()); + + // Remove strong reference to wt1 + wt1 = undefined; + + await wt2.ready; + + // Trigger garbage collection + await garbageCollect(); + + // Create bidirectional streams on wt2 + for (let i = 0; i < 8; i++) { + await timeout(wt2.createBidirectionalStream({})); + } + + // Try to dereference the weakref + const target = weakref.deref(); + + // The object may have been garbage collected (target === undefined) + // or it may still be alive (target !== undefined). Both are valid outcomes. + if (target !== undefined) { + // Object survived GC, verify it's still accessible + try { + await timeout(target.ready); + } catch (e) { + // Timeout or connection error is acceptable + } + } + // If target is undefined, the object was successfully garbage collected +}, 'WebTransport garbage collection behavior with WeakRef');