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(
799 QQmlImports::tr("module \"%1\" version %2.%3 cannot be imported because:\n%4")
800 .arg(import->uri, QString::number(import->version.majorVersion()),
801 import->version.hasMinorVersion()
802 ? QString::number(import->version.minorVersion())
803 : QLatin1String("x"),
804 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#if defined(Q_OS_OHOS)
1234 // On HarmonyOS, Qt app and plugins all are Native C++ libraries (.so files).
1235 // They have to be kept in entry/libs/${OHOS_ARCH} in .hap package.
1236 data->pluginPaths << QCoreApplication::applicationDirPath();
1237#else
1238 data->pluginPaths << QLatin1String(".");
1239#endif
1240 // Search order is:
1241 // 1. android or macos specific bundle paths.
1242 // 2. applicationDirPath()
1243 // 3. qrc:/qt-project.org/imports
1244 // 4. qrc:/qt/qml
1245 // 5. $QML2_IMPORT_PATH
1246 // 6. $QML_IMPORT_PATH
1247 // 7. QLibraryInfo::QmlImportsPath
1248 //
1249 // ... unless we're a plugin application. Then we don't pick up any special paths
1250 // from environment variables or bundles and we also don't add the application dir path.
1251
1252 const auto paths = QLibraryInfo::paths(QLibraryInfo::QmlImportsPath);
1253 for (const auto &installImportsPath: paths)
1254 addImportPath(installImportsPath);
1255
1256 const bool isPluginApplication = QCoreApplication::testAttribute(Qt::AA_PluginApplication);
1257
1258 auto addEnvImportPath = [this, isPluginApplication](const char *var) {
1259 if (Q_UNLIKELY(!isPluginApplication && !qEnvironmentVariableIsEmpty(var))) {
1260 const QStringList paths = parseEnvPath(qEnvironmentVariable(var));
1261 for (int ii = paths.size() - 1; ii >= 0; --ii)
1262 addImportPath(paths.at(ii));
1263 }
1264 };
1265
1266 // env import paths
1267 addEnvImportPath("QML_IMPORT_PATH");
1268 addEnvImportPath("QML2_IMPORT_PATH");
1269
1270 addImportPath(QStringLiteral("qrc:/qt/qml"));
1271 addImportPath(QStringLiteral("qrc:/qt-project.org/imports"));
1272
1273 if (!isPluginApplication)
1274 addImportPath(QCoreApplication::applicationDirPath());
1275
1276 auto addEnvPluginPath = [this, isPluginApplication](const char *var) {
1277 if (Q_UNLIKELY(!isPluginApplication && !qEnvironmentVariableIsEmpty(var))) {
1278 const QStringList paths = parseEnvPath(qEnvironmentVariable(var));
1279 for (int ii = paths.size() - 1; ii >= 0; --ii)
1280 addPluginPath(paths.at(ii));
1281 }
1282 };
1283
1284 addEnvPluginPath("QML_PLUGIN_PATH");
1285#if defined(Q_OS_ANDROID)
1286 addImportPath(QStringLiteral("qrc:/android_rcc_bundle/qml"));
1287 addEnvPluginPath("QT_BUNDLED_LIBS_PATH");
1288#elif defined(Q_OS_MACOS)
1289 // Add the main bundle's Resources/qml directory as an import path, so that QML modules are
1290 // found successfully when running the app from its build dir.
1291 // This is where macdeployqt and our CMake deployment logic puts Qt and user qmldir files.
1292 if (!isPluginApplication) {
1293 if (CFBundleRef bundleRef = CFBundleGetMainBundle()) {
1294 if (QCFType<CFURLRef> urlRef = CFBundleCopyResourceURL(
1295 bundleRef, QCFString(QLatin1String("qml")), 0, 0)) {
1296 if (QCFType<CFURLRef> absoluteUrlRef = CFURLCopyAbsoluteURL(urlRef)) {
1297 if (QCFString path = CFURLCopyFileSystemPath(
1298 absoluteUrlRef, kCFURLPOSIXPathStyle)) {
1299 if (QFile::exists(path)) {
1300 addImportPath(QDir(path).canonicalPath());
1301 }
1302 }
1303 }
1304 }
1305 }
1306 }
1307#endif // Q_OS_DARWIN
1308}
1309
1310/*!
1311Destroys the type loader, first clearing the cache of any information about
1312loaded files.
1313*/
1314QQmlTypeLoader::~QQmlTypeLoader()
1315{
1317
1318 shutdownThread();
1319
1320 // Delete the thread before clearing the cache. Otherwise it will be started up again.
1321 invalidate();
1322
1323 clearCache();
1324
1325 clearQmldirInfo();
1326}
1327
1328template<typename Blob>
1330 const QQmlTypeLoaderSharedDataPtr &data, QQmlRefPointer<Blob> &&blob,
1331 QQmlTypeLoader::Mode mode)
1332{
1333 if ((mode == QQmlTypeLoader::PreferSynchronous && QQmlFile::isSynchronous(blob->finalUrl()))
1334 || mode == QQmlTypeLoader::Synchronous) {
1335 // this was started Asynchronous, but we need to force Synchronous
1336 // completion now.
1337
1338 // This only works when called directly from e.g. the UI thread, but not
1339 // when recursively called on the QML thread via resolveTypes()
1340
1341 // NB: We do not want to know whether the thread is the main thread, but specifically
1342 // that the thread is _not_ the thread we're waiting for.
1343 // If !QT_CONFIG(qml_type_loader_thread) the QML thread is the main thread.
1344
1345 QQmlTypeLoaderThread *thread = data.thread();
1346 if (thread && !thread->isThisThread()) {
1347 while (!blob->isCompleteOrError())
1348 thread->waitForNextMessage(); // Requires lock to be held, via data above
1349 }
1350 }
1351 return blob;
1352}
1353
1354/*!
1355Returns a QQmlTypeData for the specified \a url. The QQmlTypeData may be cached.
1356*/
1357QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QUrl &unNormalizedUrl, Mode mode)
1358{
1359 // This can be called from either thread.
1360
1361 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1362 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1363 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1364
1365 QQmlRefPointer<QQmlTypeData> typeData;
1366 {
1367 const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl);
1368 QQmlTypeLoaderSharedDataPtr data(&m_data);
1369
1370 typeData = data->typeCache.value(url);
1371 if (typeData)
1372 return handleExisting(data, std::move(typeData), mode);
1373
1374 // Trim before adding the new type, so that we don't immediately trim it away
1375 if (data->typeCache.size() >= data->typeCacheTrimThreshold)
1376 trimCache(data);
1377
1378 typeData = QQml::makeRefPointer<QQmlTypeData>(url, this);
1379
1380 // TODO: if (compiledData == 0), is it safe to omit this insertion?
1381 data->typeCache.insert(url, typeData);
1382 }
1383
1384 return finalizeBlob(std::move(typeData), mode);
1385}
1386
1387/*!
1388Returns a QQmlTypeData for the given \a data with the provided base \a url. The
1389QQmlTypeData will not be cached.
1390*/
1391QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(
1392 const QByteArray &data, const QUrl &url, Mode mode)
1393{
1394 // Can be called from either thread.
1395
1396 QQmlRefPointer<QQmlTypeData> typeData = QQml::makeRefPointer<QQmlTypeData>(url, this);
1397 QQmlTypeLoader::loadWithStaticData(QQmlDataBlob::Ptr(typeData.data()), data, mode);
1398 return typeData;
1399}
1400
1401static bool isModuleUrl(const QUrl &url)
1402{
1403 return url.fragment() == QLatin1String("module") || url.path().endsWith(QLatin1String(".mjs"));
1404}
1405
1406QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(const QUrl &unNormalizedUrl, Mode mode)
1407{
1408 // This can be called from either thread.
1409
1410 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1411 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1412 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1413
1414 QQmlRefPointer<QQmlScriptBlob> scriptBlob;
1415 {
1416 const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl);
1417 QQmlTypeLoaderSharedDataPtr data(&m_data);
1418
1419 scriptBlob = data->scriptCache.value(url);
1420 if (scriptBlob)
1421 return handleExisting(data, std::move(scriptBlob), mode);
1422
1423 scriptBlob = QQml::makeRefPointer<QQmlScriptBlob>(url, this, isModuleUrl(url)
1424 ? QQmlScriptBlob::IsESModule::Yes
1425 : QQmlScriptBlob::IsESModule::No);
1426 data->scriptCache.insert(url, scriptBlob);
1427 }
1428
1429 return finalizeBlob(std::move(scriptBlob), mode);
1430}
1431
1432QQmlRefPointer<QV4::CompiledData::CompilationUnit> QQmlTypeLoader::injectModule(
1433 const QUrl &relativeUrl, const QV4::CompiledData::Unit *unit)
1434{
1436
1437 QQmlRefPointer<QQmlScriptBlob> blob = QQml::makeRefPointer<QQmlScriptBlob>(
1438 relativeUrl, this, QQmlScriptBlob::IsESModule::Yes);
1439 QQmlPrivate::CachedQmlUnit cached { unit, nullptr, nullptr};
1440
1441 {
1442 QQmlTypeLoaderSharedDataPtr data(&m_data);
1443 data->scriptCache.insert(relativeUrl, blob);
1444 }
1445
1446 loadWithCachedUnit(blob.data(), &cached, Synchronous);
1447 Q_ASSERT(blob->isComplete());
1448 return blob->scriptData()->compilationUnit();
1449}
1450
1451/*!
1452Return a QQmlScriptBlob for \a unNormalizedUrl or \a relativeUrl.
1453This assumes PreferSynchronous, and therefore the result may not be ready yet.
1454*/
1455QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(
1456 const QUrl &unNormalizedUrl, const QUrl &relativeUrl)
1457{
1458 // Can be called from either thread
1459
1460 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1461 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1462 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1463
1464 const QUrl url = QQmlMetaType::normalizedUrl(unNormalizedUrl);
1465
1466 QQmlRefPointer<QQmlScriptBlob> scriptBlob;
1467 {
1468 QQmlTypeLoaderSharedDataPtr data(&m_data);
1469 scriptBlob = data->scriptCache.value(url);
1470
1471 // Also try the relative URL since manually registering native modules doesn't require
1472 // passing an absolute URL and we don't have a reference URL for native modules.
1473 if (!scriptBlob && unNormalizedUrl != relativeUrl)
1474 scriptBlob = data->scriptCache.value(relativeUrl);
1475
1476 // Do not try to finish the loading via handleExisting() here.
1477 if (scriptBlob)
1478 return scriptBlob;
1479
1480 scriptBlob = QQml::makeRefPointer<QQmlScriptBlob>(url, this, isModuleUrl(url)
1481 ? QQmlScriptBlob::IsESModule::Yes
1482 : QQmlScriptBlob::IsESModule::No);
1483 data->scriptCache.insert(url, scriptBlob);
1484 }
1485
1486 return finalizeBlob(std::move(scriptBlob), PreferSynchronous);
1487}
1488
1489/*!
1490Returns a QQmlQmldirData for \a url. The QQmlQmldirData may be cached.
1491*/
1492QQmlRefPointer<QQmlQmldirData> QQmlTypeLoader::getQmldir(const QUrl &url)
1493{
1494 // Can be called from either thread.
1495
1496 Q_ASSERT(!url.isRelative() &&
1497 (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() ||
1498 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url))));
1499
1500 QQmlRefPointer<QQmlQmldirData> qmldirData;
1501 {
1502 QQmlTypeLoaderSharedDataPtr data(&m_data);
1503 qmldirData = data->qmldirCache.value(url);
1504 if (qmldirData)
1505 return qmldirData;
1506
1507 qmldirData = QQml::makeRefPointer<QQmlQmldirData>(url, this);
1508 data->qmldirCache.insert(url, qmldirData);
1509 }
1510
1511 QQmlTypeLoader::load(QQmlDataBlob::Ptr(qmldirData.data()));
1512 return qmldirData;
1513}
1514
1515/*!
1516Returns the absolute filename of path via a directory cache.
1517Returns a empty string if the path does not exist.
1518
1519Why a directory cache? QML checks for files in many paths with
1520invalid directories. By caching whether a directory exists
1521we avoid many stats. We also cache the files' existence in the
1522directory, for the same reason.
1523*/
1524QString QQmlTypeLoader::absoluteFilePath(const QString &path) const
1525{
1526 // Can be called from either thread.
1527
1528 if (path.isEmpty())
1529 return QString();
1530 if (path.at(0) == QLatin1Char(':')) {
1531 // qrc resource
1532 QFileInfo fileInfo(path);
1533 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1534 } else if (path.size() > 3 && path.at(3) == QLatin1Char(':') &&
1535 path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) {
1536 // qrc resource url
1537 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1538 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1539 }
1540#if defined(Q_OS_ANDROID)
1541 else if (path.size() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') &&
1542 path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) {
1543 // assets resource url
1544 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1545 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1546 } else if (path.size() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') &&
1547 path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) {
1548 // content url
1549 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1550 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1551 }
1552#endif
1553
1554 return fileExists(path)
1555 ? QFileInfo(path).absoluteFilePath()
1556 : QString();
1557}
1558
1560{
1561 return QDirListing::IteratorFlag::CaseSensitive | QDirListing::IteratorFlag::IncludeHidden;
1562}
1563
1566 QCache<QString, bool> *fileSet, const QString &path, const QString &file)
1567{
1568 const QDirListing listing(path, dirListingFlags());
1569 bool seen = false;
1570 for (const auto &entry : listing) {
1571 const QString next = entry.fileName();
1572 if (next == file)
1573 seen = true;
1574 fileSet->insert(next, new bool(true));
1575 if (fileSet->totalCost() == fileSet->maxCost())
1576 break;
1577 }
1578
1579 if (seen)
1581 if (fileSet->totalCost() < fileSet->maxCost())
1584}
1585
1586static QString stripTrailingSlashes(const QString &path)
1587{
1588 for (qsizetype length = path.size(); length > 0; --length) {
1589 if (path[length - 1] != QLatin1Char('/'))
1590 return path.left(length);
1591 }
1592
1593 return QString();
1594}
1595
1596bool QQmlTypeLoader::fileExists(const QString &dirPath, const QString &file) const
1597{
1598 // Can be called from either thread.
1599
1600 // We want to use QDirListing here because that gives us case-sensitive results even on
1601 // case-insensitive file systems. That is, for a file date.qml it only lists date.qml, not
1602 // Date.qml, dAte.qml, DATE.qml etc. QFileInfo::exists(), on the other hand, will happily
1603 // claim that Date.qml exists in such a situation on a case-insensitive file sysem. Such a
1604 // thing then shadows the JavaScript Date object and disaster ensues.
1605
1606 const QString path = stripTrailingSlashes(dirPath);
1607
1608 const QChar nullChar(QChar::Null);
1609 if (path.isEmpty() || path.contains(nullChar) || file.isEmpty() || file.contains(nullChar))
1610 return false;
1611
1612
1613 QQmlTypeLoaderSharedDataConstPtr data(&m_data);
1614 QCache<QString, bool> *fileSet = data->importDirCache.object(path);
1615 if (fileSet) {
1616 if (const bool *exists = fileSet->object(file))
1617 return *exists;
1618
1619 // If the cache isn't full, we know that we've scanned the whole directory.
1620 // The file not being in the cache then means it doesn't exist.
1621 if (fileSet->totalCost() < fileSet->maxCost())
1622 return false;
1623
1624 } else if (data->importDirCache.contains(path)) {
1625 // explicit nullptr in cache
1626 return false;
1627 }
1628
1629 auto addToCache = [&](const QString &path, const QString &file) {
1630 const QDir dir(path);
1631
1632 if (!fileSet) {
1633 // First try to cache the whole directory, but only up to the maxCost of the cache.
1634
1635 fileSet = dir.exists() ? new QCache<QString, bool> : nullptr;
1636 const bool inserted = data->importDirCache.insert(path, fileSet);
1637 Q_ASSERT(inserted);
1638 if (!fileSet)
1639 return false;
1640
1641 switch (populateFileSet(fileSet, dir.path(), file)) {
1642 case FileSetPopulateResult::NotFound:
1643 return false;
1644 case FileSetPopulateResult::Found:
1645 return true;
1646 case FileSetPopulateResult::Overflow:
1647 break;
1648 }
1649
1650 // Cache overflow. Look up files individually
1651 } else {
1652 // If the directory was completely cached, we'd have returned early above.
1653 Q_ASSERT(fileSet->totalCost() == fileSet->maxCost());
1654
1655 }
1656
1657 const QDirListing singleFile(dir.path(), {file}, dirListingFlags());
1658 const bool exists = singleFile.begin() != singleFile.end();
1659 fileSet->insert(file, new bool(exists));
1660 Q_ASSERT(fileSet->totalCost() == fileSet->maxCost());
1661 return exists;
1662 };
1663
1664 if (path.at(0) == QLatin1Char(':')) {
1665 // qrc resource
1666 return addToCache(path, file);
1667 }
1668
1669 if (path.size() > 3 && path.at(3) == QLatin1Char(':')
1670 && path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) {
1671 // qrc resource url
1672 return addToCache(QQmlFile::urlToLocalFileOrQrc(path), file);
1673 }
1674
1675#if defined(Q_OS_ANDROID)
1676 if (path.size() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/')
1677 && path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) {
1678 // assets resource url
1679 return addToCache(QQmlFile::urlToLocalFileOrQrc(path), file);
1680 }
1681
1682 if (path.size() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/')
1683 && path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) {
1684 // content url
1685 return addToCache(QQmlFile::urlToLocalFileOrQrc(path), file);
1686 }
1687#endif
1688
1689 return addToCache(path, file);
1690}
1691
1692
1693/*!
1694Returns true if the path is a directory via a directory cache. Cache is
1695shared with absoluteFilePath().
1696*/
1697bool QQmlTypeLoader::directoryExists(const QString &path)
1698{
1699 // Can be called from either thread.
1700
1701 if (path.isEmpty())
1702 return false;
1703
1704 bool isResource = path.at(0) == QLatin1Char(':');
1705#if defined(Q_OS_ANDROID)
1706 isResource = isResource || path.startsWith(QLatin1String("assets:/")) || path.startsWith(QLatin1String("content:/"));
1707#endif
1708
1709 if (isResource) {
1710 // qrc resource
1711 QFileInfo fileInfo(path);
1712 return fileInfo.exists() && fileInfo.isDir();
1713 }
1714
1715 const QString dirPath = stripTrailingSlashes(path);
1716
1717 QQmlTypeLoaderSharedDataPtr data(&m_data);
1718 if (!data->importDirCache.contains(dirPath)) {
1719 if (QDir(dirPath).exists()) {
1720 QCache<QString, bool> *files = new QCache<QString, bool>;
1721 populateFileSet(files, dirPath, QString());
1722 data->importDirCache.insert(dirPath, files);
1723 return true;
1724 }
1725
1726 data->importDirCache.insert(dirPath, nullptr);
1727 return false;
1728 }
1729
1730 return data->importDirCache.object(dirPath) != nullptr;
1731}
1732
1733
1734/*!
1735Return a QQmlTypeLoaderQmldirContent for absoluteFilePath. The QQmlTypeLoaderQmldirContent may be cached.
1736
1737\a filePath is a local file path.
1738
1739It can also be a remote path for a remote directory import, but it will have been cached by now in this case.
1740*/
1741const QQmlTypeLoaderQmldirContent QQmlTypeLoader::qmldirContent(const QString &filePathIn)
1742{
1744
1745 QString filePath;
1746
1747 // Try to guess if filePathIn is already a URL. This is necessarily fragile, because
1748 // - paths can contain ':', which might make them appear as URLs with schemes.
1749 // - windows drive letters appear as schemes (thus "< 2" below).
1750 // - a "file:" URL is equivalent to the respective file, but will be treated differently.
1751 // Yet, this heuristic is the best we can do until we pass more structured information here,
1752 // for example a QUrl also for local files.
1753 QUrl url(filePathIn);
1754
1755 QQmlTypeLoaderThreadDataPtr data(&m_data);
1756
1757 if (url.scheme().size() < 2) {
1758 filePath = filePathIn;
1759 } else {
1760 filePath = QQmlFile::urlToLocalFileOrQrc(url);
1761 if (filePath.isEmpty()) { // Can't load the remote here, but should be cached
1762 if (auto entry = data->importQmlDirCache.value(filePathIn))
1763 return **entry;
1764 else
1765 return QQmlTypeLoaderQmldirContent();
1766 }
1767 }
1768
1769 QQmlTypeLoaderQmldirContent **val = data->importQmlDirCache.value(filePath);
1770 if (val)
1771 return **val;
1772 QQmlTypeLoaderQmldirContent *qmldir = new QQmlTypeLoaderQmldirContent;
1773
1774#define ERROR(description) { QQmlError e; e.setDescription(description); qmldir->setError(e); }
1775#define NOT_READABLE_ERROR QString(QLatin1String("module \"$$URI$$\" definition \"%1\" not readable"))
1776#define NOT_FOUND_ERROR QString(QLatin1String("cannot load module \"$$URI$$\": File \"%1\" not found"))
1777
1778 QFile file(filePath);
1779 if (!fileExists(filePath)) {
1780 ERROR(NOT_FOUND_ERROR.arg(filePath));
1781 } else if (file.open(QFile::ReadOnly)) {
1782 QByteArray data = file.readAll();
1783 qmldir->setContent(filePath, QString::fromUtf8(data));
1784 } else {
1785 ERROR(NOT_READABLE_ERROR.arg(filePath));
1786 }
1787
1788#undef ERROR
1789#undef NOT_READABLE_ERROR
1790#undef NOT_FOUND_ERROR
1791
1792 data->importQmlDirCache.insert(filePath, qmldir);
1793 return *qmldir;
1794}
1795
1796void QQmlTypeLoader::setQmldirContent(const QString &url, const QString &content)
1797{
1799
1800 QQmlTypeLoaderThreadDataPtr data(&m_data);
1801 QQmlTypeLoaderQmldirContent *qmldir;
1802 QQmlTypeLoaderQmldirContent **val = data->importQmlDirCache.value(url);
1803 if (val) {
1804 qmldir = *val;
1805 } else {
1806 qmldir = new QQmlTypeLoaderQmldirContent;
1807 data->importQmlDirCache.insert(url, qmldir);
1808 }
1809
1810 if (!qmldir->hasContent())
1811 qmldir->setContent(url, content);
1812}
1813
1814template<typename Blob>
1815void clearBlobs(QHash<QUrl, QQmlRefPointer<Blob>> *blobs)
1816{
1817 std::for_each(blobs->cbegin(), blobs->cend(), [](const QQmlRefPointer<Blob> &blob) {
1818 blob->resetTypeLoader();
1819 });
1820 blobs->clear();
1821}
1822
1823/*!
1824Clears cached information about loaded files, including any type data, scripts
1825and qmldir information.
1826*/
1827void QQmlTypeLoader::clearCache()
1828{
1829 // This looks dangerous because we're dropping live blobs on the engine thread.
1830 // However, it's safe because we shut down the type loader thread before we do so.
1831
1833
1834 // Temporarily shut the thread down and discard all messages, making it safe to
1835 // hack into the various data structures below.
1836 shutdownThread();
1837
1838 QQmlTypeLoaderThreadDataPtr threadData(&m_data);
1839 qDeleteAll(threadData->importQmlDirCache);
1840 threadData->checksumCache.clear();
1841 threadData->importQmlDirCache.clear();
1842
1843 QQmlTypeLoaderSharedDataPtr data(&m_data);
1844 clearBlobs(&data->typeCache);
1845 clearBlobs(&data->scriptCache);
1846 clearBlobs(&data->qmldirCache);
1847 data->typeCacheTrimThreshold = QQmlTypeLoaderSharedData::MinimumTypeCacheTrimThreshold;
1848 data->importDirCache.clear();
1849
1850 // The thread will auto-restart next time we need it.
1851}
1852
1853/*!
1854 \internal
1855 Replace the compilation unit cached for \a url with \a unit.
1856
1857 This is used by the QML preview object patcher (Pass 3) to make the type
1858 loader return the updated compilation unit when other parts of the type
1859 system (e.g., QQmlTypePrivate::compositePropertyCache()) ask for the type
1860 data of a patched component.
1861*/
1862void QQmlTypeLoader::replaceCachedCompilationUnit(
1863 const QUrl &url,
1864 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &unit)
1865{
1866 const QUrl normalized = QQmlMetaType::normalizedUrl(url);
1867 const QQmlTypeLoaderSharedDataPtr data(&m_data);
1868 auto it = data->typeCache.find(normalized);
1869 if (it == data->typeCache.end())
1870 return;
1871 (*it)->m_compiledData = unit;
1872}
1873
1874void QQmlTypeLoader::trimCache()
1875{
1876 const QQmlTypeLoaderSharedDataPtr data(&m_data);
1877 trimCache(data);
1878}
1879
1880void QQmlTypeLoader::updateTypeCacheTrimThreshold(const QQmlTypeLoaderSharedDataPtr &data)
1881{
1882 // This can be called from either thread and is called from a method that locks.
1883
1884 int size = data->typeCache.size();
1885 if (size > data->typeCacheTrimThreshold)
1886 data->typeCacheTrimThreshold = size * 2;
1887 if (size < data->typeCacheTrimThreshold / 2) {
1888 data->typeCacheTrimThreshold
1889 = qMax(size * 2, int(QQmlTypeLoaderSharedData::MinimumTypeCacheTrimThreshold));
1890 }
1891}
1892
1893void QQmlTypeLoader::trimCache(const QQmlTypeLoaderSharedDataPtr &data)
1894{
1895 // This can be called from either thread. It has to be called while the type loader mutex
1896 // is locked. It drops potentially live blobs, but only ones which are isCompleteOrError and
1897 // are not depended on by other blobs.
1898
1899 while (true) {
1900 bool deletedOneType = false;
1901 for (auto iter = data->typeCache.begin(), end = data->typeCache.end(); iter != end;) {
1902 const QQmlRefPointer<QQmlTypeData> &typeData = iter.value();
1903
1904 // typeData->m_compiledData may be set early on in the proccess of loading a file, so
1905 // it's important to check the general loading status of the typeData before making any
1906 // other decisions.
1907 if (typeData->count() != 1 || !typeData->isCompleteOrError()) {
1908 ++iter;
1909 continue;
1910 }
1911
1912 // isCompleteOrError means the waitingFor list of this typeData is empty.
1913 // Therefore, it cannot interfere with other blobs on destruction anymore.
1914 // Therefore, we can drop it on either the engine thread or the type loader thread.
1915
1916 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit
1917 = typeData->m_compiledData;
1918 if (compilationUnit) {
1919 if (compilationUnit->count()
1920 > QQmlMetaType::countInternalCompositeTypeSelfReferences(
1921 compilationUnit) + 1) {
1922 ++iter;
1923 continue;
1924 }
1925
1926 QQmlMetaType::unregisterInternalCompositeType(compilationUnit);
1927 Q_ASSERT(compilationUnit->count() == 1);
1928 }
1929
1930 // There are no live objects of this type
1931 iter = data->typeCache.erase(iter);
1932 deletedOneType = true;
1933 }
1934
1935 if (!deletedOneType)
1936 break;
1937 }
1938
1939 // TODO: release any scripts which are no longer referenced by any types
1940
1941 updateTypeCacheTrimThreshold(data);
1942
1943 QQmlMetaType::freeUnusedTypesAndCaches();
1944}
1945
1946bool QQmlTypeLoader::isTypeLoaded(const QUrl &url) const
1947{
1948 const QQmlTypeLoaderSharedDataConstPtr data(&m_data);
1949 return data->typeCache.contains(url);
1950}
1951
1952bool QQmlTypeLoader::isScriptLoaded(const QUrl &url) const
1953{
1954 const QQmlTypeLoaderSharedDataConstPtr data(&m_data);
1955 return data->scriptCache.contains(url);
1956}
1957
1958static QString pathToUrl(const QString &path)
1959{
1960 const auto dir = path.left(path.lastIndexOf(u"/") + 1);
1961 if (dir.at(0) == u':')
1962 return QStringLiteral("qrc") + dir;
1963 else
1964 return QUrl::fromLocalFile(dir).toString();
1965};
1966
1967QStringList QQmlTypeLoader::urlsForModule(const QString &module) const
1968{
1969 const auto &importPaths = importPathList();
1970 const auto completedImportPaths = QQmlImports::completeQmldirPaths(module, importPaths, {});
1971 QStringList urls;
1972 for (const auto &importPath : completedImportPaths) {
1973 const auto absolutePath = absoluteFilePath(importPath);
1974 if (absolutePath.isEmpty())
1975 continue;
1976 if (const std::optional<QString> url = pathToUrl(absolutePath))
1977 urls.append(url.value());
1978 }
1979
1980 return urls;
1981};
1982
1983/*!
1984\internal
1985
1986Locates the qmldir files for \a import. For each one, calls
1987handleLocalQmldirForImport() on \a blob. If that returns \c true, returns
1988\c QmldirFound.
1989
1990If at least one callback invocation returned \c false and there are no qmldir
1991files left to check, returns \c QmldirRejected.
1992
1993Otherwise, if interception redirects a previously local qmldir URL to a remote
1994one, returns \c QmldirInterceptedToRemote. Otherwise, returns \c QmldirNotFound.
1995*/
1996QQmlTypeLoader::LocalQmldirResult QQmlTypeLoader::locateLocalQmldir(
1997 QQmlTypeLoader::Blob *blob, const QQmlTypeLoader::Blob::PendingImportPtr &import,
1998 QList<QQmlError> *errors)
1999{
2000 // Check cache first
2001
2002 LocalQmldirResult result = QmldirNotFound;
2003 QQmlTypeLoaderThreadData::QmldirInfo *cacheTail = nullptr;
2004
2005 QQmlTypeLoaderThreadDataPtr threadData(&m_data);
2006 QQmlTypeLoaderThreadData::QmldirInfo **cachePtr = threadData->qmldirInfo.value(import->uri);
2007 QQmlTypeLoaderThreadData::QmldirInfo *cacheHead = cachePtr ? *cachePtr : nullptr;
2008 if (cacheHead) {
2009 cacheTail = cacheHead;
2010 do {
2011 if (cacheTail->version == import->version) {
2012 if (cacheTail->qmldirFilePath.isEmpty()) {
2013 return cacheTail->qmldirPathUrl.isEmpty()
2014 ? QmldirNotFound
2015 : QmldirInterceptedToRemote;
2016 }
2017 if (blob->handleLocalQmldirForImport(
2018 import, cacheTail->qmldirFilePath, cacheTail->qmldirPathUrl, errors)) {
2019 return QmldirFound;
2020 }
2021 result = QmldirRejected;
2022 }
2023 } while (cacheTail->next && (cacheTail = cacheTail->next));
2024 }
2025
2026
2027 // Do not try to construct the cache if it already had any entries for the URI.
2028 // Otherwise we might duplicate cache entries.
2029 if (result != QmldirNotFound
2030 || QQmlMetaType::isStronglyLockedModule(import->uri, import->version)) {
2031 return result;
2032 }
2033
2034 QQmlTypeLoaderConfiguredDataConstPtr configuredData(&m_data);
2035 const bool hasInterceptors = !configuredData->urlInterceptors.isEmpty();
2036
2037 // Interceptor might redirect remote files to local ones.
2038 QStringList localImportPaths = importPathList(hasInterceptors ? LocalOrRemote : Local);
2039
2040 // Search local import paths for a matching version
2041 const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(
2042 import->uri, localImportPaths, import->version);
2043
2044 QString qmldirAbsoluteFilePath;
2045 for (QString qmldirPath : qmlDirPaths) {
2046 if (hasInterceptors) {
2047 // TODO:
2048 // 1. This is inexact. It triggers only on the existence of interceptors, not on
2049 // actual interception. If the URL was remote to begin with but no interceptor
2050 // actually changes it, we still clear the qmldirPath and consider it
2051 // QmldirInterceptedToRemote.
2052 // 2. This misdiagnosis makes addLibraryImport do the right thing and postpone
2053 // the loading of pre-registered types for any QML engine that has interceptors
2054 // (even if they don't do anything in this case).
2055 // Fixing this would open the door to follow-up problems but wouldn't result in any
2056 // significant benefit.
2057 const QUrl intercepted = doInterceptUrl(
2058 QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
2059 QQmlAbstractUrlInterceptor::QmldirFile,
2060 configuredData->urlInterceptors);
2061 qmldirPath = QQmlFile::urlToLocalFileOrQrc(intercepted);
2062 if (result != QmldirInterceptedToRemote
2063 && qmldirPath.isEmpty()
2064 && !QQmlFile::isLocalFile(intercepted)) {
2065 result = QmldirInterceptedToRemote;
2066 }
2067 }
2068
2069 qmldirAbsoluteFilePath = absoluteFilePath(qmldirPath);
2070 if (!qmldirAbsoluteFilePath.isEmpty()) {
2071 QString url = pathToUrl(qmldirAbsoluteFilePath);
2072 if (url.startsWith(QStringLiteral("file:")))
2073 sanitizeUNCPath(&qmldirAbsoluteFilePath);
2074
2075 QQmlTypeLoaderThreadData::QmldirInfo *cache = new QQmlTypeLoaderThreadData::QmldirInfo;
2076 cache->version = import->version;
2077 cache->qmldirFilePath = qmldirAbsoluteFilePath;
2078 cache->qmldirPathUrl = url;
2079 cache->next = nullptr;
2080 if (cacheTail)
2081 cacheTail->next = cache;
2082 else
2083 threadData->qmldirInfo.insert(import->uri, cache);
2084 cacheTail = cache;
2085
2086 if (result != QmldirFound) {
2087 result = blob->handleLocalQmldirForImport(
2088 import, qmldirAbsoluteFilePath, url, errors)
2089 ? QmldirFound
2090 : QmldirRejected;
2091 }
2092
2093 // Do not return here. Rather, construct the complete cache for this URI.
2094 }
2095 }
2096
2097 // Nothing found? Add an empty cache entry to signal that for further requests.
2098 if (result == QmldirNotFound || result == QmldirInterceptedToRemote) {
2099 QQmlTypeLoaderThreadData::QmldirInfo *cache = new QQmlTypeLoaderThreadData::QmldirInfo;
2100 cache->version = import->version;
2101 cache->next = cacheHead;
2102 if (result == QmldirInterceptedToRemote) {
2103 // The actual value doesn't matter as long as it's not empty.
2104 // We only use it to discern QmldirInterceptedToRemote from QmldirNotFound above.
2105 cache->qmldirPathUrl = QStringLiteral("intercepted");
2106 }
2107 threadData->qmldirInfo.insert(import->uri, cache);
2108
2109 if (result == QmldirNotFound) {
2110 qCDebug(lcQmlImport)
2111 << "locateLocalQmldir:" << qPrintable(import->uri)
2112 << "module's qmldir file not found";
2113 }
2114 } else {
2115 qCDebug(lcQmlImport)
2116 << "locateLocalQmldir:" << qPrintable(import->uri) << "module's qmldir found at"
2117 << qmldirAbsoluteFilePath;
2118 }
2119
2120 return result;
2121}
2122
2123QT_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