Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qaccessiblecache.cpp
Go to the documentation of this file.
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
5#include <QtCore/qdebug.h>
6#include <QtCore/qloggingcategory.h>
7#include <private/qguiapplication_p.h>
8
9#if QT_CONFIG(accessibility)
10
11QT_BEGIN_NAMESPACE
12
13Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCache, "qt.accessibility.cache");
14
15/*!
16 \class QAccessibleCache
17 \internal
18 \brief Maintains a cache of accessible interfaces.
19*/
20
21static QAccessibleCache *accessibleCache = nullptr;
22static bool inCacheDestructor = false;
23
24static void cleanupAccessibleCache()
25{
26 delete accessibleCache;
27 accessibleCache = nullptr;
28}
29
30QAccessibleObjectDestroyedEvent::~QAccessibleObjectDestroyedEvent()
31{
32}
33
34QAccessibleCache::~QAccessibleCache()
35{
36 inCacheDestructor = true;
37
38 for (QAccessible::Id id: idToInterface.keys())
39 deleteInterface(id);
40}
41
42QAccessibleCache *QAccessibleCache::instance()
43{
44 if (!accessibleCache) {
45 accessibleCache = new QAccessibleCache;
46 qAddPostRoutine(cleanupAccessibleCache);
47 }
48 return accessibleCache;
49}
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*/
56QAccessible::Id QAccessibleCache::acquireId() const
57{
58 static const QAccessible::Id FirstId = QAccessible::Id(INT_MAX) + 1;
59 static QAccessible::Id nextId = FirstId;
60
61 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 if (nextId == UINT_MAX - 1)
65 nextId = FirstId;
66 else
67 ++nextId;
68 }
69
70 return nextId++;
71}
72
73QAccessibleInterface *QAccessibleCache::interfaceForId(QAccessible::Id id) const
74{
75 return idToInterface.value(id);
76}
77
78QAccessible::Id QAccessibleCache::idForInterface(QAccessibleInterface *iface) const
79{
80 return interfaceToId.value(iface);
81}
82
83QAccessible::Id QAccessibleCache::idForObject(QObject *obj) const
84{
85 if (obj) {
86 const QMetaObject *mo = obj->metaObject();
87 for (auto pair : objectToId.values(obj)) {
88 if (pair.second == mo) {
89 return pair.first;
90 }
91 }
92 }
93 return 0;
94}
95
96/*!
97 * \internal
98 *
99 * returns true if the cache has an interface for the object and its corresponding QMetaObject
100 */
101bool QAccessibleCache::containsObject(QObject *obj) const
102{
103 if (obj) {
104 const QMetaObject *mo = obj->metaObject();
105 for (auto pair : objectToId.values(obj)) {
106 if (pair.second == mo) {
107 return true;
108 }
109 }
110 }
111 return false;
112}
113
114QAccessible::Id QAccessibleCache::insert(QObject *object, QAccessibleInterface *iface) const
115{
116 Q_ASSERT(iface);
117 Q_UNUSED(object);
118
119 // object might be 0
120 Q_ASSERT(!containsObject(object));
121 Q_ASSERT_X(!interfaceToId.contains(iface), "", "Accessible interface inserted into cache twice!");
122
123 QAccessible::Id id = acquireId();
124 QObject *obj = iface->object();
125 Q_ASSERT(object == obj);
126 if (obj) {
127 objectToId.insert(obj, std::pair(id, obj->metaObject()));
128 connect(obj, &QObject::destroyed, this, &QAccessibleCache::objectDestroyed);
129 }
130 idToInterface.insert(id, iface);
131 interfaceToId.insert(iface, id);
132 qCDebug(lcAccessibilityCache) << "insert - id:" << id << " iface:" << iface;
133 return id;
134}
135
136void QAccessibleCache::objectDestroyed(QObject* obj)
137{
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 for (auto pair : objectToId.values(obj)) {
164 QAccessible::Id id = pair.first;
165 Q_ASSERT_X(idToInterface.contains(id), "", "QObject with accessible interface deleted, where interface not in cache!");
166 deleteInterface(id, obj);
167 }
168}
169
170void QAccessibleCache::sendObjectDestroyedEvent(QObject *obj)
171{
172 for (auto pair : objectToId.values(obj)) {
173 QAccessible::Id id = pair.first;
174 Q_ASSERT_X(idToInterface.contains(id), "", "QObject with accessible interface deleted, where interface not in cache!");
175
176 QAccessibleObjectDestroyedEvent event(id);
177 QAccessible::updateAccessibility(&event);
178 }
179}
180
181void QAccessibleCache::deleteInterface(QAccessible::Id id, QObject *obj)
182{
183 const auto it = idToInterface.find(id);
184 if (it == idToInterface.end()) // the interface may be deleted already
185 return;
186
187 QAccessibleInterface *iface = *it;
188 qCDebug(lcAccessibilityCache) << "delete - id:" << id << " iface:" << iface;
189 if (!iface) {
190 idToInterface.erase(it);
191 return;
192 }
193
194 // QObjects send this from their destructors, but the interfaces
195 // with no associated object call deleteInterface directly.
196 if (!inCacheDestructor && !obj && !iface->object()) {
197 if (QGuiApplicationPrivate::is_app_running && !QGuiApplicationPrivate::is_app_closing && QAccessible::isActive()) {
198 QAccessibleObjectDestroyedEvent event(id);
199 QAccessible::updateAccessibility(&event);
200 }
201 }
202
203 idToInterface.erase(it);
204 interfaceToId.take(iface);
205 if (!obj)
206 obj = iface->object();
207 if (obj)
208 objectToId.remove(obj);
209 delete iface;
210
211#ifdef Q_OS_APPLE
212 removeAccessibleElement(id);
213#endif
214}
215
216QT_END_NAMESPACE
217
218#include "moc_qaccessiblecache_p.cpp"
219
220#endif