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