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