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)
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 (data->importPaths.contains(cPath))
565 data->importPaths.move(data->importPaths.indexOf(cPath), 0);
566 else
567 data->importPaths.prepend(cPath);
568 }
569}
570
571/*!
572 \internal
573*/
574void QQmlTypeLoader::setImportPathList(const QStringList &paths)
575{
576 qCDebug(lcQmlImport) << "setImportPathList:" << paths;
577
578 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
579 data->importPaths.clear();
580 for (auto it = paths.crbegin(); it != paths.crend(); ++it)
581 addImportPath(*it);
582
583 // Our existing cached paths may have been invalidated
584 clearQmldirInfo();
585}
586
587
588/*!
589 \internal
590*/
591void QQmlTypeLoader::setPluginPathList(const QStringList &paths)
592{
593 qCDebug(lcQmlImport) << "setPluginPathList:" << paths;
594 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
595 data->pluginPaths = paths;
596}
597
598/*!
599 \internal
600*/
601void QQmlTypeLoader::addPluginPath(const QString& path)
602{
603 qCDebug(lcQmlImport) << "addPluginPath:" << path;
604
605 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
606
607 QUrl url = QUrl(path);
608 if (url.isRelative() || url.scheme() == QLatin1String("file")
609 || (url.scheme().size() == 1 && QFile::exists(path)) ) { // windows path
610 QDir dir = QDir(path);
611 data->pluginPaths.prepend(dir.canonicalPath());
612 } else {
613 data->pluginPaths.prepend(path);
614 }
615}
616
617#if QT_CONFIG(qml_network)
618QQmlNetworkAccessManagerFactoryPtrConst QQmlTypeLoader::networkAccessManagerFactory() const
619{
620 ASSERT_ENGINETHREAD();
621 return QQmlNetworkAccessManagerFactoryPtrConst(&m_data);
622}
623
624void QQmlTypeLoader::setNetworkAccessManagerFactory(QQmlNetworkAccessManagerFactory *factory)
625{
626 ASSERT_ENGINETHREAD();
627 QQmlNetworkAccessManagerFactoryPtr(&m_data).reset(factory);
628}
629
630QNetworkAccessManager *QQmlTypeLoader::createNetworkAccessManager(QObject *parent) const
631{
632 // Can be called from both threads, or even from a WorkerScript
633
634 // TODO: Calling the user's create() method under the lock is quite rude.
635 // However, we've been doing so for a long time and stopping the
636 // practice would expose thread safety issues in user code.
637 // ### Qt7: Maybe change the factory interface to provide a different method
638 // that can be called without the lock.
639 if (const auto factory = QQmlNetworkAccessManagerFactoryPtrConst(&m_data))
640 return factory->create(parent);
641
642 return new QNetworkAccessManager(parent);
643}
644#endif // QT_CONFIG(qml_network)
645
646void QQmlTypeLoader::clearQmldirInfo()
647{
648 QQmlTypeLoaderThreadDataPtr data(&m_data);
649
650 auto itr = data->qmldirInfo.constBegin();
651 while (itr != data->qmldirInfo.constEnd()) {
652 const QQmlTypeLoaderThreadData::QmldirInfo *cache = *itr;
653 do {
654 const QQmlTypeLoaderThreadData::QmldirInfo *nextCache = cache->next;
655 delete cache;
656 cache = nextCache;
657 } while (cache);
658
659 ++itr;
660 }
661 data->qmldirInfo.clear();
662}
663
665 const QQmlTypeLoaderConfiguredDataPtr &data, QV4::ExecutionEngine *engine)
666{
667 data->diskCacheOptions = engine->diskCacheOptions();
668 data->isDebugging = engine->debugger() != nullptr;
669 data->initialized = true;
670}
671
672void QQmlTypeLoader::startThread()
673{
675
676 if (!m_data.thread()) {
677 // Re-read the relevant configuration values at the last possible moment before we start
678 // the thread. After the thread has been started, changing the configuration would result
679 // in UB. Therefore we can disregard this case. We need to re-read it because a preview
680 // or a debugger may have been connected in between.
681 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
682 initializeConfiguredData(data, m_data.engine());
683 m_data.createThread(this);
684 }
685}
686
687void QQmlTypeLoader::shutdownThread()
688{
690
691 if (m_data.thread())
692 m_data.deleteThread();
693}
694
695QQmlTypeLoader::Blob::PendingImport::PendingImport(
696 const QQmlRefPointer<Blob> &blob, const QV4::CompiledData::Import *import,
697 QQmlImports::ImportFlags flags)
698 : uri(blob->stringAt(import->uriIndex))
699 , qualifier(blob->stringAt(import->qualifierIndex))
700 , type(static_cast<QV4::CompiledData::Import::ImportType>(quint32(import->type)))
701 , location(import->location)
702 , flags(flags)
703 , version(import->version)
704{
705}
706
707QQmlTypeLoader::Blob::Blob(const QUrl &url, QQmlDataBlob::Type type, QQmlTypeLoader *loader)
708 : QQmlDataBlob(url, type, loader)
709 , m_importCache(new QQmlImports(), QQmlRefPointer<QQmlImports>::Adopt)
710{
711}
712
713QQmlTypeLoader::Blob::~Blob()
714{
715}
716
717bool QQmlTypeLoader::Blob::fetchQmldir(
718 const QUrl &url, const QQmlTypeLoader::Blob::PendingImportPtr &import, int priority,
719 QList<QQmlError> *errors)
720{
721 assertTypeLoaderThread();
722
723 QQmlRefPointer<QQmlQmldirData> data = typeLoader()->getQmldir(url);
724
725 data->setPriority(this, import, priority);
726
727 if (data->status() == Error) {
728 // This qmldir must not exist - which is not an error
729 return true;
730 } else if (data->status() == Complete) {
731 // This data is already available
732 return qmldirDataAvailable(data, errors);
733 }
734
735 // Wait for this data to become available
736 addDependency(data.data());
737 return true;
738}
739
740/*!
741 * \internal
742 * Import any qualified scripts of for \a import as listed in \a qmldir.
743 * Precondition is that \a import is actually qualified.
744 */
745void QQmlTypeLoader::Blob::importQmldirScripts(
746 const QQmlTypeLoader::Blob::PendingImportPtr &import,
747 const QQmlTypeLoaderQmldirContent &qmldir, const QUrl &qmldirUrl)
748{
749 assertTypeLoaderThread();
750
751 const auto qmldirScripts = qmldir.scripts();
752 for (const QQmlDirParser::Script &script : qmldirScripts) {
753 const QUrl plainUrl = QUrl(script.fileName);
754 const QUrl scriptUrl = qmldirUrl.resolved(plainUrl);
755 QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl, plainUrl);
756
757 // Self-import via qmldir is OK-ish. We ignore it.
758 if (blob.data() == this)
759 continue;
760
761 addDependency(blob.data());
762 scriptImported(blob, import->location, script.nameSpace, import->qualifier);
763 }
764}
765
766template<typename URL>
768 QQmlTypeLoader::Blob *self,
769 const QQmlTypeLoader::Blob::PendingImportPtr &import, const QString &qmldirFilePath,
770 const URL &qmldirUrl)
771{
772 self->assertTypeLoaderThread();
773
774 const QQmlTypeLoaderQmldirContent qmldir = self->typeLoader()->qmldirContent(qmldirFilePath);
775 if (!import->qualifier.isEmpty())
776 self->importQmldirScripts(import, qmldir, QUrl(qmldirUrl));
777
778 if (qmldir.plugins().isEmpty()) {
779 // If the qmldir does not register a plugin, we might still have declaratively
780 // registered types (if we are dealing with an application instead of a library)
781 // We should use module name given in the qmldir rather than the one given by the
782 // import since the import may be a directory import.
783 auto module = QQmlMetaType::typeModule(qmldir.typeNamespace(), import->version);
784 if (!module)
785 QQmlMetaType::qmlRegisterModuleTypes(qmldir.typeNamespace());
786 // else: If the module already exists, the types must have been already registered
787 }
788}
789
791 const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
792{
793 QQmlError error;
794 QString reason = errors->front().description();
795 if (reason.size() > 512)
796 reason = reason.first(252) + QLatin1String("... ...") + reason.last(252);
797 if (import->version.hasMajorVersion()) {
798 error.setDescription(QQmlImports::tr(
799 "module \"%1\" version %2.%3 cannot be imported because:\n%4")
800 .arg(import->uri).arg(import->version.majorVersion())
801 .arg(import->version.hasMinorVersion()
802 ? QString::number(import->version.minorVersion())
803 : QLatin1String("x"))
804 .arg(reason));
805 } else {
806 error.setDescription(QQmlImports::tr("module \"%1\" cannot be imported because:\n%2")
807 .arg(import->uri, reason));
808 }
809 errors->prepend(error);
810}
811
812bool QQmlTypeLoader::Blob::handleLocalQmldirForImport(
813 const PendingImportPtr &import, const QString &qmldirFilePath,
814 const QString &qmldirUrl, QList<QQmlError> *errors)
815{
816 // This is a local library import
817 const QTypeRevision actualVersion = m_importCache->addLibraryImport(
818 typeLoader(), import->uri, import->qualifier, import->version, qmldirFilePath,
819 qmldirUrl, import->flags, import->precedence, errors);
820 if (!actualVersion.isValid())
821 return false;
822
823 // Use more specific version for dependencies if possible
824 if (actualVersion.hasMajorVersion())
825 import->version = actualVersion;
826
827 if (!loadImportDependencies(import, qmldirFilePath, import->flags, errors)) {
828 addDependencyImportError(import, errors);
829 return false;
830 }
831
832 postProcessQmldir(this, import, qmldirFilePath, qmldirUrl);
833 return true;
834}
835
836bool QQmlTypeLoader::Blob::updateQmldir(const QQmlRefPointer<QQmlQmldirData> &data, const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
837{
838 // TODO: Shouldn't this lock?
839
840 assertTypeLoaderThread();
841
842 QString qmldirIdentifier = data->urlString();
843 QString qmldirUrl = qmldirIdentifier.left(qmldirIdentifier.lastIndexOf(QLatin1Char('/')) + 1);
844
845 typeLoader()->setQmldirContent(qmldirIdentifier, data->content());
846
847 const QTypeRevision version = m_importCache->updateQmldirContent(
848 typeLoader(), import->uri, import->version, import->qualifier, qmldirIdentifier,
849 qmldirUrl, errors);
850 if (!version.isValid())
851 return false;
852
853 // Use more specific version for dependencies if possible
854 if (version.hasMajorVersion())
855 import->version = version;
856
857 if (!loadImportDependencies(import, qmldirIdentifier, import->flags, errors))
858 return false;
859
860 import->priority = 0;
861
862 // Release this reference at destruction
863 m_qmldirs << data;
864
865 postProcessQmldir(this, import, qmldirIdentifier, qmldirUrl);
866 return true;
867}
868
869bool QQmlTypeLoader::Blob::addScriptImport(const QQmlTypeLoader::Blob::PendingImportPtr &import)
870{
871 assertTypeLoaderThread();
872 const QUrl url(import->uri);
873 QQmlTypeLoader *loader = typeLoader();
874 QQmlRefPointer<QQmlScriptBlob> blob = loader->getScript(finalUrl().resolved(url), url);
875 addDependency(blob.data());
876 scriptImported(blob, import->location, import->qualifier, QString());
877 return true;
878}
879
880bool QQmlTypeLoader::Blob::addFileImport(const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
881{
882 assertTypeLoaderThread();
883 QQmlImports::ImportFlags flags;
884
885 QUrl importUrl(import->uri);
886 QString path = importUrl.path();
887 path.append(QLatin1String(path.endsWith(QLatin1Char('/')) ? "qmldir" : "/qmldir"));
888 importUrl.setPath(path);
889
890 // Can't resolve a relative URL if we don't know where we are
891 if (!finalUrl().isValid() && importUrl.isRelative()) {
892 QQmlError error;
893 error.setDescription(
894 QString::fromLatin1("Can't resolve relative qmldir URL %1 on invalid base URL")
895 .arg(importUrl.toString()));
896 errors->append(error);
897 return false;
898 }
899
900 QUrl qmldirUrl = finalUrl().resolved(importUrl);
901 if (!QQmlImports::isLocal(qmldirUrl)) {
902 // This is a remote file; the import is currently incomplete
903 flags = QQmlImports::ImportIncomplete;
904 }
905
906 const QTypeRevision version = m_importCache->addFileImport(
907 typeLoader(), import->uri, import->qualifier, import->version, flags,
908 import->precedence, nullptr, errors);
909 if (!version.isValid())
910 return false;
911
912 // Use more specific version for the qmldir if possible
913 if (version.hasMajorVersion())
914 import->version = version;
915
916 if (flags & QQmlImports::ImportIncomplete) {
917 if (!fetchQmldir(qmldirUrl, import, 1, errors))
918 return false;
919 } else {
920 const QString qmldirFilePath = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
921 if (!loadImportDependencies(import, qmldirFilePath, import->flags, errors))
922 return false;
923
924 postProcessQmldir(this, import, qmldirFilePath, qmldirUrl);
925 }
926
927 return true;
928}
929
930bool QQmlTypeLoader::Blob::addLibraryImport(const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
931{
932 assertTypeLoaderThread();
933
934 const LocalQmldirResult qmldirResult = typeLoader()->locateLocalQmldir(this, import, errors);
935 switch (qmldirResult) {
936 case QmldirFound:
937 return true;
938 case QmldirNotFound: {
939 if (!loadImportDependencies(import, QString(), import->flags, errors)) {
940 addDependencyImportError(import, errors);
941 return false;
942 }
943 break;
944 }
945 case QmldirInterceptedToRemote:
946 break;
947 case QmldirRejected:
948 return false;
949 }
950
951 // If there is a qmldir we cannot see, yet, then we have to wait.
952 // The qmldir might contain import directives.
953 // TODO: This should trigger on any potentially remote URLs, not only intercepted ones.
954 // However, fixing this would open the door for follow-up problems while providing
955 // rather limited benefits.
956 if (qmldirResult != QmldirInterceptedToRemote && registerPendingTypes(import)) {
957 if (m_importCache->addLibraryImport(
958 typeLoader(), import->uri, import->qualifier, import->version, QString(),
959 QString(), import->flags, import->precedence, errors).isValid()) {
960 return true;
961 }
962
963 return false;
964 }
965
966 // We haven't yet resolved this import
967 m_unresolvedImports << import;
968
969 // Add this library and request the possible locations for it
970 const QTypeRevision version = m_importCache->addLibraryImport(
971 typeLoader(), import->uri, import->qualifier, import->version, QString(),
972 QString(), import->flags | QQmlImports::ImportIncomplete, import->precedence,
973 errors);
974
975 if (!version.isValid())
976 return false;
977
978 // Use more specific version for finding the qmldir if possible
979 if (version.hasMajorVersion())
980 import->version = version;
981
982 const bool hasInterceptors = m_typeLoader->hasUrlInterceptors();
983
984 // Query any network import paths for this library.
985 // Interceptor might redirect local paths.
986 QStringList remotePathList = typeLoader()->importPathList(
987 hasInterceptors ? LocalOrRemote : Remote);
988 if (!remotePathList.isEmpty()) {
989 // Probe for all possible locations
990 int priority = 0;
991 const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(
992 import->uri, remotePathList, import->version);
993 for (const QString &qmldirPath : qmlDirPaths) {
994 if (hasInterceptors) {
995 QUrl url = m_typeLoader->interceptUrl(
996 QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
997 QQmlAbstractUrlInterceptor::QmldirFile);
998 if (!QQmlFile::isLocalFile(url)
999 && !fetchQmldir(url, import, ++priority, errors)) {
1000 return false;
1001 }
1002 } else if (!fetchQmldir(QUrl(qmldirPath), import, ++priority, errors)) {
1003 return false;
1004 }
1005 }
1006 }
1007
1008 return true;
1009}
1010
1011bool QQmlTypeLoader::Blob::registerPendingTypes(const PendingImportPtr &import)
1012{
1013 assertTypeLoaderThread();
1014
1015 return
1016 // Major version of module already registered:
1017 // We believe that the registration is complete.
1018 QQmlMetaType::typeModule(import->uri, import->version)
1019
1020 // Otherwise, try to register further module types.
1021 || QQmlMetaType::qmlRegisterModuleTypes(import->uri)
1022
1023 // Otherwise, there is no way to register any further types.
1024 // Try with any module of that name.
1025 || QQmlMetaType::latestModuleVersion(import->uri).isValid();
1026}
1027
1028bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import,
1029 QQmlImports::ImportFlags flags, QList<QQmlError> *errors)
1030{
1031 assertTypeLoaderThread();
1032 return addImport(std::make_shared<PendingImport>(this, import, flags), errors);
1033}
1034
1035bool QQmlTypeLoader::Blob::addImport(
1036 const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
1037{
1038 assertTypeLoaderThread();
1039
1040 Q_ASSERT(errors);
1041
1042 switch (import->type)
1043 {
1044 case QV4::CompiledData::Import::ImportLibrary:
1045 return addLibraryImport(import, errors);
1046 case QV4::CompiledData::Import::ImportFile:
1047 return addFileImport(import ,errors);
1048 case QV4::CompiledData::Import::ImportScript:
1049 return addScriptImport(import);
1050 case QV4::CompiledData::Import::ImportInlineComponent:
1051 Q_UNREACHABLE_RETURN(false); // addImport is never called with an inline component import
1052 }
1053
1054 Q_UNREACHABLE_RETURN(false);
1055}
1056
1057void QQmlTypeLoader::Blob::dependencyComplete(const QQmlDataBlob::Ptr &blob)
1058{
1059 assertTypeLoaderThread();
1060
1061 if (blob->type() == QQmlDataBlob::QmldirFile) {
1062 QQmlQmldirData *data = static_cast<QQmlQmldirData *>(blob.data());
1063 QList<QQmlError> errors;
1064 if (!qmldirDataAvailable(data, &errors)) {
1065 Q_ASSERT(errors.size());
1066 QQmlError error(errors.takeFirst());
1067 error.setUrl(m_importCache->baseUrl());
1068 const QV4::CompiledData::Location importLocation = data->importLocation(this);
1069 error.setLine(qmlConvertSourceCoordinate<quint32, int>(importLocation.line()));
1070 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(importLocation.column()));
1071 errors.prepend(error); // put it back on the list after filling out information.
1072 setError(errors);
1073 }
1074 }
1075}
1076
1077bool QQmlTypeLoader::Blob::loadDependentImports(
1078 const QList<QQmlDirParser::Import> &imports, const QString &qualifier,
1079 QTypeRevision version, quint8 precedence, QQmlImports::ImportFlags flags,
1080 QList<QQmlError> *errors)
1081{
1082 assertTypeLoaderThread();
1083
1084 for (const auto &import : imports) {
1085 if (import.flags & QQmlDirParser::Import::Optional)
1086 continue;
1087 auto dependencyImport = std::make_shared<PendingImport>();
1088 dependencyImport->uri = import.module;
1089 dependencyImport->qualifier = qualifier;
1090 dependencyImport->version = (import.flags & QQmlDirParser::Import::Auto)
1091 ? version : import.version;
1092 dependencyImport->flags = flags;
1093 dependencyImport->precedence = precedence;
1094
1095 qCDebug(lcQmlImport)
1096 << "loading dependent import" << dependencyImport->uri << "version"
1097 << dependencyImport->version << "as" << dependencyImport->qualifier;
1098
1099 if (!addImport(dependencyImport, errors)) {
1100 QQmlError error;
1101 error.setDescription(
1102 QString::fromLatin1(
1103 "Failed to load dependent import \"%1\" version %2.%3")
1104 .arg(dependencyImport->uri)
1105 .arg(dependencyImport->version.majorVersion())
1106 .arg(dependencyImport->version.minorVersion()));
1107 errors->append(error);
1108 return false;
1109 }
1110 }
1111
1112 return true;
1113}
1114
1115bool QQmlTypeLoader::Blob::loadImportDependencies(
1116 const QQmlTypeLoader::Blob::PendingImportPtr &currentImport, const QString &qmldirUri,
1117 QQmlImports::ImportFlags flags, QList<QQmlError> *errors)
1118{
1119 assertTypeLoaderThread();
1120
1121 QList<QQmlDirParser::Import> implicitImports
1122 = QQmlMetaType::moduleImports(currentImport->uri, currentImport->version);
1123 if (!qmldirUri.isEmpty())
1124 implicitImports += typeLoader()->qmldirContent(qmldirUri).imports();
1125
1126 // Prevent overflow from one category of import into the other.
1127 switch (currentImport->precedence) {
1128 case QQmlImportInstance::Implicit - 1:
1129 case QQmlImportInstance::Lowest: {
1130 QQmlError error;
1131 error.setDescription(
1132 QString::fromLatin1("Too many dependent imports for %1 %2.%3")
1133 .arg(currentImport->uri)
1134 .arg(currentImport->version.majorVersion())
1135 .arg(currentImport->version.minorVersion()));
1136 errors->append(error);
1137 return false;
1138 }
1139 default:
1140 break;
1141 }
1142
1143 if (!loadDependentImports(
1144 implicitImports, currentImport->qualifier, currentImport->version,
1145 currentImport->precedence + 1, flags, errors)) {
1146 QQmlError error;
1147 error.setDescription(
1148 QString::fromLatin1(
1149 "Failed to load dependencies for module \"%1\" version %2.%3")
1150 .arg(currentImport->uri)
1151 .arg(currentImport->version.majorVersion())
1152 .arg(currentImport->version.minorVersion()));
1153 errors->append(error);
1154 return false;
1155 }
1156
1157 return true;
1158}
1159
1160static QQmlTypeLoaderConfiguredDataConstPtr configuredData(QQmlTypeLoaderLockedData *m_data)
1161{
1162 if (!QQmlTypeLoaderConfiguredDataConstPtr(m_data)->initialized)
1163 initializeConfiguredData(QQmlTypeLoaderConfiguredDataPtr(m_data), m_data->engine());
1164
1165 return QQmlTypeLoaderConfiguredDataConstPtr(m_data);
1166}
1167
1168bool QQmlTypeLoader::isDebugging()
1169{
1170 return configuredData(&m_data)->isDebugging;
1171}
1172
1173bool QQmlTypeLoader::readCacheFile()
1174{
1175 return configuredData(&m_data)->diskCacheOptions & QV4::ExecutionEngine::DiskCache::QmlcRead;
1176}
1177
1178bool QQmlTypeLoader::writeCacheFile()
1179{
1180 return configuredData(&m_data)->diskCacheOptions & QV4::ExecutionEngine::DiskCache::QmlcWrite;
1181}
1182
1183QQmlMetaType::CacheMode QQmlTypeLoader::aotCacheMode()
1184{
1185 const QV4::ExecutionEngine::DiskCacheOptions options
1186 = configuredData(&m_data)->diskCacheOptions;
1187 if (!(options & QV4::ExecutionEngine::DiskCache::Aot))
1188 return QQmlMetaType::RejectAll;
1189 if (options & QV4::ExecutionEngine::DiskCache::AotByteCode)
1190 return QQmlMetaType::AcceptUntyped;
1191 return QQmlMetaType::RequireFullyTyped;
1192}
1193
1194bool QQmlTypeLoader::Blob::qmldirDataAvailable(const QQmlRefPointer<QQmlQmldirData> &data, QList<QQmlError> *errors)
1195{
1196 assertTypeLoaderThread();
1197 return data->processImports(this, [&](const PendingImportPtr &import) {
1198 return updateQmldir(data, import, errors);
1199 });
1200}
1201
1202static QStringList parseEnvPath(const QString &envImportPath)
1203{
1204 if (QDir::listSeparator() == u':') {
1205 // Double colons are interpreted as separator + resource path.
1206 QStringList paths = envImportPath.split(u':');
1207 bool wasEmpty = false;
1208 for (auto it = paths.begin(); it != paths.end();) {
1209 if (it->isEmpty()) {
1210 wasEmpty = true;
1211 it = paths.erase(it);
1212 } else {
1213 if (wasEmpty) {
1214 it->prepend(u':');
1215 wasEmpty = false;
1216 }
1217 ++it;
1218 }
1219 }
1220 return paths;
1221 } else {
1222 return envImportPath.split(QDir::listSeparator(), Qt::SkipEmptyParts);
1223 }
1224}
1225
1226/*!
1227Constructs a new type loader that uses the given \a engine.
1228*/
1229QQmlTypeLoader::QQmlTypeLoader(QV4::ExecutionEngine *engine)
1230 : m_data(engine)
1231{
1232 QQmlTypeLoaderConfiguredDataPtr data(&m_data);
1233 data->pluginPaths << QLatin1String(".");
1234 // Search order is:
1235 // 1. android or macos specific bundle paths.
1236 // 2. applicationDirPath()
1237 // 3. qrc:/qt-project.org/imports
1238 // 4. qrc:/qt/qml
1239 // 5. $QML2_IMPORT_PATH
1240 // 6. $QML_IMPORT_PATH
1241 // 7. QLibraryInfo::QmlImportsPath
1242 //
1243 // ... unless we're a plugin application. Then we don't pick up any special paths
1244 // from environment variables or bundles and we also don't add the application dir path.
1245
1246 const auto paths = QLibraryInfo::paths(QLibraryInfo::QmlImportsPath);
1247 for (const auto &installImportsPath: paths)
1248 addImportPath(installImportsPath);
1249
1250 const bool isPluginApplication = QCoreApplication::testAttribute(Qt::AA_PluginApplication);
1251
1252 auto addEnvImportPath = [this, isPluginApplication](const char *var) {
1253 if (Q_UNLIKELY(!isPluginApplication && !qEnvironmentVariableIsEmpty(var))) {
1254 const QStringList paths = parseEnvPath(qEnvironmentVariable(var));
1255 for (int ii = paths.size() - 1; ii >= 0; --ii)
1256 addImportPath(paths.at(ii));
1257 }
1258 };
1259
1260 // env import paths
1261 addEnvImportPath("QML_IMPORT_PATH");
1262 addEnvImportPath("QML2_IMPORT_PATH");
1263
1264 addImportPath(QStringLiteral("qrc:/qt/qml"));
1265 addImportPath(QStringLiteral("qrc:/qt-project.org/imports"));
1266
1267 if (!isPluginApplication)
1268 addImportPath(QCoreApplication::applicationDirPath());
1269
1270 auto addEnvPluginPath = [this, isPluginApplication](const char *var) {
1271 if (Q_UNLIKELY(!isPluginApplication && !qEnvironmentVariableIsEmpty(var))) {
1272 const QStringList paths = parseEnvPath(qEnvironmentVariable(var));
1273 for (int ii = paths.size() - 1; ii >= 0; --ii)
1274 addPluginPath(paths.at(ii));
1275 }
1276 };
1277
1278 addEnvPluginPath("QML_PLUGIN_PATH");
1279#if defined(Q_OS_ANDROID)
1280 addImportPath(QStringLiteral("qrc:/android_rcc_bundle/qml"));
1281 addEnvPluginPath("QT_BUNDLED_LIBS_PATH");
1282#elif defined(Q_OS_MACOS)
1283 // Add the main bundle's Resources/qml directory as an import path, so that QML modules are
1284 // found successfully when running the app from its build dir.
1285 // This is where macdeployqt and our CMake deployment logic puts Qt and user qmldir files.
1286 if (!isPluginApplication) {
1287 if (CFBundleRef bundleRef = CFBundleGetMainBundle()) {
1288 if (QCFType<CFURLRef> urlRef = CFBundleCopyResourceURL(
1289 bundleRef, QCFString(QLatin1String("qml")), 0, 0)) {
1290 if (QCFType<CFURLRef> absoluteUrlRef = CFURLCopyAbsoluteURL(urlRef)) {
1291 if (QCFString path = CFURLCopyFileSystemPath(
1292 absoluteUrlRef, kCFURLPOSIXPathStyle)) {
1293 if (QFile::exists(path)) {
1294 addImportPath(QDir(path).canonicalPath());
1295 }
1296 }
1297 }
1298 }
1299 }
1300 }
1301#endif // Q_OS_DARWIN
1302}
1303
1304/*!
1305Destroys the type loader, first clearing the cache of any information about
1306loaded files.
1307*/
1308QQmlTypeLoader::~QQmlTypeLoader()
1309{
1311
1312 shutdownThread();
1313
1314 // Delete the thread before clearing the cache. Otherwise it will be started up again.
1315 invalidate();
1316
1317 clearCache();
1318
1319 clearQmldirInfo();
1320}
1321
1322template<typename Blob>
1324 const QQmlTypeLoaderSharedDataPtr &data, QQmlRefPointer<Blob> &&blob,
1325 QQmlTypeLoader::Mode mode)
1326{
1327 if ((mode == QQmlTypeLoader::PreferSynchronous && QQmlFile::isSynchronous(blob->finalUrl()))
1328 || mode == QQmlTypeLoader::Synchronous) {
1329 // this was started Asynchronous, but we need to force Synchronous
1330 // completion now.
1331
1332 // This only works when called directly from e.g. the UI thread, but not
1333 // when recursively called on the QML thread via resolveTypes()
1334
1335 // NB: We do not want to know whether the thread is the main thread, but specifically
1336 // that the thread is _not_ the thread we're waiting for.
1337 // If !QT_CONFIG(qml_type_loader_thread) the QML thread is the main thread.
1338
1339 QQmlTypeLoaderThread *thread = data.thread();
1340 if (thread && !thread->isThisThread()) {
1341 while (!blob->isCompleteOrError())
1342 thread->waitForNextMessage(); // Requires lock to be held, via data above
1343 }
1344 }
1345 return blob;
1346}
1347
1348/*!
1349Returns a QQmlTypeData for the specified \a url. The QQmlTypeData may be cached.
1350*/
1351QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QUrl &unNormalizedUrl, Mode mode)
1352{
1353 // This can be called from either thread.
1354
1355 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1356 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1357 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1358
1359 QQmlRefPointer<QQmlTypeData> typeData;
1360 {
1361 const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl);
1362 QQmlTypeLoaderSharedDataPtr data(&m_data);
1363
1364 typeData = data->typeCache.value(url);
1365 if (typeData)
1366 return handleExisting(data, std::move(typeData), mode);
1367
1368 // Trim before adding the new type, so that we don't immediately trim it away
1369 if (data->typeCache.size() >= data->typeCacheTrimThreshold)
1370 trimCache(data);
1371
1372 typeData = QQml::makeRefPointer<QQmlTypeData>(url, this);
1373
1374 // TODO: if (compiledData == 0), is it safe to omit this insertion?
1375 data->typeCache.insert(url, typeData);
1376 }
1377
1378 return finalizeBlob(std::move(typeData), mode);
1379}
1380
1381/*!
1382Returns a QQmlTypeData for the given \a data with the provided base \a url. The
1383QQmlTypeData will not be cached.
1384*/
1385QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(
1386 const QByteArray &data, const QUrl &url, Mode mode)
1387{
1388 // Can be called from either thread.
1389
1390 QQmlRefPointer<QQmlTypeData> typeData = QQml::makeRefPointer<QQmlTypeData>(url, this);
1391 QQmlTypeLoader::loadWithStaticData(QQmlDataBlob::Ptr(typeData.data()), data, mode);
1392 return typeData;
1393}
1394
1395static bool isModuleUrl(const QUrl &url)
1396{
1397 return url.fragment() == QLatin1String("module") || url.path().endsWith(QLatin1String(".mjs"));
1398}
1399
1400QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(const QUrl &unNormalizedUrl, Mode mode)
1401{
1402 // This can be called from either thread.
1403
1404 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1405 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1406 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1407
1408 QQmlRefPointer<QQmlScriptBlob> scriptBlob;
1409 {
1410 const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl);
1411 QQmlTypeLoaderSharedDataPtr data(&m_data);
1412
1413 scriptBlob = data->scriptCache.value(url);
1414 if (scriptBlob)
1415 return handleExisting(data, std::move(scriptBlob), mode);
1416
1417 scriptBlob = QQml::makeRefPointer<QQmlScriptBlob>(url, this, isModuleUrl(url)
1418 ? QQmlScriptBlob::IsESModule::Yes
1419 : QQmlScriptBlob::IsESModule::No);
1420 data->scriptCache.insert(url, scriptBlob);
1421 }
1422
1423 return finalizeBlob(std::move(scriptBlob), mode);
1424}
1425
1426QQmlRefPointer<QV4::CompiledData::CompilationUnit> QQmlTypeLoader::injectModule(
1427 const QUrl &relativeUrl, const QV4::CompiledData::Unit *unit)
1428{
1430
1431 QQmlRefPointer<QQmlScriptBlob> blob = QQml::makeRefPointer<QQmlScriptBlob>(
1432 relativeUrl, this, QQmlScriptBlob::IsESModule::Yes);
1433 QQmlPrivate::CachedQmlUnit cached { unit, nullptr, nullptr};
1434
1435 {
1436 QQmlTypeLoaderSharedDataPtr data(&m_data);
1437 data->scriptCache.insert(relativeUrl, blob);
1438 }
1439
1440 loadWithCachedUnit(blob.data(), &cached, Synchronous);
1441 Q_ASSERT(blob->isComplete());
1442 return blob->scriptData()->compilationUnit();
1443}
1444
1445/*!
1446Return a QQmlScriptBlob for \a unNormalizedUrl or \a relativeUrl.
1447This assumes PreferSynchronous, and therefore the result may not be ready yet.
1448*/
1449QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(
1450 const QUrl &unNormalizedUrl, const QUrl &relativeUrl)
1451{
1452 // Can be called from either thread
1453
1454 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1455 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1456 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1457
1458 const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl);
1459
1460 QQmlRefPointer<QQmlScriptBlob> scriptBlob;
1461 {
1462 QQmlTypeLoaderSharedDataPtr data(&m_data);
1463 scriptBlob = data->scriptCache.value(url);
1464
1465 // Also try the relative URL since manually registering native modules doesn't require
1466 // passing an absolute URL and we don't have a reference URL for native modules.
1467 if (!scriptBlob && unNormalizedUrl != relativeUrl)
1468 scriptBlob = data->scriptCache.value(relativeUrl);
1469
1470 // Do not try to finish the loading via handleExisting() here.
1471 if (scriptBlob)
1472 return scriptBlob;
1473
1474 scriptBlob = QQml::makeRefPointer<QQmlScriptBlob>(url, this, isModuleUrl(url)
1475 ? QQmlScriptBlob::IsESModule::Yes
1476 : QQmlScriptBlob::IsESModule::No);
1477 data->scriptCache.insert(url, scriptBlob);
1478 }
1479
1480 return finalizeBlob(std::move(scriptBlob), PreferSynchronous);
1481}
1482
1483/*!
1484Returns a QQmlQmldirData for \a url. The QQmlQmldirData may be cached.
1485*/
1486QQmlRefPointer<QQmlQmldirData> QQmlTypeLoader::getQmldir(const QUrl &url)
1487{
1488 // Can be called from either thread.
1489
1490 Q_ASSERT(!url.isRelative() &&
1491 (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() ||
1492 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url))));
1493
1494 QQmlRefPointer<QQmlQmldirData> qmldirData;
1495 {
1496 QQmlTypeLoaderSharedDataPtr data(&m_data);
1497 qmldirData = data->qmldirCache.value(url);
1498 if (qmldirData)
1499 return qmldirData;
1500
1501 qmldirData = QQml::makeRefPointer<QQmlQmldirData>(url, this);
1502 data->qmldirCache.insert(url, qmldirData);
1503 }
1504
1505 QQmlTypeLoader::load(QQmlDataBlob::Ptr(qmldirData.data()));
1506 return qmldirData;
1507}
1508
1509/*!
1510Returns the absolute filename of path via a directory cache.
1511Returns a empty string if the path does not exist.
1512
1513Why a directory cache? QML checks for files in many paths with
1514invalid directories. By caching whether a directory exists
1515we avoid many stats. We also cache the files' existence in the
1516directory, for the same reason.
1517*/
1518QString QQmlTypeLoader::absoluteFilePath(const QString &path)
1519{
1520 // Can be called from either thread.
1521
1522 if (path.isEmpty())
1523 return QString();
1524 if (path.at(0) == QLatin1Char(':')) {
1525 // qrc resource
1526 QFileInfo fileInfo(path);
1527 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1528 } else if (path.size() > 3 && path.at(3) == QLatin1Char(':') &&
1529 path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) {
1530 // qrc resource url
1531 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1532 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1533 }
1534#if defined(Q_OS_ANDROID)
1535 else if (path.size() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') &&
1536 path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) {
1537 // assets resource url
1538 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1539 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1540 } else if (path.size() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') &&
1541 path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) {
1542 // content url
1543 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1544 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1545 }
1546#endif
1547
1548 return fileExists(path)
1549 ? QFileInfo(path).absoluteFilePath()
1550 : QString();
1551}
1552
1554{
1555 return QDirListing::IteratorFlag::CaseSensitive | QDirListing::IteratorFlag::IncludeHidden;
1556}
1557
1560 QCache<QString, bool> *fileSet, const QString &path, const QString &file)
1561{
1562 const QDirListing listing(path, dirListingFlags());
1563 bool seen = false;
1564 for (const auto &entry : listing) {
1565 const QString next = entry.fileName();
1566 if (next == file)
1567 seen = true;
1568 fileSet->insert(next, new bool(true));
1569 if (fileSet->totalCost() == fileSet->maxCost())
1570 break;
1571 }
1572
1573 if (seen)
1575 if (fileSet->totalCost() < fileSet->maxCost())
1578}
1579
1580static QString stripTrailingSlashes(const QString &path)
1581{
1582 for (qsizetype length = path.size(); length > 0; --length) {
1583 if (path[length - 1] != QLatin1Char('/'))
1584 return path.left(length);
1585 }
1586
1587 return QString();
1588}
1589
1590bool QQmlTypeLoader::fileExists(const QString &dirPath, const QString &file)
1591{
1592 // Can be called from either thread.
1593
1594 // We want to use QDirListing here because that gives us case-sensitive results even on
1595 // case-insensitive file systems. That is, for a file date.qml it only lists date.qml, not
1596 // Date.qml, dAte.qml, DATE.qml etc. QFileInfo::exists(), on the other hand, will happily
1597 // claim that Date.qml exists in such a situation on a case-insensitive file sysem. Such a
1598 // thing then shadows the JavaScript Date object and disaster ensues.
1599
1600 const QString path = stripTrailingSlashes(dirPath);
1601
1602 const QChar nullChar(QChar::Null);
1603 if (path.isEmpty() || path.contains(nullChar) || file.isEmpty() || file.contains(nullChar))
1604 return false;
1605
1606
1607 QQmlTypeLoaderSharedDataPtr data(&m_data);
1608 QCache<QString, bool> *fileSet = data->importDirCache.object(path);
1609 if (fileSet) {
1610 if (const bool *exists = fileSet->object(file))
1611 return *exists;
1612
1613 // If the cache isn't full, we know that we've scanned the whole directory.
1614 // The file not being in the cache then means it doesn't exist.
1615 if (fileSet->totalCost() < fileSet->maxCost())
1616 return false;
1617
1618 } else if (data->importDirCache.contains(path)) {
1619 // explicit nullptr in cache
1620 return false;
1621 }
1622
1623 auto addToCache = [&](const QString &path, const QString &file) {
1624 const QDir dir(path);
1625
1626 if (!fileSet) {
1627 // First try to cache the whole directory, but only up to the maxCost of the cache.
1628
1629 fileSet = dir.exists() ? new QCache<QString, bool> : nullptr;
1630 const bool inserted = data->importDirCache.insert(path, fileSet);
1631 Q_ASSERT(inserted);
1632 if (!fileSet)
1633 return false;
1634
1635 switch (populateFileSet(fileSet, dir.path(), file)) {
1636 case FileSetPopulateResult::NotFound:
1637 return false;
1638 case FileSetPopulateResult::Found:
1639 return true;
1640 case FileSetPopulateResult::Overflow:
1641 break;
1642 }
1643
1644 // Cache overflow. Look up files individually
1645 } else {
1646 // If the directory was completely cached, we'd have returned early above.
1647 Q_ASSERT(fileSet->totalCost() == fileSet->maxCost());
1648
1649 }
1650
1651 const QDirListing singleFile(dir.path(), {file}, dirListingFlags());
1652 const bool exists = singleFile.begin() != singleFile.end();
1653 fileSet->insert(file, new bool(exists));
1654 Q_ASSERT(fileSet->totalCost() == fileSet->maxCost());
1655 return exists;
1656 };
1657
1658 if (path.at(0) == QLatin1Char(':')) {
1659 // qrc resource
1660 return addToCache(path, file);
1661 }
1662
1663 if (path.size() > 3 && path.at(3) == QLatin1Char(':')
1664 && path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) {
1665 // qrc resource url
1666 return addToCache(QQmlFile::urlToLocalFileOrQrc(path), file);
1667 }
1668
1669#if defined(Q_OS_ANDROID)
1670 if (path.size() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/')
1671 && path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) {
1672 // assets resource url
1673 return addToCache(QQmlFile::urlToLocalFileOrQrc(path), file);
1674 }
1675
1676 if (path.size() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/')
1677 && path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) {
1678 // content url
1679 return addToCache(QQmlFile::urlToLocalFileOrQrc(path), file);
1680 }
1681#endif
1682
1683 return addToCache(path, file);
1684}
1685
1686
1687/*!
1688Returns true if the path is a directory via a directory cache. Cache is
1689shared with absoluteFilePath().
1690*/
1691bool QQmlTypeLoader::directoryExists(const QString &path)
1692{
1693 // Can be called from either thread.
1694
1695 if (path.isEmpty())
1696 return false;
1697
1698 bool isResource = path.at(0) == QLatin1Char(':');
1699#if defined(Q_OS_ANDROID)
1700 isResource = isResource || path.startsWith(QLatin1String("assets:/")) || path.startsWith(QLatin1String("content:/"));
1701#endif
1702
1703 if (isResource) {
1704 // qrc resource
1705 QFileInfo fileInfo(path);
1706 return fileInfo.exists() && fileInfo.isDir();
1707 }
1708
1709 const QString dirPath = stripTrailingSlashes(path);
1710
1711 QQmlTypeLoaderSharedDataPtr data(&m_data);
1712 if (!data->importDirCache.contains(dirPath)) {
1713 if (QDir(dirPath).exists()) {
1714 QCache<QString, bool> *files = new QCache<QString, bool>;
1715 populateFileSet(files, dirPath, QString());
1716 data->importDirCache.insert(dirPath, files);
1717 return true;
1718 }
1719
1720 data->importDirCache.insert(dirPath, nullptr);
1721 return false;
1722 }
1723
1724 return data->importDirCache.object(dirPath) != nullptr;
1725}
1726
1727
1728/*!
1729Return a QQmlTypeLoaderQmldirContent for absoluteFilePath. The QQmlTypeLoaderQmldirContent may be cached.
1730
1731\a filePath is a local file path.
1732
1733It can also be a remote path for a remote directory import, but it will have been cached by now in this case.
1734*/
1735const QQmlTypeLoaderQmldirContent QQmlTypeLoader::qmldirContent(const QString &filePathIn)
1736{
1738
1739 QString filePath;
1740
1741 // Try to guess if filePathIn is already a URL. This is necessarily fragile, because
1742 // - paths can contain ':', which might make them appear as URLs with schemes.
1743 // - windows drive letters appear as schemes (thus "< 2" below).
1744 // - a "file:" URL is equivalent to the respective file, but will be treated differently.
1745 // Yet, this heuristic is the best we can do until we pass more structured information here,
1746 // for example a QUrl also for local files.
1747 QUrl url(filePathIn);
1748
1749 QQmlTypeLoaderThreadDataPtr data(&m_data);
1750
1751 if (url.scheme().size() < 2) {
1752 filePath = filePathIn;
1753 } else {
1754 filePath = QQmlFile::urlToLocalFileOrQrc(url);
1755 if (filePath.isEmpty()) { // Can't load the remote here, but should be cached
1756 if (auto entry = data->importQmlDirCache.value(filePathIn))
1757 return **entry;
1758 else
1759 return QQmlTypeLoaderQmldirContent();
1760 }
1761 }
1762
1763 QQmlTypeLoaderQmldirContent **val = data->importQmlDirCache.value(filePath);
1764 if (val)
1765 return **val;
1766 QQmlTypeLoaderQmldirContent *qmldir = new QQmlTypeLoaderQmldirContent;
1767
1768#define ERROR(description) { QQmlError e; e.setDescription(description); qmldir->setError(e); }
1769#define NOT_READABLE_ERROR QString(QLatin1String("module \"$$URI$$\" definition \"%1\" not readable"))
1770#define NOT_FOUND_ERROR QString(QLatin1String("cannot load module \"$$URI$$\": File \"%1\" not found"))
1771
1772 QFile file(filePath);
1773 if (!fileExists(filePath)) {
1774 ERROR(NOT_FOUND_ERROR.arg(filePath));
1775 } else if (file.open(QFile::ReadOnly)) {
1776 QByteArray data = file.readAll();
1777 qmldir->setContent(filePath, QString::fromUtf8(data));
1778 } else {
1779 ERROR(NOT_READABLE_ERROR.arg(filePath));
1780 }
1781
1782#undef ERROR
1783#undef NOT_READABLE_ERROR
1784#undef CASE_MISMATCH_ERROR
1785
1786 data->importQmlDirCache.insert(filePath, qmldir);
1787 return *qmldir;
1788}
1789
1790void QQmlTypeLoader::setQmldirContent(const QString &url, const QString &content)
1791{
1793
1794 QQmlTypeLoaderThreadDataPtr data(&m_data);
1795 QQmlTypeLoaderQmldirContent *qmldir;
1796 QQmlTypeLoaderQmldirContent **val = data->importQmlDirCache.value(url);
1797 if (val) {
1798 qmldir = *val;
1799 } else {
1800 qmldir = new QQmlTypeLoaderQmldirContent;
1801 data->importQmlDirCache.insert(url, qmldir);
1802 }
1803
1804 if (!qmldir->hasContent())
1805 qmldir->setContent(url, content);
1806}
1807
1808template<typename Blob>
1809void clearBlobs(QHash<QUrl, QQmlRefPointer<Blob>> *blobs)
1810{
1811 std::for_each(blobs->cbegin(), blobs->cend(), [](const QQmlRefPointer<Blob> &blob) {
1812 blob->resetTypeLoader();
1813 });
1814 blobs->clear();
1815}
1816
1817/*!
1818Clears cached information about loaded files, including any type data, scripts
1819and qmldir information.
1820*/
1821void QQmlTypeLoader::clearCache()
1822{
1823 // This looks dangerous because we're dropping live blobs on the engine thread.
1824 // However, it's safe because we shut down the type loader thread before we do so.
1825
1827
1828 // Temporarily shut the thread down and discard all messages, making it safe to
1829 // hack into the various data structures below.
1830 shutdownThread();
1831
1832 QQmlTypeLoaderThreadDataPtr threadData(&m_data);
1833 qDeleteAll(threadData->importQmlDirCache);
1834 threadData->checksumCache.clear();
1835 threadData->importQmlDirCache.clear();
1836
1837 QQmlTypeLoaderSharedDataPtr data(&m_data);
1838 clearBlobs(&data->typeCache);
1839 clearBlobs(&data->scriptCache);
1840 clearBlobs(&data->qmldirCache);
1841 data->typeCacheTrimThreshold = QQmlTypeLoaderSharedData::MinimumTypeCacheTrimThreshold;
1842 data->importDirCache.clear();
1843
1844 // The thread will auto-restart next time we need it.
1845}
1846
1847void QQmlTypeLoader::trimCache()
1848{
1849 const QQmlTypeLoaderSharedDataPtr data(&m_data);
1850 trimCache(data);
1851}
1852
1853void QQmlTypeLoader::updateTypeCacheTrimThreshold(const QQmlTypeLoaderSharedDataPtr &data)
1854{
1855 // This can be called from either thread and is called from a method that locks.
1856
1857 int size = data->typeCache.size();
1858 if (size > data->typeCacheTrimThreshold)
1859 data->typeCacheTrimThreshold = size * 2;
1860 if (size < data->typeCacheTrimThreshold / 2) {
1861 data->typeCacheTrimThreshold
1862 = qMax(size * 2, int(QQmlTypeLoaderSharedData::MinimumTypeCacheTrimThreshold));
1863 }
1864}
1865
1866void QQmlTypeLoader::trimCache(const QQmlTypeLoaderSharedDataPtr &data)
1867{
1868 // This can be called from either thread. It has to be called while the type loader mutex
1869 // is locked. It drops potentially live blobs, but only ones which are isCompleteOrError and
1870 // are not depended on by other blobs.
1871
1872 while (true) {
1873 bool deletedOneType = false;
1874 for (auto iter = data->typeCache.begin(), end = data->typeCache.end(); iter != end;) {
1875 const QQmlRefPointer<QQmlTypeData> &typeData = iter.value();
1876
1877 // typeData->m_compiledData may be set early on in the proccess of loading a file, so
1878 // it's important to check the general loading status of the typeData before making any
1879 // other decisions.
1880 if (typeData->count() != 1 || !typeData->isCompleteOrError()) {
1881 ++iter;
1882 continue;
1883 }
1884
1885 // isCompleteOrError means the waitingFor list of this typeData is empty.
1886 // Therefore, it cannot interfere with other blobs on destruction anymore.
1887 // Therefore, we can drop it on either the engine thread or the type loader thread.
1888
1889 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit
1890 = typeData->m_compiledData;
1891 if (compilationUnit) {
1892 if (compilationUnit->count()
1893 > QQmlMetaType::countInternalCompositeTypeSelfReferences(
1894 compilationUnit) + 1) {
1895 ++iter;
1896 continue;
1897 }
1898
1899 QQmlMetaType::unregisterInternalCompositeType(compilationUnit);
1900 Q_ASSERT(compilationUnit->count() == 1);
1901 }
1902
1903 // There are no live objects of this type
1904 iter = data->typeCache.erase(iter);
1905 deletedOneType = true;
1906 }
1907
1908 if (!deletedOneType)
1909 break;
1910 }
1911
1912 // TODO: release any scripts which are no longer referenced by any types
1913
1914 updateTypeCacheTrimThreshold(data);
1915
1916 QQmlMetaType::freeUnusedTypesAndCaches();
1917}
1918
1919bool QQmlTypeLoader::isTypeLoaded(const QUrl &url) const
1920{
1921 const QQmlTypeLoaderSharedDataConstPtr data(&m_data);
1922 return data->typeCache.contains(url);
1923}
1924
1925bool QQmlTypeLoader::isScriptLoaded(const QUrl &url) const
1926{
1927 const QQmlTypeLoaderSharedDataConstPtr data(&m_data);
1928 return data->scriptCache.contains(url);
1929}
1930
1931/*!
1932\internal
1933
1934Locates the qmldir files for \a import. For each one, calls
1935handleLocalQmldirForImport() on \a blob. If that returns \c true, returns
1936\c QmldirFound.
1937
1938If at least one callback invocation returned \c false and there are no qmldir
1939files left to check, returns \c QmldirRejected.
1940
1941Otherwise, if interception redirects a previously local qmldir URL to a remote
1942one, returns \c QmldirInterceptedToRemote. Otherwise, returns \c QmldirNotFound.
1943*/
1944QQmlTypeLoader::LocalQmldirResult QQmlTypeLoader::locateLocalQmldir(
1945 QQmlTypeLoader::Blob *blob, const QQmlTypeLoader::Blob::PendingImportPtr &import,
1946 QList<QQmlError> *errors)
1947{
1948 // Check cache first
1949
1950 LocalQmldirResult result = QmldirNotFound;
1951 QQmlTypeLoaderThreadData::QmldirInfo *cacheTail = nullptr;
1952
1953 QQmlTypeLoaderThreadDataPtr threadData(&m_data);
1954 QQmlTypeLoaderThreadData::QmldirInfo **cachePtr = threadData->qmldirInfo.value(import->uri);
1955 QQmlTypeLoaderThreadData::QmldirInfo *cacheHead = cachePtr ? *cachePtr : nullptr;
1956 if (cacheHead) {
1957 cacheTail = cacheHead;
1958 do {
1959 if (cacheTail->version == import->version) {
1960 if (cacheTail->qmldirFilePath.isEmpty()) {
1961 return cacheTail->qmldirPathUrl.isEmpty()
1962 ? QmldirNotFound
1963 : QmldirInterceptedToRemote;
1964 }
1965 if (blob->handleLocalQmldirForImport(
1966 import, cacheTail->qmldirFilePath, cacheTail->qmldirPathUrl, errors)) {
1967 return QmldirFound;
1968 }
1969 result = QmldirRejected;
1970 }
1971 } while (cacheTail->next && (cacheTail = cacheTail->next));
1972 }
1973
1974
1975 // Do not try to construct the cache if it already had any entries for the URI.
1976 // Otherwise we might duplicate cache entries.
1977 if (result != QmldirNotFound
1978 || QQmlMetaType::isStronglyLockedModule(import->uri, import->version)) {
1979 return result;
1980 }
1981
1982 QQmlTypeLoaderConfiguredDataConstPtr configuredData(&m_data);
1983 const bool hasInterceptors = !configuredData->urlInterceptors.isEmpty();
1984
1985 // Interceptor might redirect remote files to local ones.
1986 QStringList localImportPaths = importPathList(hasInterceptors ? LocalOrRemote : Local);
1987
1988 // Search local import paths for a matching version
1989 const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(
1990 import->uri, localImportPaths, import->version);
1991
1992 QString qmldirAbsoluteFilePath;
1993 for (QString qmldirPath : qmlDirPaths) {
1994 if (hasInterceptors) {
1995 // TODO:
1996 // 1. This is inexact. It triggers only on the existence of interceptors, not on
1997 // actual interception. If the URL was remote to begin with but no interceptor
1998 // actually changes it, we still clear the qmldirPath and consider it
1999 // QmldirInterceptedToRemote.
2000 // 2. This misdiagnosis makes addLibraryImport do the right thing and postpone
2001 // the loading of pre-registered types for any QML engine that has interceptors
2002 // (even if they don't do anything in this case).
2003 // Fixing this would open the door to follow-up problems but wouldn't result in any
2004 // significant benefit.
2005 const QUrl intercepted = doInterceptUrl(
2006 QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
2007 QQmlAbstractUrlInterceptor::QmldirFile,
2008 configuredData->urlInterceptors);
2009 qmldirPath = QQmlFile::urlToLocalFileOrQrc(intercepted);
2010 if (result != QmldirInterceptedToRemote
2011 && qmldirPath.isEmpty()
2012 && !QQmlFile::isLocalFile(intercepted)) {
2013 result = QmldirInterceptedToRemote;
2014 }
2015 }
2016
2017 qmldirAbsoluteFilePath = absoluteFilePath(qmldirPath);
2018 if (!qmldirAbsoluteFilePath.isEmpty()) {
2019 QString url;
2020 const QString absolutePath = qmldirAbsoluteFilePath.left(
2021 qmldirAbsoluteFilePath.lastIndexOf(u'/') + 1);
2022 if (absolutePath.at(0) == u':') {
2023 url = QStringLiteral("qrc") + absolutePath;
2024 } else {
2025 url = QUrl::fromLocalFile(absolutePath).toString();
2026 sanitizeUNCPath(&qmldirAbsoluteFilePath);
2027 }
2028
2029 QQmlTypeLoaderThreadData::QmldirInfo *cache = new QQmlTypeLoaderThreadData::QmldirInfo;
2030 cache->version = import->version;
2031 cache->qmldirFilePath = qmldirAbsoluteFilePath;
2032 cache->qmldirPathUrl = url;
2033 cache->next = nullptr;
2034 if (cacheTail)
2035 cacheTail->next = cache;
2036 else
2037 threadData->qmldirInfo.insert(import->uri, cache);
2038 cacheTail = cache;
2039
2040 if (result != QmldirFound) {
2041 result = blob->handleLocalQmldirForImport(
2042 import, qmldirAbsoluteFilePath, url, errors)
2043 ? QmldirFound
2044 : QmldirRejected;
2045 }
2046
2047 // Do not return here. Rather, construct the complete cache for this URI.
2048 }
2049 }
2050
2051 // Nothing found? Add an empty cache entry to signal that for further requests.
2052 if (result == QmldirNotFound || result == QmldirInterceptedToRemote) {
2053 QQmlTypeLoaderThreadData::QmldirInfo *cache = new QQmlTypeLoaderThreadData::QmldirInfo;
2054 cache->version = import->version;
2055 cache->next = cacheHead;
2056 if (result == QmldirInterceptedToRemote) {
2057 // The actual value doesn't matter as long as it's not empty.
2058 // We only use it to discern QmldirInterceptedToRemote from QmldirNotFound above.
2059 cache->qmldirPathUrl = QStringLiteral("intercepted");
2060 }
2061 threadData->qmldirInfo.insert(import->uri, cache);
2062
2063 if (result == QmldirNotFound) {
2064 qCDebug(lcQmlImport)
2065 << "locateLocalQmldir:" << qPrintable(import->uri)
2066 << "module's qmldir file not found";
2067 }
2068 } else {
2069 qCDebug(lcQmlImport)
2070 << "locateLocalQmldir:" << qPrintable(import->uri) << "module's qmldir found at"
2071 << qmldirAbsoluteFilePath;
2072 }
2073
2074 return result;
2075}
2076
2077QT_END_NAMESPACE
FileSetPopulateResult
#define ASSERT_ENGINETHREAD()
#define ASSERT_LOADTHREAD()
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