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