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:
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');