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