1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qaccessiblecache_p.h"
41#include <QtCore/qdebug.h>
42#include <QtCore/qloggingcategory.h>
43
44#ifndef QT_NO_ACCESSIBILITY
45
46QT_BEGIN_NAMESPACE
47
48Q_LOGGING_CATEGORY(lcAccessibilityCache, "qt.accessibility.cache");
49
50/*!
51 \class QAccessibleCache
52 \internal
53 \brief Maintains a cache of accessible interfaces.
54*/
55
56static QAccessibleCache *accessibleCache = nullptr;
57
58static void cleanupAccessibleCache()
59{
60 delete accessibleCache;
61 accessibleCache = nullptr;
62}
63
64QAccessibleCache::~QAccessibleCache()
65{
66 for (QAccessible::Id id: idToInterface.keys())
67 deleteInterface(id);
68}
69
70QAccessibleCache *QAccessibleCache::instance()
71{
72 if (!accessibleCache) {
73 accessibleCache = new QAccessibleCache;
74 qAddPostRoutine(cleanupAccessibleCache);
75 }
76 return accessibleCache;
77}
78
79/*
80 The ID is always in the range [INT_MAX+1, UINT_MAX].
81 This makes it easy on windows to reserve the positive integer range
82 for the index of a child and not clash with the unique ids.
83*/
84QAccessible::Id QAccessibleCache::acquireId() const
85{
86 static const QAccessible::Id FirstId = QAccessible::Id(INT_MAX) + 1;
87 static QAccessible::Id lastUsedId = FirstId;
88
89 while (idToInterface.contains(lastUsedId)) {
90 // (wrap back when when we reach UINT_MAX - 1)
91 // -1 because on Android -1 is taken for the "View" so just avoid it completely for consistency
92 if (lastUsedId == UINT_MAX - 1)
93 lastUsedId = FirstId;
94 else
95 ++lastUsedId;
96 }
97
98 return lastUsedId;
99}
100
101QAccessibleInterface *QAccessibleCache::interfaceForId(QAccessible::Id id) const
102{
103 return idToInterface.value(id);
104}
105
106QAccessible::Id QAccessibleCache::idForInterface(QAccessibleInterface *iface) const
107{
108 return interfaceToId.value(iface);
109}
110
111QAccessible::Id QAccessibleCache::idForObject(QObject *obj) const
112{
113 if (obj) {
114 const QMetaObject *mo = obj->metaObject();
115 for (auto pair : objectToId.values(obj)) {
116 if (pair.second == mo) {
117 return pair.first;
118 }
119 }
120 }
121 return 0;
122}
123
124/*!
125 * \internal
126 *
127 * returns true if the cache has an interface for the object and its corresponding QMetaObject
128 */
129bool QAccessibleCache::containsObject(QObject *obj) const
130{
131 if (obj) {
132 const QMetaObject *mo = obj->metaObject();
133 for (auto pair : objectToId.values(obj)) {
134 if (pair.second == mo) {
135 return true;
136 }
137 }
138 }
139 return false;
140}
141
142QAccessible::Id QAccessibleCache::insert(QObject *object, QAccessibleInterface *iface) const
143{
144 Q_ASSERT(iface);
145 Q_UNUSED(object);
146
147 // object might be 0
148 Q_ASSERT(!containsObject(object));
149 Q_ASSERT_X(!interfaceToId.contains(iface), "", "Accessible interface inserted into cache twice!");
150
151 QAccessible::Id id = acquireId();
152 QObject *obj = iface->object();
153 Q_ASSERT(object == obj);
154 if (obj) {
155 objectToId.insert(obj, qMakePair(id, obj->metaObject()));
156 connect(obj, &QObject::destroyed, this, &QAccessibleCache::objectDestroyed);
157 }
158 idToInterface.insert(id, iface);
159 interfaceToId.insert(iface, id);
160 qCDebug(lcAccessibilityCache) << "insert - id:" << id << " iface:" << iface;
161 return id;
162}
163
164void QAccessibleCache::objectDestroyed(QObject* obj)
165{
166 /*
167 In some cases we might add a not fully-constructed object to the cache. This might happen with
168 for instance QWidget subclasses that are in the construction phase. If updateAccessibility() is
169 called in the constructor of QWidget (directly or indirectly), it it will end up asking for the
170 classname of that widget in order to know which accessibility interface subclass the
171 accessibility factory should instantiate and return. However, since that requires a virtual
172 call to metaObject(), it will return the metaObject() of QWidget (not for the subclass), and so
173 the factory will ultimately return a rather generic QAccessibleWidget instead of a more
174 specialized interface. Even though it is a "incomplete" interface it will be put in the cache
175 and it will be usable as if the object is a widget. In order for the cache to not just return
176 the same generic QAccessibleWidget for that object, we have to check if the cache matches
177 the objects QMetaObject. We therefore use a QMultiHash and also store the QMetaObject * in
178 the value. We therefore might potentially store several values for the corresponding object
179 (in theory one for each level in the class inheritance chain)
180
181 This means that after the object have been fully constructed, we will at some point again query
182 for the interface for the same object, but now its metaObject() returns the correct
183 QMetaObject, so it won't return the QAccessibleWidget that is associated with the object in the
184 cache. Instead it will go to the factory and create the _correct_ specialized interface for the
185 object. If that succeeded, it will also put that entry in the cache. We will therefore in those
186 cases insert *two* cache entries for the same object (using QMultiHash). They both must live
187 until the object is destroyed.
188
189 So when the object is destroyed we might have to delete two entries from the cache.
190 */
191 for (auto pair : objectToId.values(obj)) {
192 QAccessible::Id id = pair.first;
193 Q_ASSERT_X(idToInterface.contains(id), "", "QObject with accessible interface deleted, where interface not in cache!");
194 deleteInterface(id, obj);
195 }
196}
197
198void QAccessibleCache::deleteInterface(QAccessible::Id id, QObject *obj)
199{
200 QAccessibleInterface *iface = idToInterface.take(id);
201 qCDebug(lcAccessibilityCache) << "delete - id:" << id << " iface:" << iface;
202 if (!iface) // the interface may be deleted already
203 return;
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_MAC
212 removeCocoaElement(id);
213#endif
214}
215
216QT_END_NAMESPACE
217
218#endif
219