Coverage Report

Created: 2026-03-31 07:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/gui/accessible/qaccessiblecache.cpp
Line
Count
Source
1
// Copyright (C) 2016 The Qt Company Ltd.
2
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4
#include "qaccessiblecache_p.h"
5
#include <QtCore/qdebug.h>
6
#include <QtCore/qloggingcategory.h>
7
#include <private/qguiapplication_p.h>
8
9
#if QT_CONFIG(accessibility)
10
11
QT_BEGIN_NAMESPACE
12
13
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCache, "qt.accessibility.cache");
14
15
/*!
16
    \class QAccessibleCache
17
    \internal
18
    \brief Maintains a cache of accessible interfaces.
19
*/
20
21
static QAccessibleCache *accessibleCache = nullptr;
22
static bool inCacheDestructor = false;
23
24
static void cleanupAccessibleCache()
25
0
{
26
0
    delete accessibleCache;
27
0
    accessibleCache = nullptr;
28
0
}
29
30
QAccessibleObjectDestroyedEvent::~QAccessibleObjectDestroyedEvent()
31
0
{
32
0
}
33
34
QAccessibleCache::~QAccessibleCache()
35
0
{
36
0
    inCacheDestructor = true;
37
38
0
    for (QAccessible::Id id: idToInterface.keys())
39
0
        deleteInterface(id);
40
0
}
41
42
QAccessibleCache *QAccessibleCache::instance()
43
0
{
44
0
    if (!accessibleCache) {
45
0
        accessibleCache = new QAccessibleCache;
46
0
        qAddPostRoutine(cleanupAccessibleCache);
47
0
    }
48
0
    return accessibleCache;
49
0
}
50
51
/*
52
  The ID is always in the range [INT_MAX+1, UINT_MAX].
53
  This makes it easy on windows to reserve the positive integer range
54
  for the index of a child and not clash with the unique ids.
55
*/
56
QAccessible::Id QAccessibleCache::acquireId() const
57
0
{
58
0
    static const QAccessible::Id FirstId = QAccessible::Id(INT_MAX) + 1;
59
0
    static QAccessible::Id nextId = FirstId;
60
61
0
    while (idToInterface.contains(nextId)) {
62
        // (wrap back when when we reach UINT_MAX - 1)
63
        // -1 because on Android -1 is taken for the "View" so just avoid it completely for consistency
64
0
        if (nextId == UINT_MAX - 1)
65
0
            nextId = FirstId;
66
0
        else
67
0
            ++nextId;
68
0
    }
69
70
0
    return nextId++;
71
0
}
72
73
QAccessibleInterface *QAccessibleCache::interfaceForId(QAccessible::Id id) const
74
0
{
75
0
    return idToInterface.value(id);
76
0
}
77
78
QAccessible::Id QAccessibleCache::idForInterface(QAccessibleInterface *iface) const
79
0
{
80
0
    return interfaceToId.value(iface);
81
0
}
82
83
QAccessible::Id QAccessibleCache::idForObject(QObject *obj) const
84
0
{
85
0
    if (obj) {
86
0
        const QMetaObject *mo = obj->metaObject();
87
0
        for (auto pair : objectToId.values(obj)) {
88
0
            if (pair.second == mo) {
89
0
                return pair.first;
90
0
            }
91
0
        }
92
0
    }
93
0
    return 0;
94
0
}
95
96
/*!
97
 * \internal
98
 *
99
 * returns true if the cache has an interface for the object and its corresponding QMetaObject
100
 */
