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 | |
10 | QT_BEGIN_NAMESPACE |
11 | |
12 | Q_LOGGING_CATEGORY(lcAccessibilityCache, "qt.accessibility.cache" ); |
13 | |
14 | /*! |
15 | \class QAccessibleCache |
16 | \internal |
17 | \brief Maintains a cache of accessible interfaces. |
18 | */ |
19 | |
20 | static QAccessibleCache *accessibleCache = nullptr; |
21 | |
22 | static void cleanupAccessibleCache() |
23 | { |
24 | delete accessibleCache; |
25 | accessibleCache = nullptr; |
26 | } |
27 | |
28 | QAccessibleCache::~QAccessibleCache() |
29 | { |
30 | for (QAccessible::Id id: idToInterface.keys()) |
31 | deleteInterface(id); |
32 | } |
33 | |
34 | QAccessibleCache *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 | */ |
48 | QAccessible::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 | |
65 | QAccessibleInterface *QAccessibleCache::interfaceForId(QAccessible::Id id) const |
66 | { |
67 | return idToInterface.value(key: id); |
68 | } |
69 | |
70 | QAccessible::Id QAccessibleCache::idForInterface(QAccessibleInterface *iface) const |
71 | { |
72 | return interfaceToId.value(key: iface); |
73 | } |
74 | |
75 | QAccessible::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 | */ |
93 | bool 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 | |
106 | QAccessible::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 | |
128 | void 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 | |
162 | void 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 | |
180 | QT_END_NAMESPACE |
181 | |
182 | #include "moc_qaccessiblecache_p.cpp" |
183 | |
184 | #endif |
185 | |