1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2019 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtQml 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 "qqml.h" |
41 | |
42 | #include <QtQml/qqmlprivate.h> |
43 | |
44 | #include <private/qqmlengine_p.h> |
45 | #include <private/qqmlmetatype_p.h> |
46 | #include <private/qqmlmetatypedata_p.h> |
47 | #include <private/qqmltype_p_p.h> |
48 | #include <private/qqmltypemodule_p_p.h> |
49 | #include <private/qqmltypenotavailable_p.h> |
50 | |
51 | #include <QtCore/qmutex.h> |
52 | |
53 | QT_BEGIN_NAMESPACE |
54 | |
55 | void qmlClearTypeRegistrations() // Declared in qqml.h |
56 | { |
57 | QQmlMetaType::clearTypeRegistrations(); |
58 | QQmlEnginePrivate::baseModulesUninitialized = true; //So the engine re-registers its types |
59 | qmlClearEnginePlugins(); |
60 | } |
61 | |
62 | //From qqml.h |
63 | bool qmlProtectModule(const char *uri, int majVersion) |
64 | { |
65 | return QQmlMetaType::protectModule(uri: QString::fromUtf8(str: uri), majVersion); |
66 | } |
67 | |
68 | //From qqml.h |
69 | void qmlRegisterModule(const char *uri, int versionMajor, int versionMinor) |
70 | { |
71 | QQmlMetaType::registerModule(uri, versionMajor, versionMinor); |
72 | } |
73 | |
74 | //From qqml.h |
75 | int qmlTypeId(const char *uri, int versionMajor, int versionMinor, const char *qmlName) |
76 | { |
77 | return QQmlMetaType::typeId(uri, versionMajor, versionMinor, qmlName); |
78 | } |
79 | |
80 | // From qqmlprivate.h |
81 | QObject *QQmlPrivate::RegisterSingletonFunctor::operator()(QQmlEngine *qeng, QJSEngine *) |
82 | { |
83 | if (!m_object) { |
84 | QQmlError error; |
85 | error.setDescription(QLatin1String("The registered singleton has already been deleted. Ensure that it outlives the engine." )); |
86 | QQmlEnginePrivate::get(e: qeng)->warning(qeng, error); |
87 | return nullptr; |
88 | } |
89 | |
90 | if (qeng->thread() != m_object->thread()) { |
91 | QQmlError error; |
92 | error.setDescription(QLatin1String("Registered object must live in the same thread as the engine it was registered with" )); |
93 | QQmlEnginePrivate::get(e: qeng)->warning(qeng, error); |
94 | return nullptr; |
95 | } |
96 | if (alreadyCalled) { |
97 | QQmlError error; |
98 | error.setDescription(QLatin1String("Singleton registered by registerSingletonInstance must only be accessed from one engine" )); |
99 | QQmlEnginePrivate::get(e: qeng)->warning(qeng, error); |
100 | return nullptr; |
101 | } |
102 | alreadyCalled = true; |
103 | qeng->setObjectOwnership(m_object, QQmlEngine::CppOwnership); |
104 | return m_object; |
105 | }; |
106 | |
107 | static QVector<int> availableRevisions(const QMetaObject *metaObject) |
108 | { |
109 | QVector<int> revisions; |
110 | if (!metaObject) |
111 | return revisions; |
112 | const int propertyOffset = metaObject->propertyOffset(); |
113 | const int propertyCount = metaObject->propertyCount(); |
114 | for (int propertyIndex = propertyOffset, propertyEnd = propertyOffset + propertyCount; |
115 | propertyIndex < propertyEnd; ++propertyIndex) { |
116 | const QMetaProperty property = metaObject->property(index: propertyIndex); |
117 | if (int revision = property.revision()) |
118 | revisions.append(t: revision); |
119 | } |
120 | const int methodOffset = metaObject->methodOffset(); |
121 | const int methodCount = metaObject->methodCount(); |
122 | for (int methodIndex = methodOffset, methodEnd = methodOffset + methodCount; |
123 | methodIndex < methodEnd; ++methodIndex) { |
124 | const QMetaMethod method = metaObject->method(index: methodIndex); |
125 | if (int revision = method.revision()) |
126 | revisions.append(t: revision); |
127 | } |
128 | |
129 | // Need to also check parent meta objects, as their revisions are inherited. |
130 | if (const QMetaObject *superMeta = metaObject->superClass()) |
131 | revisions += availableRevisions(metaObject: superMeta); |
132 | |
133 | return revisions; |
134 | } |
135 | |
136 | /* |
137 | This method is "over generalized" to allow us to (potentially) register more types of things in |
138 | the future without adding exported symbols. |
139 | */ |
140 | int QQmlPrivate::qmlregister(RegistrationType type, void *data) |
141 | { |
142 | QQmlType dtype; |
143 | switch (type) { |
144 | case AutoParentRegistration: |
145 | return QQmlMetaType::registerAutoParentFunction( |
146 | autoparent: *reinterpret_cast<RegisterAutoParent *>(data)); |
147 | case QmlUnitCacheHookRegistration: |
148 | return QQmlMetaType::registerUnitCacheHook( |
149 | hookRegistration: *reinterpret_cast<RegisterQmlUnitCacheHook *>(data)); |
150 | case TypeAndRevisionsRegistration: { |
151 | const RegisterTypeAndRevisions &type = *reinterpret_cast<RegisterTypeAndRevisions *>(data); |
152 | const char *elementName = classElementName(metaObject: type.classInfoMetaObject); |
153 | const bool creatable = (elementName != nullptr) |
154 | && boolClassInfo(metaObject: type.classInfoMetaObject, key: "QML.Creatable" , defaultValue: true); |
155 | |
156 | const QString noCreateReason = creatable |
157 | ? QString() |
158 | : QString::fromUtf8(str: classInfo(metaObject: type.classInfoMetaObject, key: "QML.UncreatableReason" )); |
159 | RegisterType revisionRegistration = { |
160 | .version: 1, |
161 | .typeId: type.typeId, |
162 | .listId: type.listId, |
163 | .objectSize: creatable ? type.objectSize : 0, |
164 | .create: nullptr, |
165 | .noCreationReason: noCreateReason, |
166 | .uri: type.uri, |
167 | .versionMajor: type.versionMajor, |
168 | .versionMinor: -1, |
169 | .elementName: nullptr, |
170 | .metaObject: type.metaObject, |
171 | .attachedPropertiesFunction: type.attachedPropertiesFunction, |
172 | .attachedPropertiesMetaObject: type.attachedPropertiesMetaObject, |
173 | .parserStatusCast: type.parserStatusCast, |
174 | .valueSourceCast: type.valueSourceCast, |
175 | .valueInterceptorCast: type.valueInterceptorCast, |
176 | .extensionObjectCreate: type.extensionObjectCreate, |
177 | .extensionMetaObject: type.extensionMetaObject, |
178 | .customParser: nullptr, |
179 | .revision: -1 |
180 | }; |
181 | |
182 | const int added = intClassInfo(metaObject: type.classInfoMetaObject, key: "QML.AddedInMinorVersion" , defaultValue: 0); |
183 | const int removed = intClassInfo(metaObject: type.classInfoMetaObject, key: "QML.RemovedInMinorVersion" , defaultValue: -1); |
184 | |
185 | auto revisions = availableRevisions(metaObject: type.metaObject); |
186 | revisions.append(t: qMax(a: added, b: 0)); |
187 | if (type.attachedPropertiesMetaObject) |
188 | revisions += availableRevisions(metaObject: type.attachedPropertiesMetaObject); |
189 | |
190 | std::sort(first: revisions.begin(), last: revisions.end()); |
191 | const auto it = std::unique(first: revisions.begin(), last: revisions.end()); |
192 | revisions.erase(abegin: it, aend: revisions.end()); |
193 | |
194 | const bool typeWasRemoved = removed >= added; |
195 | for (int revision : revisions) { |
196 | if (revision < added) |
197 | continue; |
198 | |
199 | // When removed, we still add revisions, but anonymous ones |
200 | if (typeWasRemoved && revision >= removed) { |
201 | revisionRegistration.elementName = nullptr; |
202 | revisionRegistration.create = nullptr; |
203 | } else { |
204 | revisionRegistration.elementName = elementName; |
205 | revisionRegistration.create = creatable ? type.create : nullptr; |
206 | } |
207 | |
208 | // Equivalent of qmlRegisterRevision<T, revision>(...) |
209 | revisionRegistration.versionMinor = revision; |
210 | revisionRegistration.revision = revision; |
211 | revisionRegistration.customParser = type.customParserFactory(); |
212 | |
213 | qmlregister(type: TypeRegistration, data: &revisionRegistration); |
214 | } |
215 | break; |
216 | } |
217 | case SingletonAndRevisionsRegistration: { |
218 | const RegisterSingletonTypeAndRevisions &type |
219 | = *reinterpret_cast<RegisterSingletonTypeAndRevisions *>(data); |
220 | const char *elementName = classElementName(metaObject: type.classInfoMetaObject); |
221 | RegisterSingletonType revisionRegistration = { |
222 | .version: QmlCurrentSingletonTypeRegistrationVersion, |
223 | .uri: type.uri, |
224 | .versionMajor: type.versionMajor, |
225 | .versionMinor: -1, |
226 | .typeName: elementName, |
227 | |
228 | .scriptApi: type.scriptApi, |
229 | .qobjectApi: nullptr, |
230 | .instanceMetaObject: type.instanceMetaObject, |
231 | .typeId: type.typeId, |
232 | .revision: -1, |
233 | |
234 | .generalizedQobjectApi: type.generalizedQobjectApi |
235 | }; |
236 | |
237 | const int added = intClassInfo(metaObject: type.classInfoMetaObject, key: "QML.AddedInMinorVersion" , defaultValue: 0); |
238 | const int removed = intClassInfo(metaObject: type.classInfoMetaObject, key: "QML.RemovedInMinorVersion" , defaultValue: -1); |
239 | |
240 | auto revisions = availableRevisions(metaObject: type.instanceMetaObject); |
241 | revisions.append(t: qMax(a: added, b: 0)); |
242 | |
243 | std::sort(first: revisions.begin(), last: revisions.end()); |
244 | const auto it = std::unique(first: revisions.begin(), last: revisions.end()); |
245 | revisions.erase(abegin: it, aend: revisions.end()); |
246 | |
247 | const bool typeWasRemoved = removed >= added; |
248 | for (int revision : qAsConst(t&: revisions)) { |
249 | if (revision < added) |
250 | continue; |
251 | |
252 | // When removed, we still add revisions, but anonymous ones |
253 | if (typeWasRemoved && revision >= removed) { |
254 | revisionRegistration.typeName = nullptr; |
255 | revisionRegistration.scriptApi = nullptr; |
256 | revisionRegistration.generalizedQobjectApi = nullptr; |
257 | } else { |
258 | revisionRegistration.typeName = elementName; |
259 | revisionRegistration.scriptApi = type.scriptApi; |
260 | revisionRegistration.generalizedQobjectApi = type.generalizedQobjectApi; |
261 | } |
262 | |
263 | // Equivalent of qmlRegisterRevision<T, revision>(...) |
264 | revisionRegistration.versionMinor = revision; |
265 | revisionRegistration.revision = revision; |
266 | |
267 | qmlregister(type: SingletonRegistration, data: &revisionRegistration); |
268 | } |
269 | break; |
270 | } |
271 | case TypeRegistration: |
272 | dtype = QQmlMetaType::registerType(type: *reinterpret_cast<RegisterType *>(data)); |
273 | break; |
274 | case InterfaceRegistration: |
275 | dtype = QQmlMetaType::registerInterface(type: *reinterpret_cast<RegisterInterface *>(data)); |
276 | break; |
277 | case SingletonRegistration: |
278 | dtype = QQmlMetaType::registerSingletonType(type: *reinterpret_cast<RegisterSingletonType *>(data)); |
279 | break; |
280 | case CompositeRegistration: |
281 | dtype = QQmlMetaType::registerCompositeType(type: *reinterpret_cast<RegisterCompositeType *>(data)); |
282 | break; |
283 | case CompositeSingletonRegistration: |
284 | dtype = QQmlMetaType::registerCompositeSingletonType(type: *reinterpret_cast<RegisterCompositeSingletonType *>(data)); |
285 | break; |
286 | default: |
287 | return -1; |
288 | } |
289 | |
290 | if (!dtype.isValid()) |
291 | return -1; |
292 | |
293 | QQmlMetaType::registerUndeletableType(dtype); |
294 | return dtype.index(); |
295 | } |
296 | |
297 | void QQmlPrivate::qmlunregister(RegistrationType type, quintptr data) |
298 | { |
299 | switch (type) { |
300 | case AutoParentRegistration: |
301 | QQmlMetaType::unregisterAutoParentFunction(function: reinterpret_cast<AutoParentFunction>(data)); |
302 | break; |
303 | case QmlUnitCacheHookRegistration: |
304 | QQmlMetaType::removeCachedUnitLookupFunction( |
305 | handler: reinterpret_cast<QmlUnitCacheLookupFunction>(data)); |
306 | break; |
307 | case TypeRegistration: |
308 | case InterfaceRegistration: |
309 | case SingletonRegistration: |
310 | case CompositeRegistration: |
311 | case CompositeSingletonRegistration: |
312 | QQmlMetaType::unregisterType(type: data); |
313 | break; |
314 | case TypeAndRevisionsRegistration: |
315 | case SingletonAndRevisionsRegistration: |
316 | // Currently unnecessary. We'd need a special data structure to hold |
317 | // URI + majorVersion and then we'd iterate the minor versions, look up the |
318 | // associated QQmlType objects by uri/elementName/major/minor and qmlunregister |
319 | // each of them. |
320 | Q_UNREACHABLE(); |
321 | break; |
322 | } |
323 | } |
324 | |
325 | namespace QQmlPrivate { |
326 | template<> |
327 | void qmlRegisterTypeAndRevisions<QQmlTypeNotAvailable, void>( |
328 | const char *uri, int versionMajor, const QMetaObject *classInfoMetaObject) |
329 | { |
330 | using T = QQmlTypeNotAvailable; |
331 | |
332 | QML_GETTYPENAMES |
333 | |
334 | RegisterTypeAndRevisions type = { |
335 | .version: 0, |
336 | .typeId: qRegisterNormalizedMetaType<T *>(normalizedTypeName: pointerName.constData()), |
337 | .listId: qRegisterNormalizedMetaType<QQmlListProperty<T> >(normalizedTypeName: listName.constData()), |
338 | .objectSize: 0, |
339 | .create: nullptr, |
340 | |
341 | .uri: uri, |
342 | .versionMajor: versionMajor, |
343 | |
344 | .metaObject: &QQmlTypeNotAvailable::staticMetaObject, |
345 | .classInfoMetaObject: classInfoMetaObject, |
346 | |
347 | .attachedPropertiesFunction: attachedPropertiesFunc<T>(), |
348 | .attachedPropertiesMetaObject: attachedPropertiesMetaObject<T>(), |
349 | |
350 | .parserStatusCast: StaticCastSelector<T, QQmlParserStatus>::cast(), |
351 | .valueSourceCast: StaticCastSelector<T, QQmlPropertyValueSource>::cast(), |
352 | .valueInterceptorCast: StaticCastSelector<T, QQmlPropertyValueInterceptor>::cast(), |
353 | |
354 | .extensionObjectCreate: nullptr, .extensionMetaObject: nullptr, .customParserFactory: qmlCreateCustomParser<T> |
355 | }; |
356 | |
357 | qmlregister(type: TypeAndRevisionsRegistration, data: &type); |
358 | } |
359 | } |
360 | |
361 | QT_END_NAMESPACE |
362 | |