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

source code of qtbase/src/gui/accessible/qaccessiblecache.cpp