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