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/qqmltypedata_p.h>
10#include <private/qqmltypeloaderqmldircontent_p.h>
11#include <private/qqmltypeloaderthread_p.h>
12#include <private/qqmlsourcecoordinate_p.h>
13
14#include <QtQml/qqmlabstracturlinterceptor.h>
15#include <QtQml/qqmlengine.h>
16#include <QtQml/qqmlextensioninterface.h>
17#include <QtQml/qqmlfile.h>
18
19#include <qtqml_tracepoints_p.h>
20
21#include <QtCore/qdir.h>
22#include <QtCore/qdiriterator.h>
23#include <QtCore/qfile.h>
24#include <QtCore/qthread.h>
25
26#include <functional>
27
28// #define DATABLOB_DEBUG
29#ifdef DATABLOB_DEBUG
30#define ASSERT_LOADTHREAD() do { if (!m_thread->isThisThread()) qFatal("QQmlTypeLoader: Caller not in load thread"); } while (false)
31#else
32#define ASSERT_LOADTHREAD()
33#endif
34
35
36QT_BEGIN_NAMESPACE
37
38namespace {
39
40 template<typename LockType>
41 struct LockHolder
42 {
43 LockType& lock;
44 LockHolder(LockType *l) : lock(*l) { lock.lock(); }
45 ~LockHolder() { lock.unlock(); }
46 };
47}
48
49Q_TRACE_POINT(qtqml, QQmlCompiling_entry, const QUrl &url)
50Q_TRACE_POINT(qtqml, QQmlCompiling_exit)
51
52/*!
53\class QQmlTypeLoader
54\brief The QQmlTypeLoader class abstracts loading files and their dependencies over the network.
55\internal
56
57The QQmlTypeLoader class is provided for the exclusive use of the QQmlTypeLoader class.
58
59Clients create QQmlDataBlob instances and submit them to the QQmlTypeLoader class
60through the QQmlTypeLoader::load() or QQmlTypeLoader::loadWithStaticData() methods.
61The loader then fetches the data over the network or from the local file system in an efficient way.
62QQmlDataBlob is an abstract class, so should always be specialized.
63
64Once data is received, the QQmlDataBlob::dataReceived() method is invoked on the blob. The
65derived class should use this callback to process the received data. Processing of the data can
66result in an error being set (QQmlDataBlob::setError()), or one or more dependencies being
67created (QQmlDataBlob::addDependency()). Dependencies are other QQmlDataBlob's that
68are required before processing can fully complete.
69
70To complete processing, the QQmlDataBlob::done() callback is invoked. done() is called when
71one of these three preconditions are met.
72
73\list 1
74\li The QQmlDataBlob has no dependencies.
75\li The QQmlDataBlob has an error set.
76\li All the QQmlDataBlob's dependencies are themselves "done()".
77\endlist
78
79Thus QQmlDataBlob::done() will always eventually be called, even if the blob has an error set.
80*/
81
82void QQmlTypeLoader::invalidate()
83{
84 if (m_thread) {
85 shutdownThread();
86 delete m_thread;
87 m_thread = nullptr;
88 }
89
90#if QT_CONFIG(qml_network)
91 // Need to delete the network replies after
92 // the loader thread is shutdown as it could be
93 // getting new replies while we clear them
94 m_networkReplies.clear();
95#endif // qml_network
96}
97
98#if QT_CONFIG(qml_debug)
99void QQmlTypeLoader::setProfiler(QQmlProfiler *profiler)
100{
101 Q_ASSERT(!m_profiler);
102 m_profiler.reset(other: profiler);
103}
104#endif
105
106struct PlainLoader {
107 void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
108 {
109 loader->loadThread(blob);
110 }
111 void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
112 {
113 loader->m_thread->load(b: blob);
114 }
115 void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
116 {
117 loader->m_thread->loadAsync(b: blob);
118 }
119};
120
121struct StaticLoader {
122 const QByteArray &data;
123 StaticLoader(const QByteArray &data) : data(data) {}
124
125 void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
126 {
127 loader->loadWithStaticDataThread(blob, data);
128 }
129 void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
130 {
131 loader->m_thread->loadWithStaticData(b: blob, data);
132 }
133 void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
134 {
135 loader->m_thread->loadWithStaticDataAsync(b: blob, data);
136 }
137};
138
139struct CachedLoader {
140 const QQmlPrivate::CachedQmlUnit *unit;
141 CachedLoader(const QQmlPrivate::CachedQmlUnit *unit) : unit(unit) {}
142
143 void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
144 {
145 loader->loadWithCachedUnitThread(blob, unit);
146 }
147 void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
148 {
149 loader->m_thread->loadWithCachedUnit(b: blob, unit);
150 }
151 void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
152 {
153 loader->m_thread->loadWithCachedUnitAsync(b: blob, unit);
154 }
155};
156
157template<typename Loader>
158void QQmlTypeLoader::doLoad(const Loader &loader, QQmlDataBlob *blob, Mode mode)
159{
160#ifdef DATABLOB_DEBUG
161 qWarning("QQmlTypeLoader::doLoad(%s): %s thread", qPrintable(blob->urlString()),
162 m_thread->isThisThread()?"Compile":"Engine");
163#endif
164 blob->startLoading();
165
166 if (m_thread->isThisThread()) {
167 unlock();
168 loader.loadThread(this, blob);
169 lock();
170 } else if (mode == Asynchronous) {
171 blob->m_data.setIsAsync(true);
172 unlock();
173 loader.loadAsync(this, blob);
174 lock();
175 } else {
176 unlock();
177 loader.load(this, blob);
178 lock();
179 if (mode == PreferSynchronous) {
180 if (!blob->isCompleteOrError())
181 blob->m_data.setIsAsync(true);
182 } else {
183 Q_ASSERT(mode == Synchronous);
184 while (!blob->isCompleteOrError()) {
185 m_thread->waitForNextMessage();
186 }
187 }
188 }
189}
190
191/*!
192Load the provided \a blob from the network or filesystem.
193
194The loader must be locked.
195*/
196void QQmlTypeLoader::load(QQmlDataBlob *blob, Mode mode)
197{
198 doLoad(loader: PlainLoader(), blob, mode);
199}
200
201/*!
202Load the provided \a blob with \a data. The blob's URL is not used by the data loader in this case.
203
204The loader must be locked.
205*/
206void QQmlTypeLoader::loadWithStaticData(QQmlDataBlob *blob, const QByteArray &data, Mode mode)
207{
208 doLoad(loader: StaticLoader(data), blob, mode);
209}
210
211void QQmlTypeLoader::loadWithCachedUnit(QQmlDataBlob *blob, const QQmlPrivate::CachedQmlUnit *unit, Mode mode)
212{
213 doLoad(loader: CachedLoader(unit), blob, mode);
214}
215
216void QQmlTypeLoader::loadWithStaticDataThread(const QQmlDataBlob::Ptr &blob, const QByteArray &data)
217{
218 ASSERT_LOADTHREAD();
219
220 setData(blob, data);
221}
222
223void QQmlTypeLoader::loadWithCachedUnitThread(const QQmlDataBlob::Ptr &blob, const QQmlPrivate::CachedQmlUnit *unit)
224{
225 ASSERT_LOADTHREAD();
226
227 setCachedUnit(blob, unit);
228}
229
230void QQmlTypeLoader::loadThread(const QQmlDataBlob::Ptr &blob)
231{
232 ASSERT_LOADTHREAD();
233
234 // Don't continue loading if we've been shutdown
235 if (m_thread->isShutdown()) {
236 QQmlError error;
237 error.setDescription(QLatin1String("Interrupted by shutdown"));
238 blob->setError(error);
239 return;
240 }
241
242 if (blob->m_url.isEmpty()) {
243 QQmlError error;
244 error.setDescription(QLatin1String("Invalid null URL"));
245 blob->setError(error);
246 return;
247 }
248
249 if (QQmlFile::isSynchronous(url: blob->m_url)) {
250 const QString fileName = QQmlFile::urlToLocalFileOrQrc(blob->m_url);
251 if (!QQml_isFileCaseCorrect(fileName)) {
252 blob->setError(QLatin1String("File name case mismatch"));
253 return;
254 }
255
256 blob->m_data.setProgress(1.f);
257 if (blob->m_data.isAsync())
258 m_thread->callDownloadProgressChanged(b: blob, p: 1.);
259
260 setData(blob, fileName);
261
262 } else {
263#if QT_CONFIG(qml_network)
264 QNetworkReply *reply = m_thread->networkAccessManager()->get(request: QNetworkRequest(blob->m_url));
265 QQmlTypeLoaderNetworkReplyProxy *nrp = m_thread->networkReplyProxy();
266 m_networkReplies.insert(key: reply, value: blob);
267
268 if (reply->isFinished()) {
269 nrp->manualFinished(reply);
270 } else {
271 QObject::connect(sender: reply, SIGNAL(downloadProgress(qint64,qint64)),
272 receiver: nrp, SLOT(downloadProgress(qint64,qint64)));
273 QObject::connect(sender: reply, SIGNAL(finished()),
274 receiver: nrp, SLOT(finished()));
275 }
276
277#ifdef DATABLOB_DEBUG
278 qWarning("QQmlDataBlob: requested %s", qPrintable(blob->urlString()));
279#endif // DATABLOB_DEBUG
280#endif // qml_network
281 }
282}
283
284#define DATALOADER_MAXIMUM_REDIRECT_RECURSION 16
285
286#ifndef TYPELOADER_MINIMUM_TRIM_THRESHOLD
287#define TYPELOADER_MINIMUM_TRIM_THRESHOLD 64
288#endif
289
290#if QT_CONFIG(qml_network)
291void QQmlTypeLoader::networkReplyFinished(QNetworkReply *reply)
292{
293 Q_ASSERT(m_thread->isThisThread());
294
295 reply->deleteLater();
296
297 QQmlRefPointer<QQmlDataBlob> blob = m_networkReplies.take(key: reply);
298
299 Q_ASSERT(blob);
300
301 blob->m_redirectCount++;
302
303 if (blob->m_redirectCount < DATALOADER_MAXIMUM_REDIRECT_RECURSION) {
304 QVariant redirect = reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute);
305 if (redirect.isValid()) {
306 QUrl url = reply->url().resolved(relative: redirect.toUrl());
307 blob->m_finalUrl = url;
308 blob->m_finalUrlString.clear();
309
310 QNetworkReply *reply = m_thread->networkAccessManager()->get(request: QNetworkRequest(url));
311 QObject *nrp = m_thread->networkReplyProxy();
312 QObject::connect(sender: reply, SIGNAL(finished()), receiver: nrp, SLOT(finished()));
313 m_networkReplies.insert(key: reply, value: std::move(blob));
314#ifdef DATABLOB_DEBUG
315 qWarning("QQmlDataBlob: redirected to %s", qPrintable(blob->finalUrlString()));
316#endif
317 return;
318 }
319 }
320
321 if (reply->error()) {
322 blob->networkError(reply->error());
323 } else {
324 QByteArray data = reply->readAll();
325 setData(blob, data);
326 }
327}
328
329void QQmlTypeLoader::networkReplyProgress(QNetworkReply *reply,
330 qint64 bytesReceived, qint64 bytesTotal)
331{
332 Q_ASSERT(m_thread->isThisThread());
333
334 const QQmlRefPointer<QQmlDataBlob> blob = m_networkReplies.value(key: reply);
335
336 Q_ASSERT(blob);
337
338 if (bytesTotal != 0) {
339 qreal progress = (qreal(bytesReceived) / qreal(bytesTotal));
340 blob->m_data.setProgress(progress);
341 if (blob->m_data.isAsync())
342 m_thread->callDownloadProgressChanged(b: blob, p: blob->m_data.progress());
343 }
344}
345#endif // qml_network
346
347/*!
348Return the QQmlEngine associated with this loader
349*/
350QQmlEngine *QQmlTypeLoader::engine() const
351{
352 return m_engine;
353}
354
355/*! \internal
356Call the initializeEngine() method on \a iface. Used by QQmlImportDatabase to ensure it
357gets called in the correct thread.
358*/
359template<class Interface>
360void doInitializeEngine(Interface *iface, QQmlTypeLoaderThread *thread, QQmlEngine *engine,
361 const char *uri)
362{
363 Q_ASSERT(thread->isThisThread() || engine->thread() == QThread::currentThread());
364
365 if (thread->isThisThread()) {
366 thread->initializeEngine(iface, uri);
367 } else {
368 Q_ASSERT(engine->thread() == QThread::currentThread());
369 iface->initializeEngine(engine, uri);
370 }
371}
372
373void QQmlTypeLoader::initializeEngine(QQmlEngineExtensionInterface *iface, const char *uri)
374{
375 doInitializeEngine(iface, thread: m_thread, engine: engine(), uri);
376}
377
378void QQmlTypeLoader::initializeEngine(QQmlExtensionInterface *iface, const char *uri)
379{
380 doInitializeEngine(iface, thread: m_thread, engine: engine(), uri);
381}
382
383void QQmlTypeLoader::setData(const QQmlDataBlob::Ptr &blob, const QByteArray &data)
384{
385 QQmlDataBlob::SourceCodeData d;
386 d.inlineSourceCode = QString::fromUtf8(ba: data);
387 d.hasInlineSourceCode = true;
388 setData(blob, d);
389}
390
391void QQmlTypeLoader::setData(const QQmlDataBlob::Ptr &blob, const QString &fileName)
392{
393 QQmlDataBlob::SourceCodeData d;
394 d.fileInfo = QFileInfo(fileName);
395 setData(blob, d);
396}
397
398void QQmlTypeLoader::setData(const QQmlDataBlob::Ptr &blob, const QQmlDataBlob::SourceCodeData &d)
399{
400 Q_TRACE_SCOPE(QQmlCompiling, blob->url());
401 QQmlCompilingProfiler prof(profiler(), blob.data());
402
403 blob->m_inCallback = true;
404
405 blob->dataReceived(d);
406
407 if (!blob->isError() && !blob->isWaiting())
408 blob->allDependenciesDone();
409
410 if (blob->status() != QQmlDataBlob::Error)
411 blob->m_data.setStatus(QQmlDataBlob::WaitingForDependencies);
412
413 blob->m_inCallback = false;
414
415 blob->tryDone();
416}
417
418void QQmlTypeLoader::setCachedUnit(const QQmlDataBlob::Ptr &blob, const QQmlPrivate::CachedQmlUnit *unit)
419{
420 Q_TRACE_SCOPE(QQmlCompiling, blob->url());
421 QQmlCompilingProfiler prof(profiler(), blob.data());
422
423 blob->m_inCallback = true;
424
425 blob->initializeFromCachedUnit(unit);
426
427 if (!blob->isError() && !blob->isWaiting())
428 blob->allDependenciesDone();
429
430 if (blob->status() != QQmlDataBlob::Error)
431 blob->m_data.setStatus(QQmlDataBlob::WaitingForDependencies);
432
433 blob->m_inCallback = false;
434
435 blob->tryDone();
436}
437
438void QQmlTypeLoader::shutdownThread()
439{
440 if (m_thread && !m_thread->isShutdown())
441 m_thread->shutdown();
442}
443
444QQmlTypeLoader::Blob::PendingImport::PendingImport(
445 QQmlTypeLoader::Blob *blob, const QV4::CompiledData::Import *import,
446 QQmlImports::ImportFlags flags)
447 : uri(blob->stringAt(import->uriIndex))
448 , qualifier(blob->stringAt(import->qualifierIndex))
449 , type(static_cast<QV4::CompiledData::Import::ImportType>(quint32(import->type)))
450 , location(import->location)
451 , flags(flags)
452 , version(import->version)
453{
454}
455
456QQmlTypeLoader::Blob::Blob(const QUrl &url, QQmlDataBlob::Type type, QQmlTypeLoader *loader)
457 : QQmlDataBlob(url, type, loader)
458 , m_importCache(new QQmlImports(loader), QQmlRefPointer<QQmlImports>::Adopt)
459{
460}
461
462QQmlTypeLoader::Blob::~Blob()
463{
464}
465
466bool QQmlTypeLoader::Blob::fetchQmldir(const QUrl &url, PendingImportPtr import, int priority, QList<QQmlError> *errors)
467{
468 QQmlRefPointer<QQmlQmldirData> data = typeLoader()->getQmldir(url);
469
470 data->setPriority(this, std::move(import), priority);
471
472 if (data->status() == Error) {
473 // This qmldir must not exist - which is not an error
474 return true;
475 } else if (data->status() == Complete) {
476 // This data is already available
477 return qmldirDataAvailable(data, errors);
478 }
479
480 // Wait for this data to become available
481 addDependency(data.data());
482 return true;
483}
484
485/*!
486 * \internal
487 * Import any qualified scripts of for \a import as listed in \a qmldir.
488 * Precondition is that \a import is actually qualified.
489 */
490void QQmlTypeLoader::Blob::importQmldirScripts(
491 const QQmlTypeLoader::Blob::PendingImportPtr &import,
492 const QQmlTypeLoaderQmldirContent &qmldir, const QUrl &qmldirUrl)
493{
494 const auto qmldirScripts = qmldir.scripts();
495 for (const QQmlDirParser::Script &script : qmldirScripts) {
496 const QUrl scriptUrl = qmldirUrl.resolved(relative: QUrl(script.fileName));
497 QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(unNormalizedUrl: scriptUrl);
498 addDependency(blob.data());
499 scriptImported(blob, import->location, script.nameSpace, import->qualifier);
500 }
501}
502
503template<typename URL>
504void postProcessQmldir(
505 QQmlTypeLoader::Blob *self,
506 const QQmlTypeLoader::Blob::PendingImportPtr &import, const QString &qmldirFilePath,
507 const URL &qmldirUrl)
508{
509 const QQmlTypeLoaderQmldirContent qmldir = self->typeLoader()->qmldirContent(filePath: qmldirFilePath);
510 if (!import->qualifier.isEmpty())
511 self->importQmldirScripts(import, qmldir, qmldirUrl: QUrl(qmldirUrl));
512
513 if (qmldir.plugins().isEmpty()) {
514 // If the qmldir does not register a plugin, we might still have declaratively
515 // registered types (if we are dealing with an application instead of a library)
516 // We should use module name given in the qmldir rather than the one given by the
517 // import since the import may be a directory import.
518 auto module = QQmlMetaType::typeModule(uri: qmldir.typeNamespace(), version: import->version);
519 if (!module)
520 QQmlMetaType::qmlRegisterModuleTypes(uri: qmldir.typeNamespace());
521 // else: If the module already exists, the types must have been already registered
522 }
523}
524
525bool QQmlTypeLoader::Blob::updateQmldir(const QQmlRefPointer<QQmlQmldirData> &data, const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
526{
527 QString qmldirIdentifier = data->urlString();
528 QString qmldirUrl = qmldirIdentifier.left(n: qmldirIdentifier.lastIndexOf(c: QLatin1Char('/')) + 1);
529
530 typeLoader()->setQmldirContent(filePath: qmldirIdentifier, content: data->content());
531
532 const QTypeRevision version = m_importCache->updateQmldirContent(
533 importDb: typeLoader()->importDatabase(), uri: import->uri, prefix: import->qualifier, qmldirIdentifier,
534 qmldirUrl, errors);
535 if (!version.isValid())
536 return false;
537
538 // Use more specific version for dependencies if possible
539 if (version.hasMajorVersion())
540 import->version = version;
541
542 if (!loadImportDependencies(currentImport: import, qmldirUri: qmldirIdentifier, flags: import->flags, errors))
543 return false;
544
545 import->priority = 0;
546
547 // Release this reference at destruction
548 m_qmldirs << data;
549
550 postProcessQmldir(self: this, import, qmldirFilePath: qmldirIdentifier, qmldirUrl);
551 return true;
552}
553
554bool QQmlTypeLoader::Blob::addScriptImport(const QQmlTypeLoader::Blob::PendingImportPtr &import)
555{
556 const QUrl url(import->uri);
557 const auto module = m_typeLoader->engine()->handle()->moduleForUrl(url: url);
558 QQmlRefPointer<QQmlScriptBlob> blob;
559 if (module.native) {
560 blob.adopt(other: new QQmlScriptBlob(url, m_typeLoader));
561 blob->initializeFromNative(value: *module.native);
562 blob->tryDone();
563 } else {
564 blob = typeLoader()->getScript(unNormalizedUrl: finalUrl().resolved(relative: url));
565 }
566 addDependency(blob.data());
567 scriptImported(blob, import->location, import->qualifier, QString());
568 return true;
569}
570
571bool QQmlTypeLoader::Blob::addFileImport(const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
572{
573 QQmlImportDatabase *importDatabase = typeLoader()->importDatabase();
574 QQmlImports::ImportFlags flags;
575
576 QUrl importUrl(import->uri);
577 QString path = importUrl.path();
578 path.append(s: QLatin1String(path.endsWith(c: QLatin1Char('/')) ? "qmldir" : "/qmldir"));
579 importUrl.setPath(path);
580 QUrl qmldirUrl = finalUrl().resolved(relative: importUrl);
581 if (!QQmlImports::isLocal(url: qmldirUrl)) {
582 // This is a remote file; the import is currently incomplete
583 flags = QQmlImports::ImportIncomplete;
584 }
585
586 const QTypeRevision version = m_importCache->addFileImport(
587 importDb: importDatabase, uri: import->uri, prefix: import->qualifier, version: import->version, flags,
588 precedence: import->precedence, localQmldir: nullptr, errors);
589 if (!version.isValid())
590 return false;
591
592 // Use more specific version for the qmldir if possible
593 if (version.hasMajorVersion())
594 import->version = version;
595
596 if (flags & QQmlImports::ImportIncomplete) {
597 if (!fetchQmldir(url: qmldirUrl, import, priority: 1, errors))
598 return false;
599 } else {
600 const QString qmldirFilePath = QQmlFile::urlToLocalFileOrQrc(qmldirUrl);
601 if (!loadImportDependencies(currentImport: import, qmldirUri: qmldirFilePath, flags: import->flags, errors))
602 return false;
603
604 postProcessQmldir(self: this, import, qmldirFilePath, qmldirUrl);
605 }
606
607 return true;
608}
609
610bool QQmlTypeLoader::Blob::addLibraryImport(const QQmlTypeLoader::Blob::PendingImportPtr &import, QList<QQmlError> *errors)
611{
612 QQmlImportDatabase *importDatabase = typeLoader()->importDatabase();
613
614 const QQmlImportDatabase::LocalQmldirSearchLocation searchMode =
615 QQmlMetaType::isStronglyLockedModule(uri: import->uri, version: import->version)
616 ? QQmlImportDatabase::QmldirCacheOnly
617 : QQmlImportDatabase::QmldirFileAndCache;
618
619 const QQmlImportDatabase::LocalQmldirResult qmldirResult
620 = importDatabase->locateLocalQmldir(
621 uri: import->uri, version: import->version, location: searchMode,
622 callback: [&](const QString &qmldirFilePath, const QString &qmldirUrl) {
623 // This is a local library import
624 const QTypeRevision actualVersion = m_importCache->addLibraryImport(
625 importDb: importDatabase, uri: import->uri, prefix: import->qualifier,
626 version: import->version, qmldirIdentifier: qmldirFilePath, qmldirUrl, flags: import->flags, precedence: import->precedence,
627 errors);
628 if (!actualVersion.isValid())
629 return false;
630
631 // Use more specific version for dependencies if possible
632 if (actualVersion.hasMajorVersion())
633 import->version = actualVersion;
634
635 if (!loadImportDependencies(currentImport: import, qmldirUri: qmldirFilePath, flags: import->flags, errors)) {
636 QQmlError error;
637 QString reason = errors->front().description();
638 if (reason.size() > 512)
639 reason = reason.first(n: 252) + QLatin1String("... ...") + reason.last(n: 252);
640 if (import->version.hasMajorVersion()) {
641 error.setDescription(QQmlImportDatabase::tr(
642 sourceText: "module \"%1\" version %2.%3 cannot be imported because:\n%4")
643 .arg(a: import->uri).arg(a: import->version.majorVersion())
644 .arg(a: import->version.hasMinorVersion()
645 ? QString::number(import->version.minorVersion())
646 : QLatin1String("x"))
647 .arg(a: reason));
648 } else {
649 error.setDescription(QQmlImportDatabase::tr(sourceText: "module \"%1\" cannot be imported because:\n%2")
650 .arg(args&: import->uri, args&: reason));
651 }
652 errors->prepend(t: error);
653 return false;
654 }
655
656 postProcessQmldir(self: this, import, qmldirFilePath, qmldirUrl);
657 return true;
658 });
659
660 switch (qmldirResult) {
661 case QQmlImportDatabase::QmldirFound:
662 return true;
663 case QQmlImportDatabase::QmldirNotFound:
664 case QQmlImportDatabase::QmldirInterceptedToRemote:
665 break;
666 case QQmlImportDatabase::QmldirRejected:
667 return false;
668 }
669
670 // If there is a qmldir we cannot see, yet, then we have to wait.
671 // The qmldir might contain import directives.
672 if (qmldirResult != QQmlImportDatabase::QmldirInterceptedToRemote && (
673 // Major version of module already registered:
674 // We believe that the registration is complete.
675 QQmlMetaType::typeModule(uri: import->uri, version: import->version)
676
677 // Otherwise, try to register further module types.
678 || QQmlMetaType::qmlRegisterModuleTypes(uri: import->uri)
679
680 // Otherwise, there is no way to register any further types.
681 // Try with any module of that name.
682 || QQmlMetaType::latestModuleVersion(uri: import->uri).isValid())) {
683
684 if (!m_importCache->addLibraryImport(
685 importDb: importDatabase, uri: import->uri, prefix: import->qualifier, version: import->version,
686 qmldirIdentifier: QString(), qmldirUrl: QString(), flags: import->flags, precedence: import->precedence, errors).isValid()) {
687 return false;
688 }
689 } else {
690 // We haven't yet resolved this import
691 m_unresolvedImports << import;
692
693 const QQmlEngine *engine = typeLoader()->engine();
694 const bool hasInterceptors
695 = !(QQmlEnginePrivate::get(e: engine)->urlInterceptors.isEmpty());
696
697 // Query any network import paths for this library.
698 // Interceptor might redirect local paths.
699 QStringList remotePathList = importDatabase->importPathList(
700 type: hasInterceptors ? QQmlImportDatabase::LocalOrRemote
701 : QQmlImportDatabase::Remote);
702 if (!remotePathList.isEmpty()) {
703 // Add this library and request the possible locations for it
704 const QTypeRevision version = m_importCache->addLibraryImport(
705 importDb: importDatabase, uri: import->uri, prefix: import->qualifier, version: import->version,
706 qmldirIdentifier: QString(), qmldirUrl: QString(), flags: import->flags | QQmlImports::ImportIncomplete,
707 precedence: import->precedence, errors);
708
709 if (!version.isValid())
710 return false;
711
712 // Use more specific version for finding the qmldir if possible
713 if (version.hasMajorVersion())
714 import->version = version;
715
716 // Probe for all possible locations
717 int priority = 0;
718 const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(
719 uri: import->uri, basePaths: remotePathList, version: import->version);
720 for (const QString &qmldirPath : qmlDirPaths) {
721 if (hasInterceptors) {
722 QUrl url = engine->interceptUrl(
723 url: QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
724 type: QQmlAbstractUrlInterceptor::QmldirFile);
725 if (!QQmlFile::isLocalFile(url)
726 && !fetchQmldir(url, import, priority: ++priority, errors)) {
727 return false;
728 }
729 } else if (!fetchQmldir(url: QUrl(qmldirPath), import, priority: ++priority, errors)) {
730 return false;
731 }
732
733 }
734 }
735 }
736
737 return true;
738}
739
740bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import,
741 QQmlImports::ImportFlags flags, QList<QQmlError> *errors)
742{
743 return addImport(import: std::make_shared<PendingImport>(args: this, args&: import, args&: flags), errors);
744}
745
746bool QQmlTypeLoader::Blob::addImport(
747 QQmlTypeLoader::Blob::PendingImportPtr import, QList<QQmlError> *errors)
748{
749 Q_ASSERT(errors);
750
751 switch (import->type)
752 {
753 case QV4::CompiledData::Import::ImportLibrary:
754 return addLibraryImport(import, errors);
755 case QV4::CompiledData::Import::ImportFile:
756 return addFileImport(import ,errors);
757 case QV4::CompiledData::Import::ImportScript:
758 return addScriptImport(import);
759 case QV4::CompiledData::Import::ImportInlineComponent:
760 Q_UNREACHABLE_RETURN(false); // addImport is never called with an inline component import
761 }
762
763 Q_UNREACHABLE_RETURN(false);
764}
765
766void QQmlTypeLoader::Blob::dependencyComplete(QQmlDataBlob *blob)
767{
768 if (blob->type() == QQmlDataBlob::QmldirFile) {
769 QQmlQmldirData *data = static_cast<QQmlQmldirData *>(blob);
770 QList<QQmlError> errors;
771 if (!qmldirDataAvailable(data, &errors)) {
772 Q_ASSERT(errors.size());
773 QQmlError error(errors.takeFirst());
774 error.setUrl(m_importCache->baseUrl());
775 const QV4::CompiledData::Location importLocation = data->importLocation(blob: this);
776 error.setLine(qmlConvertSourceCoordinate<quint32, int>(n: importLocation.line()));
777 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(n: importLocation.column()));
778 errors.prepend(t: error); // put it back on the list after filling out information.
779 setError(errors);
780 }
781 }
782}
783
784bool QQmlTypeLoader::Blob::loadDependentImports(
785 const QList<QQmlDirParser::Import> &imports, const QString &qualifier,
786 QTypeRevision version, quint16 precedence, QQmlImports::ImportFlags flags,
787 QList<QQmlError> *errors)
788{
789 for (const auto &import : imports) {
790 if (import.flags & QQmlDirParser::Import::Optional)
791 continue;
792 auto dependencyImport = std::make_shared<PendingImport>();
793 dependencyImport->uri = import.module;
794 dependencyImport->qualifier = qualifier;
795 dependencyImport->version = (import.flags & QQmlDirParser::Import::Auto)
796 ? version : import.version;
797 dependencyImport->flags = flags;
798 dependencyImport->precedence = precedence;
799
800 qCDebug(lcQmlImport)
801 << "loading dependent import" << dependencyImport->uri << "version"
802 << dependencyImport->version << "as" << dependencyImport->qualifier;
803
804 if (!addImport(import: dependencyImport, errors)) {
805 QQmlError error;
806 error.setDescription(
807 QString::fromLatin1(
808 ba: "Failed to load dependent import \"%1\" version %2.%3")
809 .arg(a: dependencyImport->uri)
810 .arg(a: dependencyImport->version.majorVersion())
811 .arg(a: dependencyImport->version.minorVersion()));
812 errors->append(t: error);
813 return false;
814 }
815 }
816
817 return true;
818}
819
820bool QQmlTypeLoader::Blob::loadImportDependencies(
821 const QQmlTypeLoader::Blob::PendingImportPtr &currentImport, const QString &qmldirUri,
822 QQmlImports::ImportFlags flags, QList<QQmlError> *errors)
823{
824 const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(filePath: qmldirUri);
825 const QList<QQmlDirParser::Import> implicitImports
826 = QQmlMetaType::moduleImports(uri: currentImport->uri, version: currentImport->version)
827 + qmldir.imports();
828
829 // Prevent overflow from one category of import into the other.
830 switch (currentImport->precedence) {
831 case QQmlImportInstance::Implicit - 1:
832 case QQmlImportInstance::Lowest: {
833 QQmlError error;
834 error.setDescription(
835 QString::fromLatin1(ba: "Too many dependent imports for %1 %2.%3")
836 .arg(a: currentImport->uri)
837 .arg(a: currentImport->version.majorVersion())
838 .arg(a: currentImport->version.minorVersion()));
839 errors->append(t: error);
840 return false;
841 }
842 default:
843 break;
844 }
845
846 if (!loadDependentImports(
847 imports: implicitImports, qualifier: currentImport->qualifier, version: currentImport->version,
848 precedence: currentImport->precedence + 1, flags, errors)) {
849 QQmlError error;
850 error.setDescription(
851 QString::fromLatin1(
852 ba: "Failed to load dependencies for module \"%1\" version %2.%3")
853 .arg(a: currentImport->uri)
854 .arg(a: currentImport->version.majorVersion())
855 .arg(a: currentImport->version.minorVersion()));
856 errors->append(t: error);
857 return false;
858 }
859
860 return true;
861}
862
863bool QQmlTypeLoader::Blob::isDebugging() const
864{
865 return typeLoader()->engine()->handle()->debugger() != nullptr;
866}
867
868bool QQmlTypeLoader::Blob::readCacheFile() const
869{
870 return typeLoader()->engine()->handle()->diskCacheOptions()
871 & QV4::ExecutionEngine::DiskCache::QmlcRead;
872}
873
874bool QQmlTypeLoader::Blob::writeCacheFile() const
875{
876 return typeLoader()->engine()->handle()->diskCacheOptions()
877 & QV4::ExecutionEngine::DiskCache::QmlcWrite;
878}
879
880QQmlMetaType::CacheMode QQmlTypeLoader::Blob::aotCacheMode() const
881{
882 const QV4::ExecutionEngine::DiskCacheOptions options
883 = typeLoader()->engine()->handle()->diskCacheOptions();
884 if (!(options & QV4::ExecutionEngine::DiskCache::Aot))
885 return QQmlMetaType::RejectAll;
886 if (options & QV4::ExecutionEngine::DiskCache::AotByteCode)
887 return QQmlMetaType::AcceptUntyped;
888 return QQmlMetaType::RequireFullyTyped;
889}
890
891bool QQmlTypeLoader::Blob::qmldirDataAvailable(const QQmlRefPointer<QQmlQmldirData> &data, QList<QQmlError> *errors)
892{
893 return data->processImports(blob: this, callback: [&](PendingImportPtr import) {
894 return updateQmldir(data, import, errors);
895 });
896}
897
898/*!
899Constructs a new type loader that uses the given \a engine.
900*/
901QQmlTypeLoader::QQmlTypeLoader(QQmlEngine *engine)
902 : m_engine(engine)
903 , m_thread(new QQmlTypeLoaderThread(this))
904 , m_mutex(m_thread->mutex())
905 , m_typeCacheTrimThreshold(TYPELOADER_MINIMUM_TRIM_THRESHOLD)
906{
907}
908
909/*!
910Destroys the type loader, first clearing the cache of any information about
911loaded files.
912*/
913QQmlTypeLoader::~QQmlTypeLoader()
914{
915 // Stop the loader thread before releasing resources
916 shutdownThread();
917
918 clearCache();
919
920 invalidate();
921}
922
923QQmlImportDatabase *QQmlTypeLoader::importDatabase() const
924{
925 return &QQmlEnginePrivate::get(e: engine())->importDatabase;
926}
927
928QUrl QQmlTypeLoader::normalize(const QUrl &unNormalizedUrl)
929{
930 QUrl normalized(unNormalizedUrl);
931 if (normalized.scheme() == QLatin1String("qrc"))
932 normalized.setHost(host: QString()); // map qrc:///a.qml to qrc:/a.qml
933 return normalized;
934}
935
936/*!
937Returns a QQmlTypeData for the specified \a url. The QQmlTypeData may be cached.
938*/
939QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QUrl &unNormalizedUrl, Mode mode)
940{
941 Q_ASSERT(!unNormalizedUrl.isRelative() &&
942 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
943 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
944
945 const QUrl url = normalize(unNormalizedUrl);
946
947 LockHolder<QQmlTypeLoader> holder(this);
948
949 QQmlTypeData *typeData = m_typeCache.value(key: url);
950
951 if (!typeData) {
952 // Trim before adding the new type, so that we don't immediately trim it away
953 if (m_typeCache.size() >= m_typeCacheTrimThreshold)
954 trimCache();
955
956 typeData = new QQmlTypeData(url, this);
957 // TODO: if (compiledData == 0), is it safe to omit this insertion?
958 m_typeCache.insert(key: url, value: typeData);
959 QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
960
961 const QQmlMetaType::CacheMode cacheMode = typeData->aotCacheMode();
962 if (const QQmlPrivate::CachedQmlUnit *cachedUnit = (cacheMode != QQmlMetaType::RejectAll)
963 ? QQmlMetaType::findCachedCompilationUnit(uri: typeData->url(), mode: cacheMode, status: &error)
964 : nullptr) {
965 QQmlTypeLoader::loadWithCachedUnit(blob: typeData, unit: cachedUnit, mode);
966 } else {
967 typeData->setCachedUnitStatus(error);
968 QQmlTypeLoader::load(blob: typeData, mode);
969 }
970 } else if ((mode == PreferSynchronous || mode == Synchronous) && QQmlFile::isSynchronous(url)) {
971 // this was started Asynchronous, but we need to force Synchronous
972 // completion now (if at all possible with this type of URL).
973
974 if (!m_thread->isThisThread()) {
975 // this only works when called directly from the UI thread, but not
976 // when recursively called on the QML thread via resolveTypes()
977
978 while (!typeData->isCompleteOrError()) {
979 m_thread->waitForNextMessage();
980 }
981 }
982 }
983
984 return typeData;
985}
986
987/*!
988Returns a QQmlTypeData for the given \a data with the provided base \a url. The
989QQmlTypeData will not be cached.
990*/
991QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QByteArray &data, const QUrl &url, Mode mode)
992{
993 LockHolder<QQmlTypeLoader> holder(this);
994
995 QQmlTypeData *typeData = new QQmlTypeData(url, this);
996 QQmlTypeLoader::loadWithStaticData(blob: typeData, data, mode);
997
998 return QQmlRefPointer<QQmlTypeData>(typeData, QQmlRefPointer<QQmlTypeData>::Adopt);
999}
1000
1001/*!
1002Return a QQmlScriptBlob for \a url. The QQmlScriptData may be cached.
1003*/
1004QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(const QUrl &unNormalizedUrl)
1005{
1006 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1007 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1008 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1009
1010 const QUrl url = normalize(unNormalizedUrl);
1011
1012 LockHolder<QQmlTypeLoader> holder(this);
1013
1014 QQmlScriptBlob *scriptBlob = m_scriptCache.value(key: url);
1015
1016 if (!scriptBlob) {
1017 scriptBlob = new QQmlScriptBlob(url, this);
1018 m_scriptCache.insert(key: url, value: scriptBlob);
1019
1020 QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
1021 const QQmlMetaType::CacheMode cacheMode = scriptBlob->aotCacheMode();
1022 if (const QQmlPrivate::CachedQmlUnit *cachedUnit = (cacheMode != QQmlMetaType::RejectAll)
1023 ? QQmlMetaType::findCachedCompilationUnit(uri: scriptBlob->url(), mode: cacheMode, status: &error)
1024 : nullptr) {
1025 QQmlTypeLoader::loadWithCachedUnit(blob: scriptBlob, unit: cachedUnit);
1026 } else {
1027 scriptBlob->setCachedUnitStatus(error);
1028 QQmlTypeLoader::load(blob: scriptBlob);
1029 }
1030 }
1031
1032 return scriptBlob;
1033}
1034
1035/*!
1036Returns a QQmlQmldirData for \a url. The QQmlQmldirData may be cached.
1037*/
1038QQmlRefPointer<QQmlQmldirData> QQmlTypeLoader::getQmldir(const QUrl &url)
1039{
1040 Q_ASSERT(!url.isRelative() &&
1041 (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() ||
1042 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url))));
1043 LockHolder<QQmlTypeLoader> holder(this);
1044
1045 QQmlQmldirData *qmldirData = m_qmldirCache.value(key: url);
1046
1047 if (!qmldirData) {
1048 qmldirData = new QQmlQmldirData(url, this);
1049 m_qmldirCache.insert(key: url, value: qmldirData);
1050 QQmlTypeLoader::load(blob: qmldirData);
1051 }
1052
1053 return qmldirData;
1054}
1055
1056/*!
1057Returns the absolute filename of path via a directory cache.
1058Returns a empty string if the path does not exist.
1059
1060Why a directory cache? QML checks for files in many paths with
1061invalid directories. By caching whether a directory exists
1062we avoid many stats. We also cache the files' existence in the
1063directory, for the same reason.
1064*/
1065QString QQmlTypeLoader::absoluteFilePath(const QString &path)
1066{
1067 if (path.isEmpty())
1068 return QString();
1069 if (path.at(i: 0) == QLatin1Char(':')) {
1070 // qrc resource
1071 QFileInfo fileInfo(path);
1072 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1073 } else if (path.size() > 3 && path.at(i: 3) == QLatin1Char(':') &&
1074 path.startsWith(s: QLatin1String("qrc"), cs: Qt::CaseInsensitive)) {
1075 // qrc resource url
1076 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1077 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1078 }
1079#if defined(Q_OS_ANDROID)
1080 else if (path.size() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') &&
1081 path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) {
1082 // assets resource url
1083 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1084 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1085 } else if (path.size() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') &&
1086 path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) {
1087 // content url
1088 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1089 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1090 }
1091#endif
1092
1093 int lastSlash = path.lastIndexOf(c: QLatin1Char('/'));
1094 QString dirPath(path.left(n: lastSlash));
1095
1096 LockHolder<QQmlTypeLoader> holder(this);
1097 if (!m_importDirCache.contains(key: dirPath)) {
1098 bool exists = QDir(dirPath).exists();
1099 QCache<QString, bool> *entry = exists ? new QCache<QString, bool> : nullptr;
1100 m_importDirCache.insert(key: dirPath, object: entry);
1101 }
1102 QCache<QString, bool> *fileSet = m_importDirCache.object(key: dirPath);
1103 if (!fileSet)
1104 return QString();
1105
1106 QString absoluteFilePath;
1107 QString fileName(path.mid(position: lastSlash+1, n: path.size()-lastSlash-1));
1108
1109 bool *value = fileSet->object(key: fileName);
1110 if (value) {
1111 if (*value)
1112 absoluteFilePath = path;
1113 } else {
1114 bool exists = QFile::exists(fileName: path);
1115 fileSet->insert(key: fileName, object: new bool(exists));
1116 if (exists)
1117 absoluteFilePath = path;
1118 }
1119
1120 if (absoluteFilePath.size() > 2 && absoluteFilePath.at(i: 0) != QLatin1Char('/') && absoluteFilePath.at(i: 1) != QLatin1Char(':'))
1121 absoluteFilePath = QFileInfo(absoluteFilePath).absoluteFilePath();
1122
1123 return absoluteFilePath;
1124}
1125
1126bool QQmlTypeLoader::fileExists(const QString &path, const QString &file)
1127{
1128 const QChar nullChar(QChar::Null);
1129 if (path.isEmpty() || path.contains(c: nullChar) || file.isEmpty() || file.contains(c: nullChar))
1130 return false;
1131
1132 Q_ASSERT(path.endsWith(QLatin1Char('/')));
1133
1134 LockHolder<QQmlTypeLoader> holder(this);
1135 QCache<QString, bool> *fileSet = m_importDirCache.object(key: path);
1136 if (fileSet) {
1137 if (bool *value = fileSet->object(key: file))
1138 return *value;
1139 } else if (m_importDirCache.contains(key: path)) {
1140 // explicit nullptr in cache
1141 return false;
1142 }
1143
1144 auto addToCache = [&](const QFileInfo &fileInfo) {
1145 if (!fileSet) {
1146 fileSet = fileInfo.dir().exists() ? new QCache<QString, bool> : nullptr;
1147 m_importDirCache.insert(key: path, object: fileSet);
1148 if (!fileSet)
1149 return false;
1150 }
1151
1152 const bool exists = fileInfo.exists();
1153 fileSet->insert(key: file, object: new bool(exists));
1154 return exists;
1155 };
1156
1157 if (path.at(i: 0) == QLatin1Char(':')) {
1158 // qrc resource
1159 return addToCache(QFileInfo(path + file));
1160 }
1161
1162 if (path.size() > 3 && path.at(i: 3) == QLatin1Char(':')
1163 && path.startsWith(s: QLatin1String("qrc"), cs: Qt::CaseInsensitive)) {
1164 // qrc resource url
1165 return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file)));
1166 }
1167
1168#if defined(Q_OS_ANDROID)
1169 if (path.size() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/')
1170 && path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) {
1171 // assets resource url
1172 return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file)));
1173 }
1174
1175 if (path.size() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/')
1176 && path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) {
1177 // content url
1178 return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file)));
1179 }
1180#endif
1181
1182 return addToCache(QFileInfo(path + file));
1183}
1184
1185
1186/*!
1187Returns true if the path is a directory via a directory cache. Cache is
1188shared with absoluteFilePath().
1189*/
1190bool QQmlTypeLoader::directoryExists(const QString &path)
1191{
1192 if (path.isEmpty())
1193 return false;
1194
1195 bool isResource = path.at(i: 0) == QLatin1Char(':');
1196#if defined(Q_OS_ANDROID)
1197 isResource = isResource || path.startsWith(QLatin1String("assets:/")) || path.startsWith(QLatin1String("content:/"));
1198#endif
1199
1200 if (isResource) {
1201 // qrc resource
1202 QFileInfo fileInfo(path);
1203 return fileInfo.exists() && fileInfo.isDir();
1204 }
1205
1206 int length = path.size();
1207 if (path.endsWith(c: QLatin1Char('/')))
1208 --length;
1209 QString dirPath(path.left(n: length));
1210
1211 LockHolder<QQmlTypeLoader> holder(this);
1212 if (!m_importDirCache.contains(key: dirPath)) {
1213 bool exists = QDir(dirPath).exists();
1214 QCache<QString, bool> *files = exists ? new QCache<QString, bool> : nullptr;
1215 m_importDirCache.insert(key: dirPath, object: files);
1216 }
1217
1218 QCache<QString, bool> *fileSet = m_importDirCache.object(key: dirPath);
1219 return fileSet != nullptr;
1220}
1221
1222
1223/*!
1224Return a QQmlTypeLoaderQmldirContent for absoluteFilePath. The QQmlTypeLoaderQmldirContent may be cached.
1225
1226\a filePath is a local file path.
1227
1228It can also be a remote path for a remote directory import, but it will have been cached by now in this case.
1229*/
1230const QQmlTypeLoaderQmldirContent QQmlTypeLoader::qmldirContent(const QString &filePathIn)
1231{
1232 LockHolder<QQmlTypeLoader> holder(this);
1233
1234 QString filePath;
1235
1236 // Try to guess if filePathIn is already a URL. This is necessarily fragile, because
1237 // - paths can contain ':', which might make them appear as URLs with schemes.
1238 // - windows drive letters appear as schemes (thus "< 2" below).
1239 // - a "file:" URL is equivalent to the respective file, but will be treated differently.
1240 // Yet, this heuristic is the best we can do until we pass more structured information here,
1241 // for example a QUrl also for local files.
1242 QUrl url(filePathIn);
1243 if (url.scheme().size() < 2) {
1244 filePath = filePathIn;
1245 } else {
1246 filePath = QQmlFile::urlToLocalFileOrQrc(url);
1247 if (filePath.isEmpty()) { // Can't load the remote here, but should be cached
1248 if (auto entry = m_importQmlDirCache.value(key: filePathIn))
1249 return **entry;
1250 else
1251 return QQmlTypeLoaderQmldirContent();
1252 }
1253 }
1254
1255 QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(key: filePath);
1256 if (val)
1257 return **val;
1258 QQmlTypeLoaderQmldirContent *qmldir = new QQmlTypeLoaderQmldirContent;
1259
1260#define ERROR(description) { QQmlError e; e.setDescription(description); qmldir->setError(e); }
1261#define NOT_READABLE_ERROR QString(QLatin1String("module \"$$URI$$\" definition \"%1\" not readable"))
1262#define CASE_MISMATCH_ERROR QString(QLatin1String("cannot load module \"$$URI$$\": File name case mismatch for \"%1\""))
1263
1264 QFile file(filePath);
1265 if (!QQml_isFileCaseCorrect(fileName: filePath)) {
1266 ERROR(CASE_MISMATCH_ERROR.arg(filePath));
1267 } else if (file.open(flags: QFile::ReadOnly)) {
1268 QByteArray data = file.readAll();
1269 qmldir->setContent(location: filePath, content: QString::fromUtf8(ba: data));
1270 } else {
1271 ERROR(NOT_READABLE_ERROR.arg(filePath));
1272 }
1273
1274#undef ERROR
1275#undef NOT_READABLE_ERROR
1276#undef CASE_MISMATCH_ERROR
1277
1278 m_importQmlDirCache.insert(key: filePath, value: qmldir);
1279 return *qmldir;
1280}
1281
1282void QQmlTypeLoader::setQmldirContent(const QString &url, const QString &content)
1283{
1284 QQmlTypeLoaderQmldirContent *qmldir;
1285 QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(key: url);
1286 if (val) {
1287 qmldir = *val;
1288 } else {
1289 qmldir = new QQmlTypeLoaderQmldirContent;
1290 m_importQmlDirCache.insert(key: url, value: qmldir);
1291 }
1292
1293 if (!qmldir->hasContent())
1294 qmldir->setContent(location: url, content);
1295}
1296
1297/*!
1298Clears cached information about loaded files, including any type data, scripts
1299and qmldir information.
1300*/
1301void QQmlTypeLoader::clearCache()
1302{
1303 for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end; ++iter)
1304 (*iter)->release();
1305 for (ScriptCache::Iterator iter = m_scriptCache.begin(), end = m_scriptCache.end(); iter != end; ++iter)
1306 (*iter)->release();
1307 for (QmldirCache::Iterator iter = m_qmldirCache.begin(), end = m_qmldirCache.end(); iter != end; ++iter)
1308 (*iter)->release();
1309
1310 qDeleteAll(c: m_importQmlDirCache);
1311
1312 m_typeCache.clear();
1313 m_typeCacheTrimThreshold = TYPELOADER_MINIMUM_TRIM_THRESHOLD;
1314 m_scriptCache.clear();
1315 m_qmldirCache.clear();
1316 m_importDirCache.clear();
1317 m_importQmlDirCache.clear();
1318 m_checksumCache.clear();
1319 QQmlMetaType::freeUnusedTypesAndCaches();
1320}
1321
1322void QQmlTypeLoader::updateTypeCacheTrimThreshold()
1323{
1324 int size = m_typeCache.size();
1325 if (size > m_typeCacheTrimThreshold)
1326 m_typeCacheTrimThreshold = size * 2;
1327 if (size < m_typeCacheTrimThreshold / 2)
1328 m_typeCacheTrimThreshold = qMax(a: size * 2, TYPELOADER_MINIMUM_TRIM_THRESHOLD);
1329}
1330
1331void QQmlTypeLoader::trimCache()
1332{
1333 while (true) {
1334 bool deletedOneType = false;
1335 for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end;) {
1336 QQmlTypeData *typeData = iter.value();
1337
1338 // typeData->m_compiledData may be set early on in the proccess of loading a file, so
1339 // it's important to check the general loading status of the typeData before making any
1340 // other decisions.
1341 if (typeData->count() == 1 && (typeData->isError() || typeData->isComplete())
1342 && (!typeData->m_compiledData || typeData->m_compiledData->count() == 1)) {
1343 // There are no live objects of this type
1344 iter.value()->release();
1345 iter = m_typeCache.erase(it: iter);
1346 deletedOneType = true;
1347 } else {
1348 ++iter;
1349 }
1350 }
1351
1352 if (!deletedOneType)
1353 break;
1354 }
1355
1356 updateTypeCacheTrimThreshold();
1357
1358 QQmlMetaType::freeUnusedTypesAndCaches();
1359
1360 // TODO: release any scripts which are no longer referenced by any types
1361}
1362
1363bool QQmlTypeLoader::isTypeLoaded(const QUrl &url) const
1364{
1365 LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this));
1366 return m_typeCache.contains(key: url);
1367}
1368
1369bool QQmlTypeLoader::isScriptLoaded(const QUrl &url) const
1370{
1371 LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this));
1372 return m_scriptCache.contains(key: url);
1373}
1374
1375QT_END_NAMESPACE
1376

source code of qtdeclarative/src/qml/qml/qqmltypeloader.cpp