Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmltypeloader.cpp
Go to the documentation of this file.
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// Qt-Security score:significant
4
5#include <private/qqmltypeloader_p.h>
6
7#include <private/qqmldirdata_p.h>
8#include <private/qqmlprofiler_p.h>
9#include <private/qqmlscriptblob_p.h>
10#include <private/qqmlscriptdata_p.h>
11#include <private/qqmlsourcecoordinate_p.h>
12#include <private/qqmltypedata_p.h>
13#include <private/qqmltypeloaderqmldircontent_p.h>
14#include <private/qqmltypeloaderthread_p.h>
15#include <private/qv4compiler_p.h>
16#include <private/qv4compilercontext_p.h>
17#include <private/qv4runtimecodegen_p.h>
18
19#include <QtQml/qqmlabstracturlinterceptor.h>
20#include <QtQml/qqmlengine.h>
21#include <QtQml/qqmlextensioninterface.h>
22#include <QtQml/qqmlfile.h>
23#include <QtQml/qqmlnetworkaccessmanagerfactory.h>
24
25#include <qtqml_tracepoints_p.h>
26
27#include <QtCore/qdir.h>
28#include <QtCore/qdirlisting.h>
29#include <QtCore/qfile.h>
30#include <QtCore/qlibraryinfo.h>
31#include <QtCore/qthread.h>
32
33#include <functional>
34
35#define ASSERT_LOADTHREAD()
36 Q_ASSERT(thread() && thread()->isThisThread())
37#define ASSERT_ENGINETHREAD()
38 Q_ASSERT(!engine()->jsEngine() || engine()->jsEngine()->thread()->isCurrentThread())
39
40QT_BEGIN_NAMESPACE
41
42Q_TRACE_POINT(qtqml, QQmlCompiling_entry, const QUrl &url)
43Q_TRACE_POINT(qtqml, QQmlCompiling_exit)
44
45/*!
46\class QQmlTypeLoader
47\brief The QQmlTypeLoader class abstracts loading files and their dependencies over the network.
48\internal
49
50The QQmlTypeLoader class is provided for the exclusive use of the QQmlTypeLoader class.
51
52Clients create QQmlDataBlob instances and submit them to the QQmlTypeLoader class
53through the QQmlTypeLoader::load() or QQmlTypeLoader::loadWithStaticData() methods.
54The loader then fetches the data over the network or from the local file system in an efficient way.
55QQmlDataBlob is an abstract class, so should always be specialized.
56
57Once data is received, the QQmlDataBlob::dataReceived() method is invoked on the blob. The
58derived class should use this callback to process the received data. Processing of the data can
59result in an error being set (QQmlDataBlob::setError()), or one or more dependencies being
60created (QQmlDataBlob::addDependency()). Dependencies are other QQmlDataBlob's that
61are required before processing can fully complete.
62
63To complete processing, the QQmlDataBlob::done() callback is invoked. done() is called when
64one of these three preconditions are met.
65
66\list 1
67\li The QQmlDataBlob has no dependencies.
68\li The QQmlDataBlob has an error set.
69\li All the QQmlDataBlob's dependencies are themselves "done()".
70\endlist
71
72Thus QQmlDataBlob::done() will always eventually be called, even if the blob has an error set.
73*/
74
75void QQmlTypeLoader::invalidate()
76{
78
79 shutdownThread();
80
81#if QT_CONFIG(qml_network)
82 // Need to delete the network replies after
83 // the loader thread is shutdown as it could be
84 // getting new replies while we clear them
85 QQmlTypeLoaderThreadDataPtr data(&m_data);
86 data->networkReplies.clear();
87#endif // qml_network
88}
89
90void QQmlTypeLoader::addUrlInterceptor(QQmlAbstractUrlInterceptor *urlInterceptor)
91{
93 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
94 data->urlInterceptors.append(urlInterceptor);
95}
96
97void QQmlTypeLoader::removeUrlInterceptor(QQmlAbstractUrlInterceptor *urlInterceptor)
98{
100 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
101 data->urlInterceptors.removeOne(urlInterceptor);
102}
103
104QList<QQmlAbstractUrlInterceptor *> QQmlTypeLoader::urlInterceptors() const
105{
107 QQmlTypeLoaderConfiguredDataConstPtr data(&m_data);
108 return data->urlInterceptors;
109}
110
111static QUrl doInterceptUrl(
112 const QUrl &url, QQmlAbstractUrlInterceptor::DataType type,
113 const QList<QQmlAbstractUrlInterceptor *> &urlInterceptors)
114{
115 QUrl result = url;
116 for (QQmlAbstractUrlInterceptor *interceptor : urlInterceptors)
117 result = interceptor->intercept(result, type);
118 return result;
119}
120
121QUrl QQmlTypeLoader::interceptUrl(const QUrl &url, QQmlAbstractUrlInterceptor::DataType type) const
122{
123 // Can be called from either thread, but only after interceptor setup is done.
124
125 QQmlTypeLoaderConfiguredDataConstPtr data(&m_data);
126 return doInterceptUrl(url, type, data->urlInterceptors);
127}
128
129bool QQmlTypeLoader::hasUrlInterceptors() const
130{
131 // Can be called from either thread, but only after interceptor setup is done.
132 QQmlTypeLoaderConfiguredDataConstPtr data(&m_data);
133 return !data->urlInterceptors.isEmpty();
134}
135
136#if QT_CONFIG(qml_debug)
137void QQmlTypeLoader::setProfiler(QQmlProfiler *profiler)
138{
139 ASSERT_ENGINETHREAD();
140
141 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
142 Q_ASSERT(!data->profiler);
143 data->profiler.reset(profiler);
144}
145#endif
146
147struct PlainLoader {
148 void loadThread(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
149 {
150 loader->loadThread(blob);
151 }
152 void load(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
153 {
154 loader->ensureThread()->load(blob);
155 }
156 void loadAsync(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
157 {
158 loader->ensureThread()->loadAsync(blob);
159 }
160};
161
164 StaticLoader(const QByteArray &data) : data(data) {}
165
166 void loadThread(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
167 {
168 loader->loadWithStaticDataThread(blob, data);
169 }
170 void load(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
171 {
172 loader->ensureThread()->loadWithStaticData(blob, data);
173 }
174 void loadAsync(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
175 {
176 loader->ensureThread()->loadWithStaticDataAsync(blob, data);
177 }
178};
179
182 CachedLoader(const QQmlPrivate::CachedQmlUnit *unit) : unit(unit) {}
183
184 void loadThread(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
185 {
186 loader->loadWithCachedUnitThread(blob, unit);
187 }
188 void load(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
189 {
190 loader->ensureThread()->loadWithCachedUnit(blob, unit);
191 }
192 void loadAsync(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
193 {
194 loader->ensureThread()->loadWithCachedUnitAsync(blob, unit);
195 }
196};
197
198template<typename Loader>
199void QQmlTypeLoader::doLoad(const Loader &loader, const QQmlDataBlob::Ptr &blob, Mode mode)
200{
201 // Can be called from either thread.
202#ifdef DATABLOB_DEBUG
203 qWarning("QQmlTypeLoader::doLoad(%s): %s thread", qPrintable(blob->urlString()),
204 (m_thread && m_thread->isThisThread()) ? "Compile" : "Engine");
205#endif
206 blob->startLoading();
207
208 if (QQmlTypeLoaderThread *t = thread(); t && t->isThisThread()) {
209 loader.loadThread(this, blob);
210 return;
211 }
212
213 if (mode == Asynchronous) {
214 blob->setIsAsync(true);
215 loader.loadAsync(this, blob);
216 return;
217 }
218
219 loader.load(this, blob);
220 if (blob->isCompleteOrError())
221 return;
222
223 if (mode == PreferSynchronous) {
224 blob->setIsAsync(true);
225 return;
226 }
227
228 Q_ASSERT(mode == Synchronous);
229 Q_ASSERT(thread());
230
231 QQmlTypeLoaderSharedDataConstPtr lock(&m_data);
232 do {
233 m_data.thread()->waitForNextMessage();
234 } while (!blob->isCompleteOrError());
235}
236
237/*!
238Load the provided \a blob from the network or filesystem.
239
240The loader must be locked.
241*/
242void QQmlTypeLoader::load(const QQmlDataBlob::Ptr &blob, Mode mode)
243{
244 // Can be called from either thread.
245 doLoad(PlainLoader(), blob, mode);
246}
247
248/*!
249Load the provided \a blob with \a data. The blob's URL is not used by the data loader in this case.
250
251The loader must be locked.
252*/
253void QQmlTypeLoader::loadWithStaticData(
254 const QQmlDataBlob::Ptr &blob, const QByteArray &data, Mode mode)
255{
256 // Can be called from either thread.
257 doLoad(StaticLoader(data), blob, mode);
258}
259
260void QQmlTypeLoader::loadWithCachedUnit(
261 const QQmlDataBlob::Ptr &blob, const QQmlPrivate::CachedQmlUnit *unit, Mode mode)
262{
263 // Can be called from either thread.
264 doLoad(CachedLoader(unit), blob, mode);
265}
266
267void QQmlTypeLoader::drop(const QQmlDataBlob::Ptr &blob)
268{
270
271 // We must not destroy a QQmlDataBlob from the main thread
272 // since it will shuffle its dependencies around.
273 // Therefore, if we're not on the type loader thread,
274 // we defer the destruction to the type loader thread.
275 if (QQmlTypeLoaderThread *t = thread(); t && !t->isThisThread())
276 t->drop(blob);
277}
278
279void QQmlTypeLoader::loadWithStaticDataThread(const QQmlDataBlob::Ptr &blob, const QByteArray &data)
280{
282
283 setData(blob, data, DataOrigin::Static);
284}
285
286void QQmlTypeLoader::loadWithCachedUnitThread(const QQmlDataBlob::Ptr &blob, const QQmlPrivate::CachedQmlUnit *unit)
287{
289
290 setCachedUnit(blob, unit);
291}
292
293void QQmlTypeLoader::loadThread(const QQmlDataBlob::Ptr &blob)
294{
296
297 if (blob->m_url.isEmpty()) {
298 QQmlError error;
299 error.setDescription(QLatin1String("Invalid null URL"));
300 blob->setError(error);
301 return;
302 }
303
304 if (QQmlFile::isSynchronous(blob->m_url)) {
305 const QString fileName = QQmlFile::urlToLocalFileOrQrc(blob->m_url);
306 if (fileName.isEmpty()) {
307 blob->setError(QLatin1String("URL cannot be represented as a local file: "
308 "unexpected NUL or percent-encoded directory separator "
309 "(%2F, or %5C on Windows)."));
310 return;
311 }
312
313 const bool exists = fileExists(fileName);
314 if (!exists && !isResource(fileName) && QFileInfo::exists(fileName)) {
315 // If the file doesn't exist at all, that's fine. It may be cached. If it's a case
316 // mismatch, though, we have to error out.
317 // NB: The resource file system is case-sensitive. No need to check there.
318 blob->setError(QLatin1String("File name case mismatch"));
319 return;
320 }
321
322 if (blob->setProgress(1.f) && blob->isAsync())
323 thread()->callDownloadProgressChanged(blob, 1.);
324
325 setData(blob, fileName, exists);
326
327 } else {
328#if QT_CONFIG(qml_network)
329 QNetworkReply *reply = thread()->networkAccessManager()->get(QNetworkRequest(blob->m_url));
330 QQmlTypeLoaderNetworkReplyProxy *nrp = thread()->networkReplyProxy();
331
332 QQmlTypeLoaderThreadDataPtr data(&m_data);
333 data->networkReplies.insert(reply, blob);
334
335 if (reply->isFinished()) {
336 nrp->manualFinished(reply);
337 } else {
338 QObject::connect(reply, &QNetworkReply::downloadProgress,
339 nrp, &QQmlTypeLoaderNetworkReplyProxy::downloadProgress);
340 QObject::connect(reply, &QNetworkReply::finished,
341 nrp, &QQmlTypeLoaderNetworkReplyProxy::finished);
342 }
343
344#ifdef DATABLOB_DEBUG
345 qWarning("QQmlDataBlob: requested %s", qPrintable(blob->urlString()));
346#endif // DATABLOB_DEBUG
347#endif // qml_network
348 }
349}
350
351#define DATALOADER_MAXIMUM_REDIRECT_RECURSION 16
352
353#if QT_CONFIG(qml_network)
354void QQmlTypeLoader::networkReplyFinished(QNetworkReply *reply)
355{
356 ASSERT_LOADTHREAD();
357
358 reply->deleteLater();
359
360 QQmlTypeLoaderThreadDataPtr data(&m_data);
361 QQmlRefPointer<QQmlDataBlob> blob = data->networkReplies.take(reply);
362
363 Q_ASSERT(blob);
364
365 blob->m_redirectCount++;
366
367 if (blob->m_redirectCount < DATALOADER_MAXIMUM_REDIRECT_RECURSION) {
368 QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
369 if (redirect.isValid()) {
370 QUrl url = reply->url().resolved(redirect.toUrl());
371 blob->m_finalUrl = url;
372 blob->m_finalUrlString.clear();
373
374 QNetworkReply *reply = thread()->networkAccessManager()->get(QNetworkRequest(url));
375 QObject *nrp = thread()->networkReplyProxy();
376 QObject::connect(reply, SIGNAL(finished()), nrp, SLOT(finished()));
377 data->networkReplies.insert(reply, std::move(blob));
378#ifdef DATABLOB_DEBUG
379 qWarning("QQmlDataBlob: redirected to %s", qPrintable(blob->finalUrlString()));
380#endif
381 return;
382 }
383 }
384
385 if (reply->error()) {
386 blob->networkError(reply->error());
387 } else {
388 QByteArray data = reply->readAll();
389 setData(blob, data, DataOrigin::Device);
390 }
391}
392
393void QQmlTypeLoader::networkReplyProgress(QNetworkReply *reply,
394 qint64 bytesReceived, qint64 bytesTotal)
395{
396 ASSERT_LOADTHREAD();
397
398 QQmlTypeLoaderThreadDataConstPtr data(&m_data);
399 const QQmlRefPointer<QQmlDataBlob> blob = data->networkReplies.value(reply);
400
401 Q_ASSERT(blob);
402
403 if (bytesTotal != 0) {
404 const qreal progress = (qreal(bytesReceived) / qreal(bytesTotal));
405 if (blob->setProgress(progress) && blob->isAsync())
406 thread()->callDownloadProgressChanged(blob, blob->progress());
407 }
408}
409#endif // qml_network
410
411/*! \internal
412Call the initializeEngine() method on \a iface. Used by QQmlTypeLoader to ensure it
413gets called in the correct thread.
414*/
415template<class Interface>
417 Interface *iface, QQmlTypeLoaderThread *thread, QQmlTypeLoaderLockedData *data,
418 const char *uri)
419{
420 // Can be called from either thread
421 // Must not touch engine if called from type loader thread
422
423 if (thread && thread->isThisThread())
424 thread->initializeEngine(iface, uri);
425 else
426 iface->initializeEngine(data->engine()->qmlEngine(), uri);
427}
428
429void QQmlTypeLoader::initializeEngine(QQmlEngineExtensionInterface *iface, const char *uri)
430{
431 // Can be called from either thread
432 doInitializeEngine(iface, thread(), &m_data, uri);
433}
434
435void QQmlTypeLoader::initializeEngine(QQmlExtensionInterface *iface, const char *uri)
436{
437 // Can be called from either thread
438 doInitializeEngine(iface, thread(), &m_data, uri);
439}
440
441/*!
442 * \internal
443 * Use the given \a data as source code for the given \a blob. \a origin states where the \a data
444 * came from and what we can do with it. DataOrigin::Static means that it's arbitrary static data
445 * passed by the user. We shall not produce a .qmlc file for it and we shall disregard any existing
446 * compilation units. DataOrigin::Device means that it was loaded from a file or other device and
447 * can be assumed to remain the same. We can use any caching mechanism to load a compilation unit
448 * for it and we can produce a .qmlc file for it.
449 */
450void QQmlTypeLoader::setData(const QQmlDataBlob::Ptr &blob, const QByteArray &data, DataOrigin origin)
451{
453
454 QQmlDataBlob::SourceCodeData d;
455 d.inlineSourceCode = QString::fromUtf8(data);
456 d.hasInlineSourceCode = true;
457 d.hasStaticData = (origin == DataOrigin::Static);
458 setData(blob, d);
459}
460
461void QQmlTypeLoader::setData(const QQmlDataBlob::Ptr &blob, const QString &fileName, bool exists)
462{
464
465 QQmlDataBlob::SourceCodeData d;
466 d.fileInfo = QFileInfo(fileName);
467 d.fileExists = exists;
468 setData(blob, d);
469}
470
471void QQmlTypeLoader::setData(const QQmlDataBlob::Ptr &blob, const QQmlDataBlob::SourceCodeData &d)
472{
474
475 Q_TRACE_SCOPE(QQmlCompiling, blob->url());
476 QQmlCompilingProfiler prof(profiler(), blob.data());
477
478 blob->m_inCallback = true;
479
480 blob->dataReceived(d);
481
482 if (!blob->isError() && !blob->isWaiting())
483 blob->allDependenciesDone();
484
485 blob->m_inCallback = false;
486
487 blob->tryDone();
488}
489
490void QQmlTypeLoader::setCachedUnit(const QQmlDataBlob::Ptr &blob, const QQmlPrivate::CachedQmlUnit *unit)
491{
493
494 Q_TRACE_SCOPE(QQmlCompiling, blob->url());
495 QQmlCompilingProfiler prof(profiler(), blob.data());
496
497 blob->m_inCallback = true;
498
499 blob->initializeFromCachedUnit(unit);
500
501 if (!blob->isError() && !blob->isWaiting())
502 blob->allDependenciesDone();
503
504 blob->m_inCallback = false;
505
506 blob->tryDone();
507}
508
509static bool isPathAbsolute(const QString &path)
510{
511#if defined(Q_OS_UNIX)
512 return (path.at(0) == QLatin1Char('/'));
513#else
514 QFileInfo fi(path);
515 return fi.isAbsolute();
516#endif
517}
518
519static bool isPathQrcOrAbsolute(const QString &path)
520{
521 return path.startsWith(u':') || isPathAbsolute(path);
522}
523
524// importPathList() and the qmldir candidate paths derived from it are a mixture of absolute
525// file system paths, qrc paths (":/...") and URLs (e.g. "qrc:/...", "file:///...", remote URLs,
526// or whatever a URL interceptor produced). cleanFilePath() and fileExists() only understand
527// paths, not URLs. Turn any local URL into its path here. Returns an empty string for empty
528// input or for anything that does not resolve to a local path (in particular remote URLs).
529static QString localPathForUrlOrPath(const QString &urlOrPath)
530{
531 if (urlOrPath.isEmpty() || isPathQrcOrAbsolute(urlOrPath))
532 return urlOrPath;
533 return QQmlFile::urlToLocalFileOrQrc(urlOrPath);
534}
535
536/*!
537 \internal
538*/
539QStringList QQmlTypeLoader::importPathList(PathType type) const
540{
541 QQmlTypeLoaderConfiguredDataConstPtr data(&m_data);
542 if (type == LocalOrRemote)
543 return data->importPaths;
544
545 QStringList list;
546 for (const QString &path : data->importPaths) {
547 // The import paths are a mixture of absolute file system paths and URLs. None of them
548 // starts with a bare ':' because addImportPath() rewrites qrc paths (":/foo") to
549 // "qrc:/foo" URLs before storing them. So an absolute-path check together with
550 // QQmlFile::isLocalFile() is enough to tell local entries (resolvable on the file
551 // system or in qrc) from remote ones.
552 if ((isPathAbsolute(path) || QQmlFile::isLocalFile(path)) == (type == Local))
553 list.append(path);
554 }
555
556 return list;
557}
558
559/*!
560 \internal
561*/
562void QQmlTypeLoader::addImportPath(const QString &path, AddPathMode mode)
563{
564 qCDebug(lcQmlImport) << "addImportPath:" << path;
565
566 if (path.isEmpty())
567 return;
568
569 QUrl url = QUrl(path);
570 QString cPath;
571
572 if (url.scheme() == QLatin1String("file")) {
573 cPath = QQmlFile::urlToLocalFileOrQrc(url);
574 } else if (path.startsWith(QLatin1Char(':'))) {
575 // qrc directory, e.g. :/foo
576 // need to convert to a qrc url, e.g. qrc:/foo
577 cPath = QLatin1String("qrc") + path;
578 cPath.replace(QLatin1Char('\\'), QLatin1Char('/'));
579 } else if (url.isRelative() ||
580 (url.scheme().size() == 1 && QFile::exists(path)) ) { // windows path
581 QDir dir = QDir(path);
582 cPath = dir.canonicalPath();
583 } else {
584 cPath = path;
585 cPath.replace(QLatin1Char('\\'), QLatin1Char('/'));
586 }
587
588 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
589 if (!cPath.isEmpty()) {
590 if (mode == PrependPath) {
591 // Prepending an existing path moves it to the front, to reflect
592 // its new, higher priority.
593 if (data->importPaths.contains(cPath))
594 data->importPaths.move(data->importPaths.indexOf(cPath), 0);
595 else
596 data->importPaths.prepend(cPath);
597 } else if (!data->importPaths.contains(cPath)) {
598 // Appending leaves an already known path untouched, so that we
599 // don't lower the priority of a path that was explicitly prepended.
600 data->importPaths.append(cPath);
601 }
602 }
603}
604
605/*!
606 \internal
607*/
608void QQmlTypeLoader::setImportPathList(const QStringList &paths)
609{
610 qCDebug(lcQmlImport) << "setImportPathList:" << paths;
611
612 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
613 data->importPaths.clear();
614 for (const QString &path : paths)
615 addImportPath(path, AppendPath);
616
617 // Our existing cached paths may have been invalidated
618 clearQmldirInfo();
619}
620
621
622/*!
623 \internal
624*/
625void QQmlTypeLoader::setPluginPathList(const QStringList &paths)
626{
627 qCDebug(lcQmlImport) << "setPluginPathList:" << paths;
628 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
629 data->pluginPaths = paths;
630}
631
632/*!
633 \internal
634*/
635void QQmlTypeLoader::addPluginPath(const QString& path, AddPathMode mode)
636{
637 qCDebug(lcQmlImport) << "addPluginPath:" << path;
638
639 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
640
641 QUrl url = QUrl(path);
642 QString canonicalPath = path;
643 if (url.isRelative() || url.scheme() == QLatin1String("file")
644 || (url.scheme().size() == 1 && QFile::exists(path)) ) { // windows path
645 QDir dir = QDir(path);
646 canonicalPath = dir.canonicalPath();
647 }
648
649 if (mode == PrependPath)
650 data->pluginPaths.prepend(canonicalPath);
651 else
652 data->pluginPaths.append(canonicalPath);
653}
654
655#if QT_CONFIG(qml_network)
656QQmlNetworkAccessManagerFactoryPtrConst QQmlTypeLoader::networkAccessManagerFactory() const
657{
658 ASSERT_ENGINETHREAD();
659 return QQmlNetworkAccessManagerFactoryPtrConst(&m_data);
660}
661
662void QQmlTypeLoader::setNetworkAccessManagerFactory(QQmlNetworkAccessManagerFactory *factory)
663{
664 ASSERT_ENGINETHREAD();
665 QQmlNetworkAccessManagerFactoryPtr(&m_data).reset(factory);
666}
667
668QNetworkAccessManager *QQmlTypeLoader::createNetworkAccessManager(QObject *parent) const
669{
670 // Can be called from both threads, or even from a WorkerScript
671
672 // TODO: Calling the user's create() method under the lock is quite rude.
673 // However, we've been doing so for a long time and stopping the
674 // practice would expose thread safety issues in user code.
675 // ### Qt7: Maybe change the factory interface to provide a different method
676 // that can be called without the lock.
677 if (const auto factory = QQmlNetworkAccessManagerFactoryPtrConst(&m_data))
678 return factory->create(parent);
679
680 return new QNetworkAccessManager(parent);
681}
682#endif // QT_CONFIG(qml_network)
683
684void QQmlTypeLoader::clearQmldirInfo()
685{
686 QQmlTypeLoaderThreadDataPtr data(&m_data);
687
688 auto itr = data->qmldirInfo.constBegin();
689 while (itr != data->qmldirInfo.constEnd()) {
690 const QQmlTypeLoaderThreadData::QmldirInfo *cache = *itr;
691 do {
692 const QQmlTypeLoaderThreadData::QmldirInfo *nextCache = cache->next;
693 delete cache;
694 cache = nextCache;
695 } while (cache);
696
697 ++itr;
698 }
699 data->qmldirInfo.clear();
700}
701
703 const QQmlTypeLoaderConfiguredDataPtr &data, QV4::ExecutionEngine *engine)
704{
705 data->diskCacheOptions = engine->diskCacheOptions();
706 data->isDebugging = engine->debugger() != nullptr;
707 data->initialized = true;
708}
709
710void QQmlTypeLoader::startThread()
711{
713
714 if (!m_data.thread()) {
715 // Re-read the relevant configuration values at the last possible moment before we start
716 // the thread. After the thread has been started, changing the configuration would result
717 // in UB. Therefore we can disregard this case. We need to re-read it because a preview
718 // or a debugger may have been connected in between.
719 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
720 initializeConfiguredData(data, m_data.engine());
721 m_data.createThread(this);
722 }
723}
724
725void QQmlTypeLoader::shutdownThread()
726{
728
729 if (m_data.thread())
730 m_data.deleteThread();
731}
732
733QQmlTypeLoader::Blob::PendingImport::PendingImport(
734 const QQmlRefPointer<Blob> &blob, const QV4::CompiledData::Import *import,
735 QQmlImports::ImportFlags flags)
736 : uri(blob->stringAt(import->uriIndex))
737 , qualifier(blob->stringAt(import->qualifierIndex))
738 , type(static_cast<QV4::CompiledData::Import::ImportType>(quint32(import->type)))
739 , location(import->location)
740 , flags(flags)
741 , version(import->version)
742 , searchVersion(import->version)
743{
744}
745
746QQmlTypeLoader::Blob::Blob(const QUrl &url, QQmlDataBlob::Type type, QQmlTypeLoader *loader)
747 : QQmlDataBlob(url, type, loader)
748 , m_importCache(new QQmlImports(), QQmlRefPointer<QQmlImports>::Adopt)
749{
750}
751
752QQmlTypeLoader::Blob::~Blob()
753{
754}
755
756bool QQmlTypeLoader::Blob::fetchQmldir(
757 const QUrl &url, const QQmlTypeLoader::Blob::PendingImportPtr &import, int priority,
758 QList<QQmlError> *errors)
759{
760 assertTypeLoaderThread();
761
762 QQmlRefPointer<QQmlQmldirData> data = typeLoader()->getQmldir(url);
763
764 data->setPriority(this, import, priority);
765
766 if (data->status() == Error) {
767 // This qmldir must not exist - which is not an error
768 return true;
769 } else if (data->status() == Complete) {
770 // This data is already available
771 return qmldirDataAvailable(data, errors);
772 }
773
774 // Wait for this data to become available
775 addDependency(data.data());
776 return true;
777}
778
779/*!
780 * \internal
781 * Import any qualified scripts of for \a import as listed in \a qmldir.
782 * Precondition is that \a import is actually qualified.
783 */
784void QQmlTypeLoader::Blob::importQmldirScripts(
785 const QQmlTypeLoader::Blob::PendingImportPtr &import,
786 const QQmlTypeLoaderQmldirContent &qmldir, const QUrl &qmldirUrl)
787{
788 assertTypeLoaderThread();
789
790 // A "prefer" directive in the qmldir redirects file lookups to another
791 // location, typically the compiled-in resources. Honor it here so that
792 // qualified scripts resolve from the same location as components and
793 // unqualified scripts, which inherit the redirect via the import URL set
794 // up by QQmlImports::addLibraryImport(). Otherwise a deployed module whose
795 // files only exist in the resources would try to load the script from a
796 // nonexistent file next to the qmldir on disk. See QTBUG-143877.
797 QQmlTypeLoaderQmldirContent redirectedQmldir = qmldir;
798 const QUrl scriptBaseUrl = redirectedQmldir.hasRedirection()
799 ? QUrl{QQmlImports::redirectQmldirContent(typeLoader(), &redirectedQmldir)}
800 : qmldirUrl;
801 const auto qmldirScripts = redirectedQmldir.scripts();
802 for (const QQmlDirParser::Script &script : qmldirScripts) {
803 const QUrl plainUrl = QUrl(script.fileName);
804 const QUrl scriptUrl = scriptBaseUrl.resolved(plainUrl);
805 QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl, plainUrl);
806
807 // Self-import via qmldir is OK-ish. We ignore it.
808 if (blob.data() == this)
809 continue;
810
811 addDependency(blob.data());
812 scriptImported(blob, import->location, script.nameSpace, import->qualifier);
813 }
814}
815
816template<typename URL>
818 QQmlTypeLoader::Blob *self,
819 const QQmlTypeLoader::Blob::PendingImportPtr &import, const QString &qmldirFilePath,
820 const URL &qmldirUrl)
821{
822 self->assertTypeLoaderThread();
823
824 const QQmlTypeLoaderQmldirContent qmldir = self->typeLoader()->qmldirContent(qmldirFilePath);
825 if (!import->qualifier.isEmpty())
826 self->importQmldirScripts(import, qmldir, QUrl(qmldirUrl));
827
828 if (qmldir.plugins().isEmpty()) {
829 // If the qmldir does not register a plugin, we might still have declaratively
830 // registered types (if we are dealing with an application instead of a library)
831 // We should use module name given in the qmldir rather than the one given by the
832 // import since the import may be a directory import.
833 auto module = QQmlMetaType::typeModule(qmldir.typeNamespace(), import->version);
834 if (!module)
835 QQmlMetaType::qmlRegisterModuleTypes(qmldir.typeNamespace());
836 // else: If the module already exists, the types must have been already registered
837 }
838}
839
841 const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
842{
843 QQmlError error;
844 QString reason = errors->front().description();
845 if (reason.size() > 512)
846 reason = reason.first(252) + QLatin1String("... ...") + reason.last(252);
847 if (import->version.hasMajorVersion()) {
848 error.setDescription(
849 QQmlImports::tr("module \"%1\" version %2.%3 cannot be imported because:\n%4")
850 .arg(import->uri, QString::number(import->version.majorVersion()),
851 import->version.hasMinorVersion()
852 ? QString::number(import->version.minorVersion())
853 : QLatin1String("x"),
854 reason));
855 } else {
856 error.setDescription(QQmlImports::tr("module \"%1\" cannot be imported because:\n%2")
857 .arg(import->uri, reason));
858 }
859 errors->prepend(error);
860}
861
862bool QQmlTypeLoader::Blob::handleLocalQmldirForImport(
863 const PendingImportPtr &import, const QString &qmldirFilePath,
864 const QString &qmldirUrl, QList<QQmlError> *errors)
865{
866 // This is a local library import
867 const QTypeRevision actualVersion = m_importCache->addLibraryImport(
868 typeLoader(), import->uri, import->qualifier, import->version, qmldirFilePath,
869 qmldirUrl, import->flags, import->precedence, errors);
870 if (!actualVersion.isValid())
871 return false;
872
873 // Use more specific version for dependencies if possible
874 if (actualVersion.hasMajorVersion())
875 import->version = actualVersion;
876
877 if (!loadImportDependencies(import, qmldirFilePath, import->flags, errors)) {
878 addDependencyImportError(import, errors);
879 return false;
880 }
881
882 postProcessQmldir(this, import, qmldirFilePath, qmldirUrl);
883 return true;
884}
885
886bool QQmlTypeLoader::Blob::updateQmldir(const QQmlRefPointer<QQmlQmldirData> &data, const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
887{
888 // TODO: Shouldn't this lock?
889
890 assertTypeLoaderThread();
891
892 QString qmldirIdentifier = data->urlString();
893 QString qmldirUrl = qmldirIdentifier.left(qmldirIdentifier.lastIndexOf(QLatin1Char('/')) + 1);
894
895 typeLoader()->setQmldirContent(qmldirIdentifier, data->content());
896
897 const QTypeRevision version = m_importCache->updateQmldirContent(
898 typeLoader(), import->uri, import->version, import->qualifier, qmldirIdentifier,
899 qmldirUrl, errors);
900 if (!version.isValid())
901 return false;
902
903 // Use more specific version for dependencies if possible
904 if (version.hasMajorVersion())
905 import->version = version;
906
907 if (!loadImportDependencies(import, qmldirIdentifier, import->flags, errors))
908 return false;
909
910 import->priority = 0;
911
912 // Release this reference at destruction
913 m_qmldirs << data;
914
915 postProcessQmldir(this, import, qmldirIdentifier, qmldirUrl);
916 return true;
917}
918
919bool QQmlTypeLoader::Blob::addScriptImport(const QQmlTypeLoader::Blob::PendingImportPtr &import)
920{
921 assertTypeLoaderThread();
922 const QUrl url(import->uri);
923 QQmlTypeLoader *loader = typeLoader();
924 QQmlRefPointer<QQmlScriptBlob> blob = loader->getScript(finalUrl().resolved(url), url);
925 addDependency(blob.data());
926 scriptImported(blob, import->location, import->qualifier, QString());
927 return true;
928}
929
930bool QQmlTypeLoader::Blob::addFileImport(const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
931{
932 assertTypeLoaderThread();
933 QQmlImports::ImportFlags flags;
934
935 QUrl importUrl(import->uri);
936 QString path = importUrl.path();
937 path.append(QLatin1String(path.endsWith(QLatin1Char('/')) ? "qmldir" : "/qmldir"));
938 importUrl.setPath(path);
939
940 // Can't resolve a relative URL if we don't know where we are
941 if (!finalUrl().isValid() && importUrl.isRelative()) {
942 QQmlError error;
943 error.setDescription(
944 QString::fromLatin1("Can't resolve relative qmldir URL %1 on invalid base URL")
945 .arg(importUrl.toString()));
946 errors->append(error);
947 return false;
948 }
949
950 QUrl qmldirUrl = finalUrl().resolved(importUrl);
951 if (!QQmlImports::isLocal(qmldirUrl)) {
952 // This is a remote file; the import is currently incomplete
953 flags = QQmlImports::ImportIncomplete;
954 }
955
956 const QTypeRevision version = m_importCache->addFileImport(
957 typeLoader(), import->uri, import->qualifier, import->version, flags,
958 import->precedence, nullptr, errors);
959 if (!version.isValid())
960 return false;
961
962 // Use more specific version for the qmldir if possible
963 if (version.hasMajorVersion())
964 import->version = version;
965
966 if (flags & QQmlImports::ImportIncomplete) {
967 if (!fetchQmldir(qmldirUrl, import, 1, errors))
968 return false;
969 } else {
970 const QString qmldirFilePath = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
971 if (!loadImportDependencies(import, qmldirFilePath, import->flags, errors))
972 return false;
973
974 postProcessQmldir(this, import, qmldirFilePath, qmldirUrl);
975 }
976
977 return true;
978}
979
980bool QQmlTypeLoader::Blob::addLibraryImport(const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
981{
982 assertTypeLoaderThread();
983
984 const LocalQmldirResult qmldirResult = typeLoader()->locateLocalQmldir(this, import, errors);
985 switch (qmldirResult) {
986 case QmldirFound:
987 return true;
988 case QmldirNotFound: {
989 if (!loadImportDependencies(import, QString(), import->flags, errors)) {
990 addDependencyImportError(import, errors);
991 return false;
992 }
993 break;
994 }
995 case QmldirInterceptedToRemote:
996 break;
997 case QmldirRejected:
998 return false;
999 }
1000
1001 // If there is a qmldir we cannot see, yet, then we have to wait.
1002 // The qmldir might contain import directives.
1003 // TODO: This should trigger on any potentially remote URLs, not only intercepted ones.
1004 // However, fixing this would open the door for follow-up problems while providing
1005 // rather limited benefits.
1006 if (qmldirResult != QmldirInterceptedToRemote && registerPendingTypes(import)) {
1007 if (m_importCache->addLibraryImport(
1008 typeLoader(), import->uri, import->qualifier, import->version, QString(),
1009 QString(), import->flags, import->precedence, errors).isValid()) {
1010 return true;
1011 }
1012
1013 return false;
1014 }
1015
1016 // We haven't yet resolved this import
1017 m_unresolvedImports << import;
1018
1019 // Add this library and request the possible locations for it
1020 const QTypeRevision version = m_importCache->addLibraryImport(
1021 typeLoader(), import->uri, import->qualifier, import->version, QString(),
1022 QString(), import->flags | QQmlImports::ImportIncomplete, import->precedence,
1023 errors);
1024
1025 if (!version.isValid())
1026 return false;
1027
1028 // Use more specific version for finding the qmldir if possible
1029 if (version.hasMajorVersion())
1030 import->version = version;
1031
1032 const bool hasInterceptors = m_typeLoader->hasUrlInterceptors();
1033
1034 // Query any network import paths for this library.
1035 // Interceptor might redirect local paths.
1036 QStringList remotePathList = typeLoader()->importPathList(
1037 hasInterceptors ? LocalOrRemote : Remote);
1038 if (!remotePathList.isEmpty()) {
1039 // Probe for all possible locations
1040 int priority = 0;
1041 const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(
1042 import->uri, remotePathList, import->searchVersion);
1043 for (const QString &qmldirPath : qmlDirPaths) {
1044 if (hasInterceptors) {
1045 QUrl url = m_typeLoader->interceptUrl(
1046 QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
1047 QQmlAbstractUrlInterceptor::QmldirFile);
1048 if (!QQmlFile::isLocalFile(url)
1049 && !fetchQmldir(url, import, ++priority, errors)) {
1050 return false;
1051 }
1052 } else if (!fetchQmldir(QUrl(qmldirPath), import, ++priority, errors)) {
1053 return false;
1054 }
1055 }
1056 }
1057
1058 return true;
1059}
1060
1061bool QQmlTypeLoader::Blob::registerPendingTypes(const PendingImportPtr &import)
1062{
1063 assertTypeLoaderThread();
1064
1065 return
1066 // Major version of module already registered:
1067 // We believe that the registration is complete.
1068 QQmlMetaType::typeModule(import->uri, import->version)
1069
1070 // Otherwise, try to register further module types.
1071 || QQmlMetaType::qmlRegisterModuleTypes(import->uri)
1072
1073 // Otherwise, there is no way to register any further types.
1074 // Try with any module of that name.
1075 || QQmlMetaType::latestModuleVersion(import->uri).isValid();
1076}
1077
1078bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import,
1079 QQmlImports::ImportFlags flags, QList<QQmlError> *errors)
1080{
1081 assertTypeLoaderThread();
1082 return addImport(std::make_shared<PendingImport>(this, import, flags), errors);
1083}
1084
1085bool QQmlTypeLoader::Blob::addImport(
1086 const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
1087{
1088 assertTypeLoaderThread();
1089
1090 Q_ASSERT(errors);
1091
1092 switch (import->type)
1093 {
1094 case QV4::CompiledData::Import::ImportLibrary:
1095 return addLibraryImport(import, errors);
1096 case QV4::CompiledData::Import::ImportFile:
1097 return addFileImport(import ,errors);
1098 case QV4::CompiledData::Import::ImportScript:
1099 return addScriptImport(import);
1100 case QV4::CompiledData::Import::ImportInlineComponent:
1101 Q_UNREACHABLE_RETURN(false); // addImport is never called with an inline component import
1102 }
1103
1104 Q_UNREACHABLE_RETURN(false);
1105}
1106
1107void QQmlTypeLoader::Blob::dependencyComplete(const QQmlDataBlob::Ptr &blob)
1108{
1109 assertTypeLoaderThread();
1110
1111 if (blob->type() == QQmlDataBlob::QmldirFile) {
1112 QQmlQmldirData *data = static_cast<QQmlQmldirData *>(blob.data());
1113 QList<QQmlError> errors;
1114 if (!qmldirDataAvailable(data, &errors)) {
1115 Q_ASSERT(errors.size());
1116 QQmlError error(errors.takeFirst());
1117 error.setUrl(m_importCache->baseUrl());
1118 const QV4::CompiledData::Location importLocation = data->importLocation(this);
1119 error.setLine(qmlConvertSourceCoordinate<quint32, int>(importLocation.line()));
1120 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(importLocation.column()));
1121 errors.prepend(error); // put it back on the list after filling out information.
1122 setError(errors);
1123 }
1124 }
1125}
1126
1127bool QQmlTypeLoader::Blob::loadDependentImports(
1128 const QList<QQmlDirParser::Import> &imports, const QString &qualifier,
1129 QTypeRevision version, QTypeRevision searchVersion, quint8 precedence,
1130 QQmlImports::ImportFlags flags, QList<QQmlError> *errors)
1131{
1132 assertTypeLoaderThread();
1133
1134 for (const auto &import : imports) {
1135 if (import.flags & QQmlDirParser::Import::Optional)
1136 continue;
1137 auto dependencyImport = std::make_shared<PendingImport>();
1138 dependencyImport->uri = import.module;
1139 dependencyImport->qualifier = qualifier;
1140 if (import.flags & QQmlDirParser::Import::Auto) {
1141 dependencyImport->version = version;
1142 dependencyImport->searchVersion = searchVersion;
1143 } else {
1144 dependencyImport->version = import.version;
1145 dependencyImport->searchVersion = import.version;
1146 }
1147 dependencyImport->flags = flags;
1148 dependencyImport->precedence = precedence;
1149
1150 qCDebug(lcQmlImport)
1151 << "loading dependent import" << dependencyImport->uri << "version"
1152 << dependencyImport->version << "as" << dependencyImport->qualifier;
1153
1154 if (!addImport(dependencyImport, errors)) {
1155 QQmlError error;
1156 error.setDescription(
1157 QString::fromLatin1(
1158 "Failed to load dependent import \"%1\" version %2.%3")
1159 .arg(dependencyImport->uri)
1160 .arg(dependencyImport->version.majorVersion())
1161 .arg(dependencyImport->version.minorVersion()));
1162 errors->append(error);
1163 return false;
1164 }
1165 }
1166
1167 return true;
1168}
1169
1170bool QQmlTypeLoader::Blob::loadImportDependencies(
1171 const QQmlTypeLoader::Blob::PendingImportPtr &currentImport, const QString &qmldirUri,
1172 QQmlImports::ImportFlags flags, QList<QQmlError> *errors)
1173{
1174 assertTypeLoaderThread();
1175
1176 QList<QQmlDirParser::Import> implicitImports
1177 = QQmlMetaType::moduleImports(currentImport->uri, currentImport->version);
1178 if (!qmldirUri.isEmpty())
1179 implicitImports += typeLoader()->qmldirContent(qmldirUri).imports();
1180
1181 // Prevent overflow from one category of import into the other.
1182 switch (currentImport->precedence) {
1183 case QQmlImportInstance::Implicit - 1:
1184 case QQmlImportInstance::Lowest: {
1185 QQmlError error;
1186 error.setDescription(
1187 QString::fromLatin1("Too many dependent imports for %1 %2.%3")
1188 .arg(currentImport->uri)
1189 .arg(currentImport->version.majorVersion())
1190 .arg(currentImport->version.minorVersion()));
1191 errors->append(error);
1192 return false;
1193 }
1194 default:
1195 break;
1196 }
1197
1198 if (!loadDependentImports(
1199 implicitImports, currentImport->qualifier, currentImport->version,
1200 currentImport->searchVersion, currentImport->precedence + 1, flags, errors)) {
1201 QQmlError error;
1202 error.setDescription(
1203 QString::fromLatin1(
1204 "Failed to load dependencies for module \"%1\" version %2.%3")
1205 .arg(currentImport->uri)
1206 .arg(currentImport->version.majorVersion())
1207 .arg(currentImport->version.minorVersion()));
1208 errors->append(error);
1209 return false;
1210 }
1211
1212 return true;
1213}
1214
1215static QQmlTypeLoaderConfiguredDataConstPtr configuredData(QQmlTypeLoaderLockedData *m_data)
1216{
1217 if (!QQmlTypeLoaderConfiguredDataConstPtr(m_data)->initialized)
1218 initializeConfiguredData(QQmlTypeLoaderConfiguredDataPtr(m_data), m_data->engine());
1219
1220 return QQmlTypeLoaderConfiguredDataConstPtr(m_data);
1221}
1222
1223bool QQmlTypeLoader::isDebugging()
1224{
1225 return configuredData(&m_data)->isDebugging;
1226}
1227
1228bool QQmlTypeLoader::readCacheFile()
1229{
1230 return configuredData(&m_data)->diskCacheOptions & QV4::ExecutionEngine::DiskCache::QmlcRead;
1231}
1232
1233bool QQmlTypeLoader::writeCacheFile()
1234{
1235 return configuredData(&m_data)->diskCacheOptions & QV4::ExecutionEngine::DiskCache::QmlcWrite;
1236}
1237
1238QQmlMetaType::CacheMode QQmlTypeLoader::aotCacheMode()
1239{
1240 const QV4::ExecutionEngine::DiskCacheOptions options
1241 = configuredData(&m_data)->diskCacheOptions;
1242 if (!(options & QV4::ExecutionEngine::DiskCache::Aot))
1243 return QQmlMetaType::RejectAll;
1244 if (options & QV4::ExecutionEngine::DiskCache::AotByteCode)
1245 return QQmlMetaType::AcceptUntyped;
1246 return QQmlMetaType::RequireFullyTyped;
1247}
1248
1249bool QQmlTypeLoader::Blob::qmldirDataAvailable(const QQmlRefPointer<QQmlQmldirData> &data, QList<QQmlError> *errors)
1250{
1251 assertTypeLoaderThread();
1252 return data->processImports(this, [&](const PendingImportPtr &import) {
1253 return updateQmldir(data, import, errors);
1254 });
1255}
1256
1257static QStringList parseEnvPath(const QString &envImportPath)
1258{
1259 if (QDir::listSeparator() == u':') {
1260 // Double colons are interpreted as separator + resource path.
1261 QStringList paths = envImportPath.split(u':');
1262 bool wasEmpty = false;
1263 for (auto it = paths.begin(); it != paths.end();) {
1264 if (it->isEmpty()) {
1265 wasEmpty = true;
1266 it = paths.erase(it);
1267 } else {
1268 if (wasEmpty) {
1269 it->prepend(u':');
1270 wasEmpty = false;
1271 }
1272 ++it;
1273 }
1274 }
1275 return paths;
1276 } else {
1277 return envImportPath.split(QDir::listSeparator(), Qt::SkipEmptyParts);
1278 }
1279}
1280
1281/*!
1282Constructs a new type loader that uses the given \a engine.
1283*/
1284QQmlTypeLoader::QQmlTypeLoader(QV4::ExecutionEngine *engine)
1285 : m_data(engine)
1286{
1287 // Add default import and plugin paths. Paths are added in decreasting
1288 // priority, by appending to the path lists.
1289
1290 const bool isPluginApplication = QCoreApplication::testAttribute(Qt::AA_PluginApplication);
1291
1292 auto addEnvPath = [this, isPluginApplication](const char *var, auto addPath) {
1293 if (Q_UNLIKELY(!isPluginApplication && !qEnvironmentVariableIsEmpty(var))) {
1294 const QStringList paths = parseEnvPath(qEnvironmentVariable(var));
1295 for (const QString &path : paths)
1296 (this->*addPath)(path, AppendPath);
1297 }
1298 };
1299
1300 // Import paths, used for looking up QML modules, e.g. `MyModules/qmldir`
1301
1302#if defined(Q_OS_ANDROID)
1303 addImportPath(QStringLiteral("qrc:/android_rcc_bundle/qml"), AppendPath);
1304#endif
1305
1306 if (!isPluginApplication)
1307 addImportPath(QCoreApplication::applicationDirPath(), AppendPath);
1308
1309 addImportPath(QStringLiteral("qrc:/qt-project.org/imports"), AppendPath);
1310 addImportPath(QStringLiteral("qrc:/qt/qml"), AppendPath);
1311
1312 addEnvPath("QML2_IMPORT_PATH", &QQmlTypeLoader::addImportPath);
1313 addEnvPath("QML_IMPORT_PATH", &QQmlTypeLoader::addImportPath);
1314
1315 const auto qmlImportPaths = QLibraryInfo::paths(QLibraryInfo::QmlImportsPath);
1316 for (const auto &qmlImportPath : qmlImportPaths)
1317 addImportPath(qmlImportPath, AppendPath);
1318
1319 // Plugin paths, used for looking up the backing library of a QML module
1320
1321#if defined(Q_OS_ANDROID)
1322 addEnvPath("QT_BUNDLED_LIBS_PATH", &QQmlTypeLoader::addPluginPath);
1323#endif
1324 addEnvPath("QML_PLUGIN_PATH", &QQmlTypeLoader::addPluginPath);
1325
1326 const auto pluginPaths = QLibraryInfo::paths(QLibraryInfo::PluginsPath);
1327 for (const auto &pluginPath : pluginPaths)
1328 addPluginPath(pluginPath, AppendPath);
1329
1330 // Explicitly add "." to the plugin paths, to represent the path relative
1331 // to where the qmldir was found. We can't use addPluginPath() here, as
1332 // it will resolve the path to an absolute path based on the working dir.
1333 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
1334 data->pluginPaths << QLatin1String(".");
1335}
1336
1337/*!
1338Destroys the type loader, first clearing the cache of any information about
1339loaded files.
1340*/
1341QQmlTypeLoader::~QQmlTypeLoader()
1342{
1344
1345 shutdownThread();
1346
1347 // Delete the thread before clearing the cache. Otherwise it will be started up again.
1348 invalidate();
1349
1350 clearCache();
1351
1352 clearQmldirInfo();
1353}
1354
1355template<typename Blob>
1357 const QQmlTypeLoaderSharedDataPtr &data, QQmlRefPointer<Blob> &&blob,
1358 QQmlTypeLoader::Mode mode)
1359{
1360 if ((mode == QQmlTypeLoader::PreferSynchronous && QQmlFile::isSynchronous(blob->finalUrl()))
1361 || mode == QQmlTypeLoader::Synchronous) {
1362 // this was started Asynchronous, but we need to force Synchronous
1363 // completion now.
1364
1365 // This only works when called directly from e.g. the UI thread, but not
1366 // when recursively called on the QML thread via resolveTypes()
1367
1368 // NB: We do not want to know whether the thread is the main thread, but specifically
1369 // that the thread is _not_ the thread we're waiting for.
1370 // If !QT_CONFIG(qml_type_loader_thread) the QML thread is the main thread.
1371
1372 QQmlTypeLoaderThread *thread = data.thread();
1373 if (thread && !thread->isThisThread()) {
1374 while (!blob->isCompleteOrError())
1375 thread->waitForNextMessage(); // Requires lock to be held, via data above
1376 }
1377 }
1378 return blob;
1379}
1380
1381/*!
1382Returns a QQmlTypeData for the specified \a url. The QQmlTypeData may be cached.
1383*/
1384QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QUrl &unNormalizedUrl, Mode mode)
1385{
1386 // This can be called from either thread.
1387
1388 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1389 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1390 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1391
1392 QQmlRefPointer<QQmlTypeData> typeData;
1393 {
1394 const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl);
1395 QQmlTypeLoaderSharedDataPtr data(&m_data);
1396
1397 typeData = data->typeCache.value(url);
1398 if (typeData)
1399 return handleExisting(data, std::move(typeData), mode);
1400
1401 // Trim before adding the new type, so that we don't immediately trim it away
1402 if (data->typeCache.size() >= data->typeCacheTrimThreshold)
1403 trimCache(data);
1404
1405 typeData = QQml::makeRefPointer<QQmlTypeData>(url, this);
1406
1407 // TODO: if (compiledData == 0), is it safe to omit this insertion?
1408 data->typeCache.insert(url, typeData);
1409 }
1410
1411 return finalizeBlob(std::move(typeData), mode);
1412}
1413
1414/*!
1415Returns a QQmlTypeData for the given \a data with the provided base \a url. The
1416QQmlTypeData will not be cached.
1417*/
1418QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(
1419 const QByteArray &data, const QUrl &url, Mode mode)
1420{
1421 // Can be called from either thread.
1422
1423 QQmlRefPointer<QQmlTypeData> typeData = QQml::makeRefPointer<QQmlTypeData>(url, this);
1424 QQmlTypeLoader::loadWithStaticData(QQmlDataBlob::Ptr(typeData.data()), data, mode);
1425 return typeData;
1426}
1427
1428static bool isModuleUrl(const QUrl &url)
1429{
1430 return url.fragment() == QLatin1String("module") || url.path().endsWith(QLatin1String(".mjs"));
1431}
1432
1433QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(const QUrl &unNormalizedUrl, Mode mode)
1434{
1435 // This can be called from either thread.
1436
1437 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1438 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1439 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1440
1441 QQmlRefPointer<QQmlScriptBlob> scriptBlob;
1442 {
1443 const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl);
1444 QQmlTypeLoaderSharedDataPtr data(&m_data);
1445
1446 scriptBlob = data->scriptCache.value(url);
1447 if (scriptBlob)
1448 return handleExisting(data, std::move(scriptBlob), mode);
1449
1450 scriptBlob = QQml::makeRefPointer<QQmlScriptBlob>(url, this, isModuleUrl(url)
1451 ? QQmlScriptBlob::IsESModule::Yes
1452 : QQmlScriptBlob::IsESModule::No);
1453 data->scriptCache.insert(url, scriptBlob);
1454 }
1455
1456 return finalizeBlob(std::move(scriptBlob), mode);
1457}
1458
1459QQmlRefPointer<QV4::CompiledData::CompilationUnit> QQmlTypeLoader::injectModule(
1460 const QUrl &relativeUrl, const QV4::CompiledData::Unit *unit)
1461{
1463
1464 QQmlRefPointer<QQmlScriptBlob> blob = QQml::makeRefPointer<QQmlScriptBlob>(
1465 relativeUrl, this, QQmlScriptBlob::IsESModule::Yes);
1466 QQmlPrivate::CachedQmlUnit cached { unit, nullptr, nullptr};
1467
1468 {
1469 QQmlTypeLoaderSharedDataPtr data(&m_data);
1470 data->scriptCache.insert(relativeUrl, blob);
1471 }
1472
1473 loadWithCachedUnit(blob.data(), &cached, Synchronous);
1474 Q_ASSERT(blob->isComplete());
1475 return blob->scriptData()->compilationUnit();
1476}
1477
1478/*!
1479Return a QQmlScriptBlob for \a unNormalizedUrl or \a relativeUrl.
1480This assumes PreferSynchronous, and therefore the result may not be ready yet.
1481*/
1482QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(
1483 const QUrl &unNormalizedUrl, const QUrl &relativeUrl)
1484{
1485 // Can be called from either thread
1486
1487 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1488 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1489 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1490
1491 const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl);
1492
1493 QQmlRefPointer<QQmlScriptBlob> scriptBlob;
1494 {
1495 QQmlTypeLoaderSharedDataPtr data(&m_data);
1496 scriptBlob = data->scriptCache.value(url);
1497
1498 // Also try the relative URL since manually registering native modules doesn't require
1499 // passing an absolute URL and we don't have a reference URL for native modules.
1500 if (!scriptBlob && unNormalizedUrl != relativeUrl)
1501 scriptBlob = data->scriptCache.value(relativeUrl);
1502
1503 // Do not try to finish the loading via handleExisting() here.
1504 if (scriptBlob)
1505 return scriptBlob;
1506
1507 scriptBlob = QQml::makeRefPointer<QQmlScriptBlob>(url, this, isModuleUrl(url)
1508 ? QQmlScriptBlob::IsESModule::Yes
1509 : QQmlScriptBlob::IsESModule::No);
1510 data->scriptCache.insert(url, scriptBlob);
1511 }
1512
1513 return finalizeBlob(std::move(scriptBlob), PreferSynchronous);
1514}
1515
1516/*!
1517Returns a QQmlQmldirData for \a url. The QQmlQmldirData may be cached.
1518*/
1519QQmlRefPointer<QQmlQmldirData> QQmlTypeLoader::getQmldir(const QUrl &url)
1520{
1521 // Can be called from either thread.
1522
1523 Q_ASSERT(!url.isRelative() &&
1524 (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() ||
1525 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url))));
1526
1527 QQmlRefPointer<QQmlQmldirData> qmldirData;
1528 {
1529 QQmlTypeLoaderSharedDataPtr data(&m_data);
1530 qmldirData = data->qmldirCache.value(url);
1531 if (qmldirData)
1532 return qmldirData;
1533
1534 qmldirData = QQml::makeRefPointer<QQmlQmldirData>(url, this);
1535 data->qmldirCache.insert(url, qmldirData);
1536 }
1537
1538 QQmlTypeLoader::load(QQmlDataBlob::Ptr(qmldirData.data()));
1539 return qmldirData;
1540}
1541
1542bool QQmlTypeLoader::isResource(const QString &path)
1543{
1544 const bool startsWithColon = path.at(0) == QLatin1Char(':');
1545#if defined(Q_OS_ANDROID)
1546 return startsWithColon || path.startsWith(QLatin1String("assets:/"), Qt::CaseInsensitive)
1547 || path.startsWith(QLatin1String("content:/"), Qt::CaseInsensitive);
1548#else
1549 return startsWithColon;
1550#endif
1551}
1552
1553/*!
1554Returns the clean filename of path via a directory cache.
1555Returns a empty string if the path does not exist.
1556
1557Why a directory cache? QML checks for files in many paths with
1558invalid directories. By caching whether a directory exists
1559we avoid many stats. We also cache the files' existence in the
1560directory, for the same reason.
1561*/
1562QString QQmlTypeLoader::cleanFilePath(const QString &path) const
1563{
1564 // Can be called from either thread.
1565
1566 if (path.isEmpty())
1567 return QString();
1568
1569 const QString cleanPath = QDir::cleanPath(path);
1570 if (isResource(cleanPath)) {
1571 // qrc resource
1572 QFileInfo fileInfo(cleanPath);
1573 return fileInfo.isFile() ? cleanPath : QString();
1574 }
1575
1576 return fileExists(cleanPath)
1577 ? cleanPath
1578 : QString();
1579}
1580
1581static QString stripTrailingSlashes(const QString &path)
1582{
1583 for (qsizetype length = path.size(); length > 0; --length) {
1584 if (path[length - 1] != QLatin1Char('/'))
1585 return path.left(length);
1586 }
1587
1588 return QString();
1589}
1590
1591bool QQmlTypeLoader::fileExists(const QString &dirPath, const QString &file) const
1592{
1593 // Can be called from either thread.
1594
1595 // We want to use QDirListing here because that gives us case-sensitive results even on
1596 // case-insensitive file systems. That is, for a file date.qml it only lists date.qml, not
1597 // Date.qml, dAte.qml, DATE.qml etc. QFileInfo::exists(), on the other hand, will happily
1598 // claim that Date.qml exists in such a situation on a case-insensitive file sysem. Such a
1599 // thing then shadows the JavaScript Date object and disaster ensues.
1600
1601 const QChar nullChar(QChar::Null);
1602 if (dirPath.isEmpty() || dirPath.contains(nullChar) || file.isEmpty()
1603 || file.contains(nullChar)) {
1604 return false;
1605 }
1606
1607 // NB: We really shouldn't see URLs (qrc:/ or the like) here. This is explicitly about paths.
1608 // We don't handle file:/ either.
1609
1610 if (isResource(dirPath)) {
1611 // The resource file system is case-sensitive. So we don't have to do the below gymnastics.
1612 // However, it changes spontanously as resources are loaded. Therefore we can't cache it.
1613 const QFileInfo info(dirPath + u'/' + file);
1614 return info.exists();
1615 }
1616
1617 const QString path = stripTrailingSlashes(dirPath);
1618
1619 QQmlTypeLoaderSharedDataConstPtr data(&m_data);
1620 return data->importDirCache.fileExists(path, file);
1621}
1622
1623
1624/*!
1625Returns true if the path is a directory via a directory cache. Cache is
1626shared with cleanFilePath().
1627*/
1628bool QQmlTypeLoader::directoryExists(const QString &path) const
1629{
1630 // Can be called from either thread.
1631
1632 if (path.isEmpty())
1633 return false;
1634
1635 if (isResource(path)) {
1636 // qrc resource
1637 QFileInfo fileInfo(path);
1638 return fileInfo.exists() && fileInfo.isDir();
1639 }
1640
1641 const QString dirPath = stripTrailingSlashes(path);
1642
1643 QQmlTypeLoaderSharedDataConstPtr data(&m_data);
1644 return data->importDirCache.directoryExists(path);
1645
1646}
1647
1648
1649/*!
1650
1651\internal
1652
1653Return a QQmlTypeLoaderQmldirContent for \a filePathIn.
1654The QQmlTypeLoaderQmldirContent may be cached.
1655
1656It can also be a remote path for a remote directory import, but it will have
1657been cached by now in this case.
1658*/
1659const QQmlTypeLoaderQmldirContent QQmlTypeLoader::qmldirContent(const QString &filePathIn)
1660{
1662
1663 QString filePath;
1664
1665 // Try to guess if filePathIn is already a URL. This is necessarily fragile, because
1666 // - paths can contain ':', which might make them appear as URLs with schemes.
1667 // - windows drive letters appear as schemes (thus "< 2" below).
1668 // - a "file:" URL is equivalent to the respective file, but will be treated differently.
1669 // Yet, this heuristic is the best we can do until we pass more structured information here,
1670 // for example a QUrl also for local files.
1671 QUrl url(filePathIn);
1672
1673 QQmlTypeLoaderThreadDataPtr data(&m_data);
1674
1675 if (url.scheme().size() < 2) {
1676 filePath = filePathIn;
1677 } else {
1678 filePath = QQmlFile::urlToLocalFileOrQrc(url);
1679 if (filePath.isEmpty()) { // Can't load the remote here, but should be cached
1680 if (auto entry = data->importQmlDirCache.value(filePathIn))
1681 return **entry;
1682 else
1683 return QQmlTypeLoaderQmldirContent();
1684 }
1685 }
1686
1687 QQmlTypeLoaderQmldirContent **val = data->importQmlDirCache.value(filePath);
1688 if (val)
1689 return **val;
1690 QQmlTypeLoaderQmldirContent *qmldir = new QQmlTypeLoaderQmldirContent;
1691
1692#define ERROR(description) { QQmlError e; e.setDescription(description); qmldir->setError(e); }
1693#define NOT_READABLE_ERROR QString(QLatin1String("module \"$$URI$$\" definition \"%1\" not readable"))
1694#define NOT_FOUND_ERROR QString(QLatin1String("cannot load module \"$$URI$$\": File \"%1\" not found"))
1695
1696 QFile file(filePath);
1697 if (!fileExists(filePath)) {
1698 ERROR(NOT_FOUND_ERROR.arg(filePath));
1699 } else if (file.open(QFile::ReadOnly)) {
1700 QByteArray data = file.readAll();
1701 qmldir->setContent(filePath, QString::fromUtf8(data));
1702 } else {
1703 ERROR(NOT_READABLE_ERROR.arg(filePath));
1704 }
1705
1706#undef ERROR
1707#undef NOT_READABLE_ERROR
1708#undef NOT_FOUND_ERROR
1709
1710 data->importQmlDirCache.insert(filePath, qmldir);
1711 return *qmldir;
1712}
1713
1714void QQmlTypeLoader::setQmldirContent(const QString &url, const QString &content)
1715{
1717
1718 QQmlTypeLoaderThreadDataPtr data(&m_data);
1719 QQmlTypeLoaderQmldirContent *qmldir;
1720 QQmlTypeLoaderQmldirContent **val = data->importQmlDirCache.value(url);
1721 if (val) {
1722 qmldir = *val;
1723 } else {
1724 qmldir = new QQmlTypeLoaderQmldirContent;
1725 data->importQmlDirCache.insert(url, qmldir);
1726 }
1727
1728 if (!qmldir->hasContent())
1729 qmldir->setContent(url, content);
1730}
1731
1732template<typename Blob>
1733void clearBlobs(QHash<QUrl, QQmlRefPointer<Blob>> *blobs)
1734{
1735 std::for_each(blobs->cbegin(), blobs->cend(), [](const QQmlRefPointer<Blob> &blob) {
1736 blob->resetTypeLoader();
1737 });
1738 blobs->clear();
1739}
1740
1741/*!
1742Clears cached information about loaded files, including any type data, scripts
1743and qmldir information.
1744*/
1745void QQmlTypeLoader::clearCache()
1746{
1747 // This looks dangerous because we're dropping live blobs on the engine thread.
1748 // However, it's safe because we shut down the type loader thread before we do so.
1749
1751
1752 // Temporarily shut the thread down and discard all messages, making it safe to
1753 // hack into the various data structures below.
1754 shutdownThread();
1755
1756 QQmlTypeLoaderThreadDataPtr threadData(&m_data);
1757 qDeleteAll(threadData->importQmlDirCache);
1758 threadData->checksumCache.clear();
1759 threadData->importQmlDirCache.clear();
1760
1761 QQmlTypeLoaderSharedDataPtr data(&m_data);
1762 clearBlobs(&data->typeCache);
1763 clearBlobs(&data->scriptCache);
1764 clearBlobs(&data->qmldirCache);
1765 data->typeCacheTrimThreshold = QQmlTypeLoaderSharedData::MinimumTypeCacheTrimThreshold;
1766 data->importDirCache.clear();
1767
1768 // The thread will auto-restart next time we need it.
1769}
1770
1771/*!
1772 \internal
1773 Remove any cached artifacts for \a url.
1774
1775 Returns \c true if anything was removed, or \c false otherwise.
1776*/
1777bool QQmlTypeLoader::removeFromCache(const QUrl &url)
1778{
1779 const QUrl normalized = QQmlMetaType::normalizedUrl(url);
1780 const QQmlTypeLoaderSharedDataPtr data(&m_data);
1781 return data->typeCache.remove(normalized) || data->scriptCache.remove(normalized)
1782 || data->qmldirCache.remove(normalized);
1783}
1784
1785void QQmlTypeLoader::trimCache()
1786{
1787 const QQmlTypeLoaderSharedDataPtr data(&m_data);
1788 trimCache(data);
1789}
1790
1791void QQmlTypeLoader::updateTypeCacheTrimThreshold(const QQmlTypeLoaderSharedDataPtr &data)
1792{
1793 // This can be called from either thread and is called from a method that locks.
1794
1795 int size = data->typeCache.size();
1796 if (size > data->typeCacheTrimThreshold)
1797 data->typeCacheTrimThreshold = size * 2;
1798 if (size < data->typeCacheTrimThreshold / 2) {
1799 data->typeCacheTrimThreshold
1800 = qMax(size * 2, int(QQmlTypeLoaderSharedData::MinimumTypeCacheTrimThreshold));
1801 }
1802}
1803
1804void QQmlTypeLoader::trimCache(const QQmlTypeLoaderSharedDataPtr &data)
1805{
1806 // This can be called from either thread. It has to be called while the type loader mutex
1807 // is locked. It drops potentially live blobs, but only ones which are isCompleteOrError and
1808 // are not depended on by other blobs.
1809
1810 while (true) {
1811 bool deletedOneType = false;
1812 for (auto iter = data->typeCache.begin(), end = data->typeCache.end(); iter != end;) {
1813 const QQmlRefPointer<QQmlTypeData> &typeData = iter.value();
1814
1815 // typeData->m_compiledData may be set early on in the proccess of loading a file, so
1816 // it's important to check the general loading status of the typeData before making any
1817 // other decisions.
1818 if (typeData->count() != 1 || !typeData->isCompleteOrError()) {
1819 ++iter;
1820 continue;
1821 }
1822
1823 // isCompleteOrError means the waitingFor list of this typeData is empty.
1824 // Therefore, it cannot interfere with other blobs on destruction anymore.
1825 // Therefore, we can drop it on either the engine thread or the type loader thread.
1826
1827 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit
1828 = typeData->m_compiledData;
1829 if (compilationUnit) {
1830 if (compilationUnit->count()
1831 > QQmlMetaType::countInternalCompositeTypeSelfReferences(
1832 compilationUnit) + 1) {
1833 ++iter;
1834 continue;
1835 }
1836
1837 QQmlMetaType::unregisterInternalCompositeType(compilationUnit);
1838 Q_ASSERT(compilationUnit->count() == 1);
1839 }
1840
1841 // There are no live objects of this type
1842 iter = data->typeCache.erase(iter);
1843 deletedOneType = true;
1844 }
1845
1846 if (!deletedOneType)
1847 break;
1848 }
1849
1850 // TODO: release any scripts which are no longer referenced by any types
1851
1852 updateTypeCacheTrimThreshold(data);
1853
1854 QQmlMetaType::freeUnusedTypesAndCaches();
1855}
1856
1857bool QQmlTypeLoader::isTypeLoaded(const QUrl &url) const
1858{
1859 const QQmlTypeLoaderSharedDataConstPtr data(&m_data);
1860 return data->typeCache.contains(url);
1861}
1862
1863bool QQmlTypeLoader::isScriptLoaded(const QUrl &url) const
1864{
1865 const QQmlTypeLoaderSharedDataConstPtr data(&m_data);
1866 return data->scriptCache.contains(url);
1867}
1868
1869static QString pathToUrl(const QString &path)
1870{
1871 const auto dir = path.left(path.lastIndexOf(u"/") + 1);
1872 if (dir.at(0) == u':')
1873 return QStringLiteral("qrc") + dir;
1874 else
1875 return QUrl::fromLocalFile(dir).toString();
1876};
1877
1878/*!
1879\internal
1880
1881Locates the qmldir files for \a import. For each one, calls
1882handleLocalQmldirForImport() on \a blob. If that returns \c true, returns
1883\c QmldirFound.
1884
1885If at least one callback invocation returned \c false and there are no qmldir
1886files left to check, returns \c QmldirRejected.
1887
1888Otherwise, if interception redirects a previously local qmldir URL to a remote
1889one, returns \c QmldirInterceptedToRemote. Otherwise, returns \c QmldirNotFound.
1890*/
1891QQmlTypeLoader::LocalQmldirResult QQmlTypeLoader::locateLocalQmldir(
1892 QQmlTypeLoader::Blob *blob, const QQmlTypeLoader::Blob::PendingImportPtr &import,
1893 QList<QQmlError> *errors)
1894{
1895 // Check cache first
1896
1897 LocalQmldirResult result = QmldirNotFound;
1898 QQmlTypeLoaderThreadData::QmldirInfo *cacheTail = nullptr;
1899
1900 QQmlTypeLoaderThreadDataPtr threadData(&m_data);
1901 QQmlTypeLoaderThreadData::QmldirInfo **cachePtr = threadData->qmldirInfo.value(import->uri);
1902 QQmlTypeLoaderThreadData::QmldirInfo *cacheHead = cachePtr ? *cachePtr : nullptr;
1903 if (cacheHead) {
1904 cacheTail = cacheHead;
1905 do {
1906 if (cacheTail->version == import->searchVersion) {
1907 if (cacheTail->qmldirFilePath.isEmpty()) {
1908 return cacheTail->qmldirPathUrl.isEmpty()
1909 ? QmldirNotFound
1910 : QmldirInterceptedToRemote;
1911 }
1912 if (blob->handleLocalQmldirForImport(
1913 import, cacheTail->qmldirFilePath, cacheTail->qmldirPathUrl, errors)) {
1914 return QmldirFound;
1915 }
1916 result = QmldirRejected;
1917 }
1918 } while (cacheTail->next && (cacheTail = cacheTail->next));
1919 }
1920
1921
1922 // Do not try to construct the cache if it already had any entries for the URI.
1923 // Otherwise we might duplicate cache entries.
1924 if (result != QmldirNotFound
1925 || QQmlMetaType::isStronglyLockedModule(import->uri, import->version)) {
1926 return result;
1927 }
1928
1929 QQmlTypeLoaderConfiguredDataConstPtr configuredData(&m_data);
1930 const bool hasInterceptors = !configuredData->urlInterceptors.isEmpty();
1931
1932 // Interceptor might redirect remote files to local ones.
1933 QStringList localImportPaths = importPathList(hasInterceptors ? LocalOrRemote : Local);
1934
1935 // Search local import paths for a matching version
1936 const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(
1937 import->uri, localImportPaths, import->searchVersion);
1938
1939 QString qmldirCleanFilePath;
1940 for (QString qmldirPath : qmlDirPaths) {
1941 if (hasInterceptors) {
1942 // TODO:
1943 // 1. This is inexact. It triggers only on the existence of interceptors, not on
1944 // actual interception. If the URL was remote to begin with but no interceptor
1945 // actually changes it, we still clear the qmldirPath and consider it
1946 // QmldirInterceptedToRemote.
1947 // 2. This misdiagnosis makes addLibraryImport do the right thing and postpone
1948 // the loading of pre-registered types for any QML engine that has interceptors
1949 // (even if they don't do anything in this case).
1950 // Fixing this would open the door to follow-up problems but wouldn't result in any
1951 // significant benefit.
1952 const QUrl intercepted = doInterceptUrl(
1953 QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
1954 QQmlAbstractUrlInterceptor::QmldirFile,
1955 configuredData->urlInterceptors);
1956 qmldirPath = QQmlFile::urlToLocalFileOrQrc(intercepted);
1957 if (result != QmldirInterceptedToRemote
1958 && qmldirPath.isEmpty()
1959 && !QQmlFile::isLocalFile(intercepted)) {
1960 result = QmldirInterceptedToRemote;
1961 }
1962 }
1963
1964 // qmldirPath can be a path or a URL at this point because we've inherited that property
1965 // from importPathList(). cleanFilePath() however, wants only paths, not URLs. It may
1966 // also be empty here if an interceptor redirected it to a non-local URL above.
1967 qmldirCleanFilePath = cleanFilePath(localPathForUrlOrPath(qmldirPath));
1968
1969 if (!qmldirCleanFilePath.isEmpty()) {
1970 QString url = pathToUrl(qmldirCleanFilePath);
1971 if (url.startsWith(QStringLiteral("file:")))
1972 sanitizeUNCPath(&qmldirCleanFilePath);
1973
1974 QQmlTypeLoaderThreadData::QmldirInfo *cache = new QQmlTypeLoaderThreadData::QmldirInfo;
1975 cache->version = import->searchVersion;
1976 cache->qmldirFilePath = qmldirCleanFilePath;
1977 cache->qmldirPathUrl = url;
1978 cache->next = nullptr;
1979 if (cacheTail)
1980 cacheTail->next = cache;
1981 else
1982 threadData->qmldirInfo.insert(import->uri, cache);
1983 cacheTail = cache;
1984
1985 if (result != QmldirFound) {
1986 result = blob->handleLocalQmldirForImport(
1987 import, qmldirCleanFilePath, url, errors)
1988 ? QmldirFound
1989 : QmldirRejected;
1990 }
1991
1992 // Do not return here. Rather, construct the complete cache for this URI.
1993 }
1994 }
1995
1996 // Nothing found? Add an empty cache entry to signal that for further requests.
1997 if (result == QmldirNotFound || result == QmldirInterceptedToRemote) {
1998 QQmlTypeLoaderThreadData::QmldirInfo *cache = new QQmlTypeLoaderThreadData::QmldirInfo;
1999 cache->version = import->searchVersion;
2000 cache->next = cacheHead;
2001 if (result == QmldirInterceptedToRemote) {
2002 // The actual value doesn't matter as long as it's not empty.
2003 // We only use it to discern QmldirInterceptedToRemote from QmldirNotFound above.
2004 cache->qmldirPathUrl = QStringLiteral("intercepted");
2005 }
2006 threadData->qmldirInfo.insert(import->uri, cache);
2007
2008 if (result == QmldirNotFound) {
2009 qCDebug(lcQmlImport)
2010 << "locateLocalQmldir:" << qPrintable(import->uri)
2011 << "module's qmldir file not found";
2012 }
2013 } else {
2014 qCDebug(lcQmlImport)
2015 << "locateLocalQmldir:" << qPrintable(import->uri) << "module's qmldir found at"
2016 << qmldirCleanFilePath;
2017 }
2018
2019 return result;
2020}
2021
2022QT_END_NAMESPACE
#define ASSERT_ENGINETHREAD()
#define ASSERT_LOADTHREAD()
static QString pathToUrl(const QString &path)
static bool isPathAbsolute(const QString &path)
static QString localPathForUrlOrPath(const QString &urlOrPath)
static void initializeConfiguredData(const QQmlTypeLoaderConfiguredDataPtr &data, QV4::ExecutionEngine *engine)
static void addDependencyImportError(const QQmlTypeLoader::Blob::PendingImportPtr &import, QList< QQmlError > *errors)
static QQmlTypeLoaderConfiguredDataConstPtr configuredData(QQmlTypeLoaderLockedData *m_data)
#define ERROR(description)
#define NOT_READABLE_ERROR
static bool isModuleUrl(const QUrl &url)
void clearBlobs(QHash< QUrl, QQmlRefPointer< Blob > > *blobs)
void doInitializeEngine(Interface *iface, QQmlTypeLoaderThread *thread, QQmlTypeLoaderLockedData *data, const char *uri)
#define NOT_FOUND_ERROR
static QStringList parseEnvPath(const QString &envImportPath)
static bool isPathQrcOrAbsolute(const QString &path)
void postProcessQmldir(QQmlTypeLoader::Blob *self, const QQmlTypeLoader::Blob::PendingImportPtr &import, const QString &qmldirFilePath, const URL &qmldirUrl)
QQmlRefPointer< Blob > handleExisting(const QQmlTypeLoaderSharedDataPtr &data, QQmlRefPointer< Blob > &&blob, QQmlTypeLoader::Mode mode)
static QString stripTrailingSlashes(const QString &path)
const QQmlPrivate::CachedQmlUnit * unit
CachedLoader(const QQmlPrivate::CachedQmlUnit *unit)
void loadThread(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
void loadAsync(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
void load(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
void load(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
StaticLoader(const QByteArray &data)
void loadThread(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
void loadAsync(QQmlTypeLoader *loader, const QQmlDataBlob::Ptr &blob) const
const QByteArray & data