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
32QT_BEGIN_NAMESPACE
33
34class QQmlTypeNameCache;
35class QQmlEngine;
36class QDir;
37class QQmlImportNamespace;
38class QQmlImportDatabase;
39class QQmlTypeLoader;
40class QQmlTypeLoaderQmldirContent;
41class QTypeRevision;
42
43const QLoggingCategory &lcQmlImport();
44
45namespace QQmlImport {
46 enum RecursionRestriction { PreventRecursion, AllowRecursion };
47}
48
49struct 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
87class QQmlImportNamespace
88{
89public:
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
119class Q_QML_PRIVATE_EXPORT QQmlImports : public QQmlRefCounted<QQmlImports>
120{
121 Q_DISABLE_COPY_MOVE(QQmlImports)
122public:
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
219private:
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
265Q_DECLARE_OPERATORS_FOR_FLAGS(QQmlImports::ImportFlags)
266
267class Q_QML_PRIVATE_EXPORT QQmlImportDatabase
268{
269 Q_DECLARE_TR_FUNCTIONS(QQmlImportDatabase)
270public:
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
308private:
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
334template<typename Callback>
335QQmlImportDatabase::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
452void qmlClearEnginePlugins();// For internal use by qmlClearRegisteredProperties
453
454QT_END_NAMESPACE
455
456#endif // QQMLIMPORT_P_H
457
458

source code of qtdeclarative/src/qml/qml/qqmlimport_p.h