101
bool QAccessibleCache::containsObject(QObject *obj) const
102
0
{
103
0
    if (obj) {
104
0
        const QMetaObject *mo = obj->metaObject();
105
0
        for (auto pair : objectToId.values(obj)) {
106
0
            if (pair.second == mo) {
107
0
                return true;
108
0
            }
109
0
        }
110
0
    }
111
0
    return false;
112
0
}
113
114
QAccessible::Id QAccessibleCache::insert(QObject *object, QAccessibleInterface *iface) const
115
0
{
116
0
    Q_ASSERT(iface);
117
0
    Q_UNUSED(object);
118
119
    // object might be 0
120
0
    Q_ASSERT(!containsObject(object));
121
0
    Q_ASSERT_X(!interfaceToId.contains(iface), "", "Accessible interface inserted into cache twice!");
122
123
0
    QAccessible::Id id = acquireId();
124
0
    QObject *obj = iface->object();
125
0
    Q_ASSERT(object == obj);
126
0
    if (obj) {
127
0
        objectToId.insert(obj, std::pair(id, obj->metaObject()));
128
0
        connect(obj, &QObject::destroyed, this, &QAccessibleCache::objectDestroyed);
129
0
    }
130
0
    idToInterface.insert(id, iface);
131
0
    interfaceToId.insert(iface, id);
132
0
    qCDebug(lcAccessibilityCache) << "insert - id:" << id << " iface:" << iface;
133
0
    return id;
134
0
}
135
136
void QAccessibleCache::objectDestroyed(QObject* obj)
137
0
{
138
    /*
139
    In some cases we might add a not fully-constructed object to the cache. This might happen with
140
    for instance QWidget subclasses that are in the construction phase. If updateAccessibility() is
141
    called in the constructor of QWidget (directly or indirectly), it will end up asking for the
142
    classname of that widget in order to know which accessibility interface subclass the
143
    accessibility factory should instantiate and return. However, since that requires a virtual
144
    call to metaObject(), it will return the metaObject() of QWidget (not for the subclass), and so
145
    the factory will ultimately return a rather generic QAccessibleWidget instead of a more
146
    specialized interface. Even though it is a "incomplete" interface it will be put in the cache
147
    and it will be usable as if the object is a widget. In order for the cache to not just return
148
    the same generic QAccessibleWidget for that object, we have to check if the cache matches
149
    the objects QMetaObject. We therefore use a QMultiHash and also store the QMetaObject * in
150
    the value. We therefore might potentially store several values for the corresponding object
151
    (in theory one for each level in the class inheritance chain)
152
153
    This means that after the object have been fully constructed, we will at some point again query
154
    for the interface for the same object, but now its metaObject() returns the correct
155
    QMetaObject, so it won't return the QAccessibleWidget that is associated with the object in the
156
    cache. Instead it will go to the factory and create the _correct_ specialized interface for the
157
    object. If that succeeded, it will also put that entry in the cache. We will therefore in those
158
    cases insert *two* cache entries for the same object (using QMultiHash). They both must live
159
    until the object is destroyed.
160
161
    So when the object is destroyed we might have to delete two entries from the cache.
162
    */
163
0
    for (auto pair : objectToId.values(obj)) {
164
0
        QAccessible::Id id = pair.first;
165
0
        Q_ASSERT_X(idToInterface.contains(id), "", "QObject with accessible interface deleted, where interface not in cache!");
166
0
        deleteInterface(id, obj);
167
0
    }
168
0
}
169
170
void QAccessibleCache::sendObjectDestroyedEvent(QObject *obj)
171
0
{
172
0
    for (auto pair : objectToId.values(obj)) {
173
0
        QAccessible::Id id = pair.first;
174
0
        Q_ASSERT_X(idToInterface.contains(id), "", "QObject with accessible interface deleted, where interface not in cache!");
175
176
0
        QAccessibleObjectDestroyedEvent event(id);
177
0
        QAccessible::updateAccessibility(&event);
178
0
    }
179
0
}
180
181
void QAccessibleCache::deleteInterface(QAccessible::Id id, QObject *obj)
182
0
{
183
0
    const auto it = idToInterface.find(id);
184
0
    if (it == idToInterface.end()) // the interface may be deleted already
185
0
        return;
186
187
0
    QAccessibleInterface *iface = *it;
188
0
    qCDebug(lcAccessibilityCache) << "delete - id:" << id << " iface:" << iface;
189
0
    if (!iface) {
190
0
        idToInterface.erase(it);
191
0
        return;
192
0
    }
193
194
    // QObjects send this from their destructors, but the interfaces
195
    // with no associated object call deleteInterface directly.
196
0
    if (!inCacheDestructor && !obj && !iface->object()) {
197
0
        if (QGuiApplicationPrivate::is_app_running && !QGuiApplicationPrivate::is_app_closing && QAccessible::isActive()) {
198
0
            QAccessibleObjectDestroyedEvent event(id);
199
0
            QAccessible::updateAccessibility(&event);
200
0
        }
201
0
    }
202
203
0
    idToInterface.erase(it);
204
0
    interfaceToId.take(iface);
205
0
    if (!obj)
206
0
        obj = iface->object();
207
0
    if (obj)
208
0
        objectToId.remove(obj);
209
0
    delete iface;
210
211
#ifdef Q_OS_APPLE
212
    removeAccessibleElement(id);
213
#endif
214
0
}
215
216
QT_END_NAMESPACE
217
218
#include "moc_qaccessiblecache_p.cpp"
219
220
#endif