1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only
#include "registry.h"
#include <QJSEngine>
#include <QQmlApplicationEngine>
#include <private/qqmldata_p.h>
#include "qtbridge-runtime/src/registry.rs.h"
namespace rust::bridge::registry {
namespace {
// Replacement for a "garbage collection finished" signal, which the engine
// does not offer.
//
// Adds a dummy QObject with JavaScriptOwnership to the engine, whose
// JS wrapper is referenced by nothing. The next garbage collection frees the
// wrapper and thereby deletes the sentinel; its destroyed() signal runs the
// registry's collect_garbage() and re-arms a new sentinel one event-loop turn
// later, so the trigger is exactly-once-per-collection and costs nothing
// while no collection happens.
void armGcSentinel(QJSEngine* engine)
{
auto* sentinel = new QObject();
sentinel->setObjectName("GC_Sentinel");
QJSEngine::setObjectOwnership(sentinel, QJSEngine::JavaScriptOwnership);
// Create the wrapper and drop the only reference to it.
(void)engine->newQObject(sentinel);
QObject::connect(sentinel, &QObject::destroyed, engine, [engine] {
collect_garbage();
// Re-arm from the event loop: during engine teardown the queued
// call is dropped (dead context), ending the cycle; arming
// directly from inside the collection would wrap mid-sweep.
QMetaObject::invokeMethod(engine, [engine] {
armGcSentinel(engine);
}, Qt::QueuedConnection);
});
}
} // namespace
void setCppOwnership(QObject* obj)
{
QJSEngine::setObjectOwnership(obj, QJSEngine::CppOwnership);
}
void setJavaScriptOwnership(QObject* obj)
{
QJSEngine::setObjectOwnership(obj, QJSEngine::JavaScriptOwnership);
}
bool isJavaScriptOwnership(QObject* obj)
{
return QJSEngine::objectOwnership(obj) == QJSEngine::JavaScriptOwnership;
}
bool hasLiveJsWrapper(const QObject* obj)
{
const QQmlData* ddata = QQmlData::get(obj);
if (!ddata)
return false;
if (!ddata->jsWrapper.isNullOrUndefined())
return true;
// Wrapped by more than one engine: the per-engine wrappers live in the
// multiply-wrapped map, not in jsWrapper. Conservatively keep alive.
return ddata->hasTaintedV4Object;
}
void installGcSentinel(QQmlApplicationEngine& engine)
{
armGcSentinel(&engine);
}
} // namespace rust::bridge::registry