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