/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 |