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 | #ifndef QQMLIMPORT_P_H |
5 | #define QQMLIMPORT_P_H |
6 | |
7 | #include <QtCore/qurl.h> |
8 | #include <QtCore/qcoreapplication.h> |
9 | #include <QtCore/qloggingcategory.h> |
10 | #include <QtCore/qset.h> |
11 | #include <QtCore/qstringlist.h> |
12 | #include <QtQml/qqmlengine.h> |
13 | #include <QtQml/qqmlerror.h> |
14 | #include <QtQml/qqmlfile.h> |
15 | #include <private/qqmldirparser_p.h> |
16 | #include <private/qqmltype_p.h> |
17 | #include <private/qstringhash_p.h> |
18 | #include <private/qv4compileddata_p.h> |
19 | #include <private/qfieldlist_p.h> |
20 | |
21 | // |
22 | // W A R N I N G |
23 | // ------------- |
24 | // |
25 | // This file is not part of the Qt API. It exists purely as an |
26 | // implementation detail. This header file may change from version to |
27 | // version without notice, or even be removed. |
28 | // |
29 | // We mean it. |
30 | // |
31 | |
32 | QT_BEGIN_NAMESPACE |
33 | |
34 | class QQmlTypeNameCache; |
35 | class QQmlEngine; |
36 | class QDir; |
37 | class QQmlImportNamespace; |
38 | class QQmlImportDatabase; |
39 | class QQmlTypeLoader; |
40 | class QQmlTypeLoaderQmldirContent; |
41 | class QTypeRevision; |
42 | |
43 | const QLoggingCategory &lcQmlImport(); |
44 | |
45 | namespace QQmlImport { |
46 | enum RecursionRestriction { PreventRecursion, AllowRecursion }; |
47 | } |
48 | |
49 | struct QQmlImportInstance |
50 | { |
51 | enum Precedence { |
52 | Lowest = std::numeric_limits<quint8>::max(), |
53 | Implicit = Lowest / 2, |
54 | Highest = 0, |
55 | }; |
56 | |
57 | QString uri; // e.g. QtQuick |
58 | QString url; // the base path of the import |
59 | QQmlType containingType; // points to the containing type for inline components |
60 | QTypeRevision version; // the version imported |
61 | |
62 | bool isLibrary; // true means that this is not a file import |
63 | |
64 | // not covered by precedence. You can set a component as implicitly imported after the fact. |
65 | bool implicitlyImported = false; |
66 | bool isInlineComponent = false; |
67 | |
68 | quint8 precedence = 0; |
69 | |
70 | QQmlDirComponents qmlDirComponents; // a copy of the components listed in the qmldir |
71 | QQmlDirScripts qmlDirScripts; // a copy of the scripts in the qmldir |
72 | |
73 | bool setQmldirContent(const QString &resolvedUrl, const QQmlTypeLoaderQmldirContent &qmldir, |
74 | QQmlImportNamespace *nameSpace, QList<QQmlError> *errors); |
75 | |
76 | static QQmlDirScripts getVersionedScripts(const QQmlDirScripts &qmldirscripts, |
77 | QTypeRevision version); |
78 | |
79 | bool resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef &type, |
80 | QTypeRevision *version_return, QQmlType* type_return, |
81 | const QString *base = nullptr, bool *typeRecursionDetected = nullptr, |
82 | QQmlType::RegistrationType = QQmlType::AnyRegistrationType, |
83 | QQmlImport::RecursionRestriction recursionRestriction = QQmlImport::PreventRecursion, |
84 | QList<QQmlError> *errors = nullptr) const; |
85 | }; |
86 | |
87 | class QQmlImportNamespace |
88 | { |
89 | public: |
90 | QQmlImportNamespace() : nextNamespace(nullptr) {} |
91 | ~QQmlImportNamespace() { qDeleteAll(c: imports); } |
92 | |
93 | QList<QQmlImportInstance *> imports; |
94 | |
95 | QQmlImportInstance *findImport(const QString &uri) const; |
96 | |
97 | bool resolveType(QQmlTypeLoader *typeLoader, const QHashedStringRef& type, |
98 | QTypeRevision *version_return, QQmlType* type_return, |
99 | const QString *base = nullptr, QList<QQmlError> *errors = nullptr, |
100 | QQmlType::RegistrationType registrationType = QQmlType::AnyRegistrationType, |
101 | bool *typeRecursionDeteced = nullptr); |
102 | |
103 | // Prefix when used as a qualified import. Otherwise empty. |
104 | QHashedString prefix; |
105 | |
106 | // Used by QQmlImports::m_qualifiedSets |
107 | // set to this in unqualifiedSet to indicate that the lists of imports needs |
108 | // to be sorted when an inline component import was added |
109 | // We can't use flag pointer, as that does not work with QFieldList |
110 | QQmlImportNamespace *nextNamespace = nullptr; |
111 | bool needsSorting() const { return nextNamespace == this; } |
112 | void setNeedsSorting(bool needsSorting) |
113 | { |
114 | Q_ASSERT(nextNamespace == this || nextNamespace == nullptr); |
115 | nextNamespace = needsSorting ? this : nullptr; |
116 | } |
117 | }; |
118 | |
119 | class Q_QML_PRIVATE_EXPORT QQmlImports : public QQmlRefCounted<QQmlImports> |
120 | { |
121 | Q_DISABLE_COPY_MOVE(QQmlImports) |
122 | public: |
123 | enum ImportVersion { FullyVersioned, PartiallyVersioned, Unversioned }; |
124 | |
125 | enum ImportFlag : quint8 { |
126 | ImportNoFlag = 0x0, |
127 | ImportIncomplete = 0x1, |
128 | }; |
129 | Q_DECLARE_FLAGS(ImportFlags, ImportFlag) |
130 | |
131 | QQmlImports(QQmlTypeLoader *loader) : m_typeLoader(loader) {} |
132 | ~QQmlImports() |
133 | { |
134 | while (QQmlImportNamespace *ns = m_qualifiedSets.takeFirst()) |
135 | delete ns; |
136 | } |
137 | |
138 | void setBaseUrl(const QUrl &url, const QString &urlString = QString()); |
139 | QUrl baseUrl() const { return m_baseUrl; } |
140 | |
141 | bool resolveType( |
142 | const QHashedStringRef &type, QQmlType *type_return, QTypeRevision *version_return, |
143 | QQmlImportNamespace **ns_return, QList<QQmlError> *errors = nullptr, |
144 | QQmlType::RegistrationType registrationType = QQmlType::AnyRegistrationType, |
145 | bool *typeRecursionDetected = nullptr) const; |
146 | |
147 | QTypeRevision addImplicitImport( |
148 | QQmlImportDatabase *importDb, QString *localQmldir, QList<QQmlError> *errors) |
149 | { |
150 | Q_ASSERT(errors); |
151 | qCDebug(lcQmlImport) << "addImplicitImport:" << qPrintable(baseUrl().toString()); |
152 | |
153 | const ImportFlags flags = |
154 | ImportFlags(!isLocal(url: baseUrl()) ? ImportIncomplete : ImportNoFlag); |
155 | return addFileImport( |
156 | importDb, uri: QLatin1String("." ), prefix: QString(), version: QTypeRevision(), |
157 | flags, precedence: QQmlImportInstance::Implicit, localQmldir, errors); |
158 | } |
159 | |
160 | bool addInlineComponentImport( |
161 | QQmlImportInstance *const importInstance, const QString &name, const QUrl importUrl, |
162 | QQmlType containingType); |
163 | |
164 | QTypeRevision addFileImport( |
165 | QQmlImportDatabase *importDb, const QString &uri, const QString &prefix, |
166 | QTypeRevision version, ImportFlags flags, quint16 precedence, QString *localQmldir, |
167 | QList<QQmlError> *errors); |
168 | |
169 | QTypeRevision addLibraryImport( |
170 | QQmlImportDatabase *importDb, const QString &uri, const QString &prefix, |
171 | QTypeRevision version, const QString &qmldirIdentifier, const QString &qmldirUrl, |
172 | ImportFlags flags, quint16 precedence, QList<QQmlError> *errors); |
173 | |
174 | QTypeRevision updateQmldirContent( |
175 | QQmlImportDatabase *importDb, const QString &uri, const QString &prefix, |
176 | const QString &qmldirIdentifier, const QString &qmldirUrl, QList<QQmlError> *errors); |
177 | |
178 | void populateCache(QQmlTypeNameCache *cache) const; |
179 | |
180 | struct ScriptReference |
181 | { |
182 | QString nameSpace; |
183 | QString qualifier; |
184 | QUrl location; |
185 | }; |
186 | |
187 | QList<ScriptReference> resolvedScripts() const; |
188 | |
189 | struct CompositeSingletonReference |
190 | { |
191 | QString typeName; |
192 | QString prefix; |
193 | QTypeRevision version; |
194 | }; |
195 | |
196 | QList<CompositeSingletonReference> resolvedCompositeSingletons() const; |
197 | |
198 | static QStringList completeQmldirPaths( |
199 | const QString &uri, const QStringList &basePaths, QTypeRevision version); |
200 | |
201 | static QString versionString(QTypeRevision version, ImportVersion importVersion); |
202 | |
203 | static bool isLocal(const QString &url) |
204 | { |
205 | return !QQmlFile::urlToLocalFileOrQrc(url).isEmpty(); |
206 | } |
207 | |
208 | static bool isLocal(const QUrl &url) |
209 | { |
210 | return !QQmlFile::urlToLocalFileOrQrc(url).isEmpty(); |
211 | } |
212 | |
213 | static QUrl urlFromLocalFileOrQrcOrUrl(const QString &); |
214 | |
215 | static void setDesignerSupportRequired(bool b); |
216 | |
217 | static QTypeRevision validVersion(QTypeRevision version = QTypeRevision()); |
218 | |
219 | private: |
220 | friend class QQmlImportDatabase; |
221 | |
222 | QQmlImportNamespace *importNamespace(const QString &prefix); |
223 | |
224 | bool resolveType( |
225 | const QHashedStringRef &type, QTypeRevision *version_return, QQmlType *type_return, |
226 | QList<QQmlError> *errors, QQmlType::RegistrationType registrationType, |
227 | bool *typeRecursionDetected = nullptr) const; |
228 | |
229 | QQmlImportNamespace *findQualifiedNamespace(const QHashedStringRef &) const; |
230 | |
231 | static QTypeRevision matchingQmldirVersion( |
232 | const QQmlTypeLoaderQmldirContent &qmldir, const QString &uri, |
233 | QTypeRevision version, QList<QQmlError> *errors); |
234 | |
235 | QTypeRevision importExtension( |
236 | const QString &uri, QTypeRevision version, QQmlImportDatabase *database, |
237 | const QQmlTypeLoaderQmldirContent *qmldir, QList<QQmlError> *errors); |
238 | |
239 | bool getQmldirContent( |
240 | const QString &qmldirIdentifier, const QString &uri, QQmlTypeLoaderQmldirContent *qmldir, |
241 | QList<QQmlError> *errors); |
242 | |
243 | QString resolvedUri(const QString &dir_arg, QQmlImportDatabase *database); |
244 | |
245 | QQmlImportInstance *addImportToNamespace( |
246 | QQmlImportNamespace *nameSpace, const QString &uri, const QString &url, |
247 | QTypeRevision version, QV4::CompiledData::Import::ImportType type, |
248 | QList<QQmlError> *errors, quint16 precedence); |
249 | |
250 | QUrl m_baseUrl; |
251 | QString m_base; |
252 | |
253 | // storage of data related to imports without a namespace |
254 | // TODO: This needs to be mutable because QQmlImportNamespace likes to sort itself on |
255 | // resolveType(). Therefore, QQmlImportNamespace::resolveType() is not const. |
256 | // There should be a better way to do this. |
257 | mutable QQmlImportNamespace m_unqualifiedset; |
258 | |
259 | // storage of data related to imports with a namespace |
260 | QFieldList<QQmlImportNamespace, &QQmlImportNamespace::nextNamespace> m_qualifiedSets; |
261 | |
262 | QQmlTypeLoader *m_typeLoader = nullptr; |
263 | }; |
264 | |
265 | Q_DECLARE_OPERATORS_FOR_FLAGS(QQmlImports::ImportFlags) |
266 | |
267 | class Q_QML_PRIVATE_EXPORT QQmlImportDatabase |
268 | { |
269 | Q_DECLARE_TR_FUNCTIONS(QQmlImportDatabase) |
270 | public: |
271 | enum PathType { Local, Remote, LocalOrRemote }; |
272 | |
273 | enum LocalQmldirSearchLocation { |
274 | QmldirFileAndCache, |
275 | QmldirCacheOnly, |
276 | }; |
277 | |
278 | enum LocalQmldirResult { |
279 | QmldirFound, |
280 | QmldirNotFound, |
281 | QmldirInterceptedToRemote, |
282 | QmldirRejected |
283 | }; |
284 | |
285 | QQmlImportDatabase(QQmlEngine *); |
286 | ~QQmlImportDatabase() { clearDirCache(); } |
287 | |
288 | bool removeDynamicPlugin(const QString &pluginId); |
289 | QStringList dynamicPlugins() const; |
290 | |
291 | QStringList importPathList(PathType type = LocalOrRemote) const; |
292 | void setImportPathList(const QStringList &paths); |
293 | void addImportPath(const QString& dir); |
294 | |
295 | QStringList pluginPathList() const { return filePluginPath; } |
296 | void setPluginPathList(const QStringList &paths); |
297 | |
298 | void addPluginPath(const QString& path); |
299 | |
300 | template<typename Callback> |
301 | LocalQmldirResult locateLocalQmldir( |
302 | const QString &uri, QTypeRevision version, LocalQmldirSearchLocation location, |
303 | const Callback &callback); |
304 | |
305 | static QTypeRevision lockModule(const QString &uri, const QString &typeNamespace, |
306 | QTypeRevision version, QList<QQmlError> *errors); |
307 | |
308 | private: |
309 | friend class QQmlImports; |
310 | friend class QQmlPluginImporter; |
311 | |
312 | QString absoluteFilePath(const QString &path) const; |
313 | void clearDirCache(); |
314 | |
315 | struct QmldirCache { |
316 | QTypeRevision version; |
317 | QString qmldirFilePath; |
318 | QString qmldirPathUrl; |
319 | QmldirCache *next; |
320 | }; |
321 | // Maps from an import to a linked list of qmldir info. |
322 | // Used in QQmlImports::locateQmldir() |
323 | QStringHash<QmldirCache *> qmldirCache; |
324 | |
325 | // XXX thread |
326 | QStringList filePluginPath; |
327 | QStringList fileImportPath; |
328 | |
329 | QSet<QString> modulesForWhichPluginsHaveBeenLoaded; |
330 | QSet<QString> initializedPlugins; |
331 | QQmlEngine *engine; |
332 | }; |
333 | |
334 | template<typename Callback> |
335 | QQmlImportDatabase::LocalQmldirResult QQmlImportDatabase::locateLocalQmldir( |
336 | const QString &uri, QTypeRevision version, |
337 | QQmlImportDatabase::LocalQmldirSearchLocation location, const Callback &callback) |
338 | { |
339 | // Check cache first |
340 | |
341 | LocalQmldirResult result = QmldirNotFound; |
342 | QmldirCache *cacheTail = nullptr; |
343 | |
344 | QmldirCache **cachePtr = qmldirCache.value(uri); |
345 | QmldirCache *cacheHead = cachePtr ? *cachePtr : nullptr; |
346 | if (cacheHead) { |
347 | cacheTail = cacheHead; |
348 | do { |
349 | if (cacheTail->version == version) { |
350 | if (cacheTail->qmldirFilePath.isEmpty()) { |
351 | return cacheTail->qmldirPathUrl.isEmpty() |
352 | ? QmldirNotFound |
353 | : QmldirInterceptedToRemote; |
354 | } |
355 | if (callback(cacheTail->qmldirFilePath, cacheTail->qmldirPathUrl)) |
356 | return QmldirFound; |
357 | result = QmldirRejected; |
358 | } |
359 | } while (cacheTail->next && (cacheTail = cacheTail->next)); |
360 | } |
361 | |
362 | |
363 | // Do not try to construct the cache if it already had any entries for the URI. |
364 | // Otherwise we might duplicate cache entries. |
365 | if (location == QmldirCacheOnly || result != QmldirNotFound) |
366 | return result; |
367 | |
368 | const bool hasInterceptors = !engine->urlInterceptors().isEmpty(); |
369 | |
370 | // Interceptor might redirect remote files to local ones. |
371 | QStringList localImportPaths = importPathList(type: hasInterceptors ? LocalOrRemote : Local); |
372 | |
373 | // Search local import paths for a matching version |
374 | const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths( |
375 | uri, basePaths: localImportPaths, version); |
376 | |
377 | QString qmldirAbsoluteFilePath; |
378 | for (QString qmldirPath : qmlDirPaths) { |
379 | if (hasInterceptors) { |
380 | const QUrl intercepted = engine->interceptUrl( |
381 | url: QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath), |
382 | type: QQmlAbstractUrlInterceptor::QmldirFile); |
383 | qmldirPath = QQmlFile::urlToLocalFileOrQrc(intercepted); |
384 | if (result != QmldirInterceptedToRemote |
385 | && qmldirPath.isEmpty() |
386 | && !QQmlFile::isLocalFile(url: intercepted)) { |
387 | result = QmldirInterceptedToRemote; |
388 | } |
389 | } |
390 | |
391 | qmldirAbsoluteFilePath = absoluteFilePath(path: qmldirPath); |
392 | if (!qmldirAbsoluteFilePath.isEmpty()) { |
393 | QString url; |
394 | const QString absolutePath = qmldirAbsoluteFilePath.left( |
395 | n: qmldirAbsoluteFilePath.lastIndexOf(c: u'/') + 1); |
396 | if (absolutePath.at(i: 0) == u':') { |
397 | url = QStringLiteral("qrc" ) + absolutePath; |
398 | } else { |
399 | url = QUrl::fromLocalFile(localfile: absolutePath).toString(); |
400 | // This handles the UNC path case as when the path is retrieved from the QUrl it |
401 | // will convert the host name from upper case to lower case. So the absoluteFilePath |
402 | // is changed at this point to make sure it will match later on in that case. |
403 | if (qmldirAbsoluteFilePath.startsWith(QStringLiteral("//" ))) { |
404 | qmldirAbsoluteFilePath = QUrl::fromLocalFile(localfile: qmldirAbsoluteFilePath) |
405 | .toString(options: QUrl::RemoveScheme); |
406 | } |
407 | } |
408 | |
409 | QmldirCache *cache = new QmldirCache; |
410 | cache->version = version; |
411 | cache->qmldirFilePath = qmldirAbsoluteFilePath; |
412 | cache->qmldirPathUrl = url; |
413 | cache->next = nullptr; |
414 | if (cacheTail) |
415 | cacheTail->next = cache; |
416 | else |
417 | qmldirCache.insert(uri, cache); |
418 | cacheTail = cache; |
419 | |
420 | if (result != QmldirFound) |
421 | result = callback(qmldirAbsoluteFilePath, url) ? QmldirFound : QmldirRejected; |
422 | |
423 | // Do not return here. Rather, construct the complete cache for this URI. |
424 | } |
425 | } |
426 | |
427 | // Nothing found? Add an empty cache entry to signal that for further requests. |
428 | if (result == QmldirNotFound || result == QmldirInterceptedToRemote) { |
429 | QmldirCache *cache = new QmldirCache; |
430 | cache->version = version; |
431 | cache->next = cacheHead; |
432 | if (result == QmldirInterceptedToRemote) { |
433 | // The actual value doesn't matter as long as it's not empty. |
434 | // We only use it to discern QmldirInterceptedToRemote from QmldirNotFound above. |
435 | cache->qmldirPathUrl = QStringLiteral("intercepted" ); |
436 | } |
437 | qmldirCache.insert(uri, cache); |
438 | |
439 | if (result == QmldirNotFound) { |
440 | qCDebug(lcQmlImport) |
441 | << "locateLocalQmldir:" << qPrintable(uri) << "module's qmldir file not found" ; |
442 | } |
443 | } else { |
444 | qCDebug(lcQmlImport) |
445 | << "locateLocalQmldir:" << qPrintable(uri) << "module's qmldir found at" |
446 | << qmldirAbsoluteFilePath; |
447 | } |
448 | |
449 | return result; |
450 | } |
451 | |
452 | void qmlClearEnginePlugins();// For internal use by qmlClearRegisteredProperties |
453 | |
454 | QT_END_NAMESPACE |
455 | |
456 | #endif // QQMLIMPORT_P_H |
457 | |
458 | |