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