1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtQml module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qqmltypeloader_p.h"
41#include "qqmlabstracturlinterceptor.h"
42#include "qqmlexpression_p.h"
43
44#include <private/qqmlengine_p.h>
45#include <private/qqmlglobal_p.h>
46#include <private/qqmlthread_p.h>
47#include <private/qv4codegen_p.h>
48#include <private/qqmlcomponent_p.h>
49#include <private/qqmlprofiler_p.h>
50#include <private/qqmlmemoryprofiler_p.h>
51#include <private/qqmltypecompiler_p.h>
52#include <private/qqmlpropertyvalidator_p.h>
53#include <private/qqmlpropertycachecreator_p.h>
54#include <private/qv4module_p.h>
55
56#include <QtCore/qdir.h>
57#include <QtCore/qfile.h>
58#include <QtCore/qdatetime.h>
59#include <QtCore/qdebug.h>
60#include <QtCore/qmutex.h>
61#include <QtCore/qthread.h>
62#include <QtQml/qqmlfile.h>
63#include <QtCore/qdiriterator.h>
64#include <QtQml/qqmlcomponent.h>
65#include <QtCore/qwaitcondition.h>
66#include <QtCore/qloggingcategory.h>
67#include <QtQml/qqmlextensioninterface.h>
68#include <QtCore/qcryptographichash.h>
69#include <QtCore/qscopeguard.h>
70
71#include <functional>
72
73#if defined (Q_OS_UNIX)
74#include <sys/types.h>
75#include <sys/stat.h>
76#include <unistd.h>
77#endif
78
79#if defined (QT_LINUXBASE)
80// LSB doesn't declare NAME_MAX. Use SYMLINK_MAX instead, which seems to
81// always be identical to NAME_MAX
82#ifndef NAME_MAX
83# define NAME_MAX _POSIX_SYMLINK_MAX
84#endif
85
86#endif
87
88// #define DATABLOB_DEBUG
89
90#ifdef DATABLOB_DEBUG
91
92#define ASSERT_MAINTHREAD() do { if (m_thread->isThisThread()) qFatal("QQmlTypeLoader: Caller not in main thread"); } while (false)
93#define ASSERT_LOADTHREAD() do { if (!m_thread->isThisThread()) qFatal("QQmlTypeLoader: Caller not in load thread"); } while (false)
94#define ASSERT_CALLBACK() do { if (!m_typeLoader || !m_typeLoader->m_thread->isThisThread()) qFatal("QQmlDataBlob: An API call was made outside a callback"); } while (false)
95
96#else
97
98#define ASSERT_MAINTHREAD()
99#define ASSERT_LOADTHREAD()
100#define ASSERT_CALLBACK()
101
102#endif
103
104DEFINE_BOOL_CONFIG_OPTION(dumpErrors, QML_DUMP_ERRORS);
105DEFINE_BOOL_CONFIG_OPTION(disableDiskCache, QML_DISABLE_DISK_CACHE);
106DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE);
107
108Q_DECLARE_LOGGING_CATEGORY(DBG_DISK_CACHE)
109Q_LOGGING_CATEGORY(DBG_DISK_CACHE, "qt.qml.diskcache")
110
111QT_BEGIN_NAMESPACE
112
113namespace {
114
115 template<typename LockType>
116 struct LockHolder
117 {
118 LockType& lock;
119 LockHolder(LockType *l) : lock(*l) { lock.lock(); }
120 ~LockHolder() { lock.unlock(); }
121 };
122}
123
124#if QT_CONFIG(qml_network)
125// This is a lame object that we need to ensure that slots connected to
126// QNetworkReply get called in the correct thread (the loader thread).
127// As QQmlTypeLoader lives in the main thread, and we can't use
128// Qt::DirectConnection connections from a QNetworkReply (because then
129// sender() wont work), we need to insert this object in the middle.
130class QQmlTypeLoaderNetworkReplyProxy : public QObject
131{
132 Q_OBJECT
133public:
134 QQmlTypeLoaderNetworkReplyProxy(QQmlTypeLoader *l);
135
136public slots:
137 void finished();
138 void downloadProgress(qint64, qint64);
139 void manualFinished(QNetworkReply*);
140
141private:
142 QQmlTypeLoader *l;
143};
144#endif // qml_network
145
146class QQmlTypeLoaderThread : public QQmlThread
147{
148 typedef QQmlTypeLoaderThread This;
149
150public:
151 QQmlTypeLoaderThread(QQmlTypeLoader *loader);
152#if QT_CONFIG(qml_network)
153 QNetworkAccessManager *networkAccessManager() const;
154 QQmlTypeLoaderNetworkReplyProxy *networkReplyProxy() const;
155#endif // qml_network
156 void load(QQmlDataBlob *b);
157 void loadAsync(QQmlDataBlob *b);
158 void loadWithStaticData(QQmlDataBlob *b, const QByteArray &);
159 void loadWithStaticDataAsync(QQmlDataBlob *b, const QByteArray &);
160 void loadWithCachedUnit(QQmlDataBlob *b, const QV4::CompiledData::Unit *unit);
161 void loadWithCachedUnitAsync(QQmlDataBlob *b, const QV4::CompiledData::Unit *unit);
162 void callCompleted(QQmlDataBlob *b);
163 void callDownloadProgressChanged(QQmlDataBlob *b, qreal p);
164 void initializeEngine(QQmlExtensionInterface *, const char *);
165
166protected:
167 void shutdownThread() override;
168
169private:
170 void loadThread(QQmlDataBlob *b);
171 void loadWithStaticDataThread(QQmlDataBlob *b, const QByteArray &);
172 void loadWithCachedUnitThread(QQmlDataBlob *b, const QV4::CompiledData::Unit *unit);
173 void callCompletedMain(QQmlDataBlob *b);
174 void callDownloadProgressChangedMain(QQmlDataBlob *b, qreal p);
175 void initializeEngineMain(QQmlExtensionInterface *iface, const char *uri);
176
177 QQmlTypeLoader *m_loader;
178#if QT_CONFIG(qml_network)
179 mutable QNetworkAccessManager *m_networkAccessManager;
180 mutable QQmlTypeLoaderNetworkReplyProxy *m_networkReplyProxy;
181#endif // qml_network
182};
183
184#if QT_CONFIG(qml_network)
185QQmlTypeLoaderNetworkReplyProxy::QQmlTypeLoaderNetworkReplyProxy(QQmlTypeLoader *l)
186: l(l)
187{
188}
189
190void QQmlTypeLoaderNetworkReplyProxy::finished()
191{
192 Q_ASSERT(sender());
193 Q_ASSERT(qobject_cast<QNetworkReply *>(sender()));
194 QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
195 l->networkReplyFinished(reply);
196}
197
198void QQmlTypeLoaderNetworkReplyProxy::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
199{
200 Q_ASSERT(sender());
201 Q_ASSERT(qobject_cast<QNetworkReply *>(sender()));
202 QNetworkReply *reply = static_cast<QNetworkReply *>(sender());
203 l->networkReplyProgress(reply, bytesReceived, bytesTotal);
204}
205
206// This function is for when you want to shortcut the signals and call directly
207void QQmlTypeLoaderNetworkReplyProxy::manualFinished(QNetworkReply *reply)
208{
209 qint64 replySize = reply->size();
210 l->networkReplyProgress(reply, replySize, replySize);
211 l->networkReplyFinished(reply);
212}
213#endif // qml_network
214
215/*!
216\class QQmlDataBlob
217\brief The QQmlDataBlob encapsulates a data request that can be issued to a QQmlTypeLoader.
218\internal
219
220QQmlDataBlob's are loaded by a QQmlTypeLoader. The user creates the QQmlDataBlob
221and then calls QQmlTypeLoader::load() or QQmlTypeLoader::loadWithStaticData() to load it.
222The QQmlTypeLoader invokes callbacks on the QQmlDataBlob as data becomes available.
223*/
224
225/*!
226\enum QQmlDataBlob::Status
227
228This enum describes the status of the data blob.
229
230\list
231\li Null The blob has not yet been loaded by a QQmlTypeLoader
232\li Loading The blob is loading network data. The QQmlDataBlob::setData() callback has not yet been
233invoked or has not yet returned.
234\li WaitingForDependencies The blob is waiting for dependencies to be done before continueing. This status
235only occurs after the QQmlDataBlob::setData() callback has been made, and when the blob has outstanding
236dependencies.
237\li Complete The blob's data has been loaded and all dependencies are done.
238\li Error An error has been set on this blob.
239\endlist
240*/
241
242/*!
243\enum QQmlDataBlob::Type
244
245This enum describes the type of the data blob.
246
247\list
248\li QmlFile This is a QQmlTypeData
249\li JavaScriptFile This is a QQmlScriptData
250\li QmldirFile This is a QQmlQmldirData
251\endlist
252*/
253
254/*!
255Create a new QQmlDataBlob for \a url and of the provided \a type.
256*/
257QQmlDataBlob::QQmlDataBlob(const QUrl &url, Type type, QQmlTypeLoader *manager)
258: m_typeLoader(manager), m_type(type), m_url(url), m_finalUrl(url), m_redirectCount(0),
259 m_inCallback(false), m_isDone(false)
260{
261 //Set here because we need to get the engine from the manager
262 if (m_typeLoader->engine() && m_typeLoader->engine()->urlInterceptor())
263 m_url = m_typeLoader->engine()->urlInterceptor()->intercept(m_url,
264 (QQmlAbstractUrlInterceptor::DataType)m_type);
265}
266
267/*! \internal */
268QQmlDataBlob::~QQmlDataBlob()
269{
270 Q_ASSERT(m_waitingOnMe.isEmpty());
271
272 cancelAllWaitingFor();
273}
274
275/*!
276 Must be called before loading can occur.
277*/
278void QQmlDataBlob::startLoading()
279{
280 Q_ASSERT(status() == QQmlDataBlob::Null);
281 m_data.setStatus(QQmlDataBlob::Loading);
282}
283
284/*!
285Returns the type provided to the constructor.
286*/
287QQmlDataBlob::Type QQmlDataBlob::type() const
288{
289 return m_type;
290}
291
292/*!
293Returns the blob's status.
294*/
295QQmlDataBlob::Status QQmlDataBlob::status() const
296{
297 return m_data.status();
298}
299
300/*!
301Returns true if the status is Null.
302*/
303bool QQmlDataBlob::isNull() const
304{
305 return status() == Null;
306}
307
308/*!
309Returns true if the status is Loading.
310*/
311bool QQmlDataBlob::isLoading() const
312{
313 return status() == Loading;
314}
315
316/*!
317Returns true if the status is WaitingForDependencies.
318*/
319bool QQmlDataBlob::isWaiting() const
320{
321 return status() == WaitingForDependencies ||
322 status() == ResolvingDependencies;
323}
324
325/*!
326Returns true if the status is Complete.
327*/
328bool QQmlDataBlob::isComplete() const
329{
330 return status() == Complete;
331}
332
333/*!
334Returns true if the status is Error.
335*/
336bool QQmlDataBlob::isError() const
337{
338 return status() == Error;
339}
340
341/*!
342Returns true if the status is Complete or Error.
343*/
344bool QQmlDataBlob::isCompleteOrError() const
345{
346 Status s = status();
347 return s == Error || s == Complete;
348}
349
350/*!
351Returns the data download progress from 0 to 1.
352*/
353qreal QQmlDataBlob::progress() const
354{
355 quint8 p = m_data.progress();
356 if (p == 0xFF) return 1.;
357 else return qreal(p) / qreal(0xFF);
358}
359
360/*!
361Returns the physical url of the data. Initially this is the same as
362finalUrl(), but if a URL interceptor is set, it will work on this URL
363and leave finalUrl() alone.
364
365\sa finalUrl()
366*/
367QUrl QQmlDataBlob::url() const
368{
369 return m_url;
370}
371
372QString QQmlDataBlob::urlString() const
373{
374 if (m_urlString.isEmpty())
375 m_urlString = m_url.toString();
376
377 return m_urlString;
378}
379
380/*!
381Returns the logical URL to be used for resolving further URLs referred to in
382the code.
383
384This is the blob url passed to the constructor. If a URL interceptor rewrites
385the URL, this one stays the same. If a network redirect happens while fetching
386the data, this url is updated to reflect the new location. Therefore, if both
387an interception and a redirection happen, the final url will indirectly
388incorporate the result of the interception, potentially breaking further
389lookups.
390
391\sa url()
392*/
393QUrl QQmlDataBlob::finalUrl() const
394{
395 return m_finalUrl;
396}
397
398/*!
399Returns the finalUrl() as a string.
400*/
401QString QQmlDataBlob::finalUrlString() const
402{
403 if (m_finalUrlString.isEmpty())
404 m_finalUrlString = m_finalUrl.toString();
405
406 return m_finalUrlString;
407}
408
409/*!
410Return the errors on this blob.
411
412May only be called from the load thread, or after the blob isCompleteOrError().
413*/
414QList<QQmlError> QQmlDataBlob::errors() const
415{
416 Q_ASSERT(isCompleteOrError() || (m_typeLoader && m_typeLoader->m_thread->isThisThread()));
417 return m_errors;
418}
419
420/*!
421Mark this blob as having \a errors.
422
423All outstanding dependencies will be cancelled. Requests to add new dependencies
424will be ignored. Entry into the Error state is irreversable.
425
426The setError() method may only be called from within a QQmlDataBlob callback.
427*/
428void QQmlDataBlob::setError(const QQmlError &errors)
429{
430 ASSERT_CALLBACK();
431
432 QList<QQmlError> l;
433 l << errors;
434 setError(l);
435}
436
437/*!
438\overload
439*/
440void QQmlDataBlob::setError(const QList<QQmlError> &errors)
441{
442 ASSERT_CALLBACK();
443
444 Q_ASSERT(status() != Error);
445 Q_ASSERT(m_errors.isEmpty());
446
447 m_errors = errors; // Must be set before the m_data fence
448 m_data.setStatus(Error);
449
450 if (dumpErrors()) {
451 qWarning().nospace() << "Errors for " << urlString();
452 for (int ii = 0; ii < errors.count(); ++ii)
453 qWarning().nospace() << " " << qPrintable(errors.at(ii).toString());
454 }
455 cancelAllWaitingFor();
456
457 if (!m_inCallback)
458 tryDone();
459}
460
461void QQmlDataBlob::setError(const QQmlCompileError &error)
462{
463 QQmlError e;
464 e.setColumn(error.location.column);
465 e.setLine(error.location.line);
466 e.setDescription(error.description);
467 e.setUrl(url());
468 setError(e);
469}
470
471void QQmlDataBlob::setError(const QVector<QQmlCompileError> &errors)
472{
473 QList<QQmlError> finalErrors;
474 finalErrors.reserve(errors.count());
475 for (const QQmlCompileError &error: errors) {
476 QQmlError e;
477 e.setColumn(error.location.column);
478 e.setLine(error.location.line);
479 e.setDescription(error.description);
480 e.setUrl(url());
481 finalErrors << e;
482 }
483 setError(finalErrors);
484}
485
486void QQmlDataBlob::setError(const QString &description)
487{
488 QQmlError e;
489 e.setDescription(description);
490 e.setUrl(url());
491 setError(e);
492}
493
494/*!
495Wait for \a blob to become complete or to error. If \a blob is already
496complete or in error, or this blob is already complete, this has no effect.
497
498The setError() method may only be called from within a QQmlDataBlob callback.
499*/
500void QQmlDataBlob::addDependency(QQmlDataBlob *blob)
501{
502 ASSERT_CALLBACK();
503
504 Q_ASSERT(status() != Null);
505
506 if (!blob ||
507 blob->status() == Error || blob->status() == Complete ||
508 status() == Error || status() == Complete || m_isDone)
509 return;
510
511 for (auto existingDep: qAsConst(m_waitingFor))
512 if (existingDep.data() == blob)
513 return;
514
515 m_data.setStatus(WaitingForDependencies);
516
517 m_waitingFor.append(blob);
518 blob->m_waitingOnMe.append(this);
519}
520
521/*!
522\fn void QQmlDataBlob::dataReceived(const Data &data)
523
524Invoked when data for the blob is received. Implementors should use this callback
525to determine a blob's dependencies. Within this callback you may call setError()
526or addDependency().
527*/
528
529/*!
530Invoked once data has either been received or a network error occurred, and all
531dependencies are complete.
532
533You can set an error in this method, but you cannot add new dependencies. Implementors
534should use this callback to finalize processing of data.
535
536The default implementation does nothing.
537
538XXX Rename processData() or some such to avoid confusion between done() (processing thread)
539and completed() (main thread)
540*/
541void QQmlDataBlob::done()
542{
543}
544
545#if QT_CONFIG(qml_network)
546/*!
547Invoked if there is a network error while fetching this blob.
548
549The default implementation sets an appropriate QQmlError.
550*/
551void QQmlDataBlob::networkError(QNetworkReply::NetworkError networkError)
552{
553 Q_UNUSED(networkError);
554
555 QQmlError error;
556 error.setUrl(m_url);
557
558 const char *errorString = nullptr;
559 switch (networkError) {
560 default:
561 errorString = "Network error";
562 break;
563 case QNetworkReply::ConnectionRefusedError:
564 errorString = "Connection refused";
565 break;
566 case QNetworkReply::RemoteHostClosedError:
567 errorString = "Remote host closed the connection";
568 break;
569 case QNetworkReply::HostNotFoundError:
570 errorString = "Host not found";
571 break;
572 case QNetworkReply::TimeoutError:
573 errorString = "Timeout";
574 break;
575 case QNetworkReply::ProxyConnectionRefusedError:
576 case QNetworkReply::ProxyConnectionClosedError:
577 case QNetworkReply::ProxyNotFoundError:
578 case QNetworkReply::ProxyTimeoutError:
579 case QNetworkReply::ProxyAuthenticationRequiredError:
580 case QNetworkReply::UnknownProxyError:
581 errorString = "Proxy error";
582 break;
583 case QNetworkReply::ContentAccessDenied:
584 errorString = "Access denied";
585 break;
586 case QNetworkReply::ContentNotFoundError:
587 errorString = "File not found";
588 break;
589 case QNetworkReply::AuthenticationRequiredError:
590 errorString = "Authentication required";
591 break;
592 };
593
594 error.setDescription(QLatin1String(errorString));
595
596 setError(error);
597}
598#endif // qml_network
599
600/*!
601Called if \a blob, which was previously waited for, has an error.
602
603The default implementation does nothing.
604*/
605void QQmlDataBlob::dependencyError(QQmlDataBlob *blob)
606{
607 Q_UNUSED(blob);
608}
609
610/*!
611Called if \a blob, which was previously waited for, has completed.
612
613The default implementation does nothing.
614*/
615void QQmlDataBlob::dependencyComplete(QQmlDataBlob *blob)
616{
617 Q_UNUSED(blob);
618}
619
620/*!
621Called when all blobs waited for have completed. This occurs regardless of
622whether they are in error, or complete state.
623
624The default implementation does nothing.
625*/
626void QQmlDataBlob::allDependenciesDone()
627{
628 m_data.setStatus(QQmlDataBlob::ResolvingDependencies);
629}
630
631/*!
632Called when the download progress of this blob changes. \a progress goes
633from 0 to 1.
634
635This callback is only invoked if an asynchronous load for this blob is
636made. An asynchronous load is one in which the Asynchronous mode is
637specified explicitly, or one that is implicitly delayed due to a network
638operation.
639
640The default implementation does nothing.
641*/
642void QQmlDataBlob::downloadProgressChanged(qreal progress)
643{
644 Q_UNUSED(progress);
645}
646
647/*!
648Invoked on the main thread sometime after done() was called on the load thread.
649
650You cannot modify the blobs state at all in this callback and cannot depend on the
651order or timeliness of these callbacks. Implementors should use this callback to notify
652dependencies on the main thread that the blob is done and not a lot else.
653
654This callback is only invoked if an asynchronous load for this blob is
655made. An asynchronous load is one in which the Asynchronous mode is
656specified explicitly, or one that is implicitly delayed due to a network
657operation.
658
659The default implementation does nothing.
660*/
661void QQmlDataBlob::completed()
662{
663}
664
665
666void QQmlDataBlob::tryDone()
667{
668 if (status() != Loading && m_waitingFor.isEmpty() && !m_isDone) {
669 m_isDone = true;
670 addref();
671
672#ifdef DATABLOB_DEBUG
673 qWarning("QQmlDataBlob::done() %s", qPrintable(urlString()));
674#endif
675 done();
676
677 if (status() != Error)
678 m_data.setStatus(Complete);
679
680 notifyAllWaitingOnMe();
681
682 // Locking is not required here, as anyone expecting callbacks must
683 // already be protected against the blob being completed (as set above);
684#ifdef DATABLOB_DEBUG
685 qWarning("QQmlDataBlob: Dispatching completed");
686#endif
687 m_typeLoader->m_thread->callCompleted(this);
688
689 release();
690 }
691}
692
693void QQmlDataBlob::cancelAllWaitingFor()
694{
695 while (m_waitingFor.count()) {
696 QQmlRefPointer<QQmlDataBlob> blob = m_waitingFor.takeLast();
697
698 Q_ASSERT(blob->m_waitingOnMe.contains(this));
699
700 blob->m_waitingOnMe.removeOne(this);
701 }
702}
703
704void QQmlDataBlob::notifyAllWaitingOnMe()
705{
706 while (m_waitingOnMe.count()) {
707 QQmlDataBlob *blob = m_waitingOnMe.takeLast();
708
709 Q_ASSERT(std::any_of(blob->m_waitingFor.constBegin(), blob->m_waitingFor.constEnd(),
710 [this](const QQmlRefPointer<QQmlDataBlob> &waiting) { return waiting.data() == this; }));
711
712 blob->notifyComplete(this);
713 }
714}
715
716void QQmlDataBlob::notifyComplete(QQmlDataBlob *blob)
717{
718 Q_ASSERT(blob->status() == Error || blob->status() == Complete);
719 QQmlCompilingProfiler prof(typeLoader()->profiler(), blob);
720
721 m_inCallback = true;
722
723 QQmlRefPointer<QQmlDataBlob> blobRef;
724 for (int i = 0; i < m_waitingFor.count(); ++i) {
725 if (m_waitingFor.at(i).data() == blob) {
726 blobRef = m_waitingFor.takeAt(i);
727 break;
728 }
729 }
730 Q_ASSERT(blobRef);
731
732 if (blob->status() == Error) {
733 dependencyError(blob);
734 } else if (blob->status() == Complete) {
735 dependencyComplete(blob);
736 }
737
738 if (!isError() && m_waitingFor.isEmpty())
739 allDependenciesDone();
740
741 m_inCallback = false;
742
743 tryDone();
744}
745
746#define TD_STATUS_MASK 0x0000FFFF
747#define TD_STATUS_SHIFT 0
748#define TD_PROGRESS_MASK 0x00FF0000
749#define TD_PROGRESS_SHIFT 16
750#define TD_ASYNC_MASK 0x80000000
751
752QQmlDataBlob::ThreadData::ThreadData()
753: _p(0)
754{
755}
756
757QQmlDataBlob::Status QQmlDataBlob::ThreadData::status() const
758{
759 return QQmlDataBlob::Status((_p.load() & TD_STATUS_MASK) >> TD_STATUS_SHIFT);
760}
761
762void QQmlDataBlob::ThreadData::setStatus(QQmlDataBlob::Status status)
763{
764 while (true) {
765 int d = _p.load();
766 int nd = (d & ~TD_STATUS_MASK) | ((status << TD_STATUS_SHIFT) & TD_STATUS_MASK);
767 if (d == nd || _p.testAndSetOrdered(d, nd)) return;
768 }
769}
770
771bool QQmlDataBlob::ThreadData::isAsync() const
772{
773 return _p.load() & TD_ASYNC_MASK;
774}
775
776void QQmlDataBlob::ThreadData::setIsAsync(bool v)
777{
778 while (true) {
779 int d = _p.load();
780 int nd = (d & ~TD_ASYNC_MASK) | (v?TD_ASYNC_MASK:0);
781 if (d == nd || _p.testAndSetOrdered(d, nd)) return;
782 }
783}
784
785quint8 QQmlDataBlob::ThreadData::progress() const
786{
787 return quint8((_p.load() & TD_PROGRESS_MASK) >> TD_PROGRESS_SHIFT);
788}
789
790void QQmlDataBlob::ThreadData::setProgress(quint8 v)
791{
792 while (true) {
793 int d = _p.load();
794 int nd = (d & ~TD_PROGRESS_MASK) | ((v << TD_PROGRESS_SHIFT) & TD_PROGRESS_MASK);
795 if (d == nd || _p.testAndSetOrdered(d, nd)) return;
796 }
797}
798
799QQmlTypeLoaderThread::QQmlTypeLoaderThread(QQmlTypeLoader *loader)
800: m_loader(loader)
801#if QT_CONFIG(qml_network)
802, m_networkAccessManager(nullptr), m_networkReplyProxy(nullptr)
803#endif // qml_network
804{
805 // Do that after initializing all the members.
806 startup();
807}
808
809#if QT_CONFIG(qml_network)
810QNetworkAccessManager *QQmlTypeLoaderThread::networkAccessManager() const
811{
812 Q_ASSERT(isThisThread());
813 if (!m_networkAccessManager) {
814 m_networkAccessManager = QQmlEnginePrivate::get(m_loader->engine())->createNetworkAccessManager(nullptr);
815 m_networkReplyProxy = new QQmlTypeLoaderNetworkReplyProxy(m_loader);
816 }
817
818 return m_networkAccessManager;
819}
820
821QQmlTypeLoaderNetworkReplyProxy *QQmlTypeLoaderThread::networkReplyProxy() const
822{
823 Q_ASSERT(isThisThread());
824 Q_ASSERT(m_networkReplyProxy); // Must call networkAccessManager() first
825 return m_networkReplyProxy;
826}
827#endif // qml_network
828
829void QQmlTypeLoaderThread::load(QQmlDataBlob *b)
830{
831 b->addref();
832 callMethodInThread(&This::loadThread, b);
833}
834
835void QQmlTypeLoaderThread::loadAsync(QQmlDataBlob *b)
836{
837 b->addref();
838 postMethodToThread(&This::loadThread, b);
839}
840
841void QQmlTypeLoaderThread::loadWithStaticData(QQmlDataBlob *b, const QByteArray &d)
842{
843 b->addref();
844 callMethodInThread(&This::loadWithStaticDataThread, b, d);
845}
846
847void QQmlTypeLoaderThread::loadWithStaticDataAsync(QQmlDataBlob *b, const QByteArray &d)
848{
849 b->addref();
850 postMethodToThread(&This::loadWithStaticDataThread, b, d);
851}
852
853void QQmlTypeLoaderThread::loadWithCachedUnit(QQmlDataBlob *b, const QV4::CompiledData::Unit *unit)
854{
855 b->addref();
856 callMethodInThread(&This::loadWithCachedUnitThread, b, unit);
857}
858
859void QQmlTypeLoaderThread::loadWithCachedUnitAsync(QQmlDataBlob *b, const QV4::CompiledData::Unit *unit)
860{
861 b->addref();
862 postMethodToThread(&This::loadWithCachedUnitThread, b, unit);
863}
864
865void QQmlTypeLoaderThread::callCompleted(QQmlDataBlob *b)
866{
867 b->addref();
868#if !QT_CONFIG(thread)
869 if (!isThisThread())
870 postMethodToThread(&This::callCompletedMain, b);
871#else
872 postMethodToMain(&This::callCompletedMain, b);
873#endif
874}
875
876void QQmlTypeLoaderThread::callDownloadProgressChanged(QQmlDataBlob *b, qreal p)
877{
878 b->addref();
879#if !QT_CONFIG(thread)
880 if (!isThisThread())
881 postMethodToThread(&This::callDownloadProgressChangedMain, b, p);
882#else
883 postMethodToMain(&This::callDownloadProgressChangedMain, b, p);
884#endif
885}
886
887void QQmlTypeLoaderThread::initializeEngine(QQmlExtensionInterface *iface,
888 const char *uri)
889{
890 callMethodInMain(&This::initializeEngineMain, iface, uri);
891}
892
893void QQmlTypeLoaderThread::shutdownThread()
894{
895#if QT_CONFIG(qml_network)
896 delete m_networkAccessManager;
897 m_networkAccessManager = nullptr;
898 delete m_networkReplyProxy;
899 m_networkReplyProxy = nullptr;
900#endif // qml_network
901}
902
903void QQmlTypeLoaderThread::loadThread(QQmlDataBlob *b)
904{
905 m_loader->loadThread(b);
906 b->release();
907}
908
909void QQmlTypeLoaderThread::loadWithStaticDataThread(QQmlDataBlob *b, const QByteArray &d)
910{
911 m_loader->loadWithStaticDataThread(b, d);
912 b->release();
913}
914
915void QQmlTypeLoaderThread::loadWithCachedUnitThread(QQmlDataBlob *b, const QV4::CompiledData::Unit *unit)
916{
917 m_loader->loadWithCachedUnitThread(b, unit);
918 b->release();
919}
920
921void QQmlTypeLoaderThread::callCompletedMain(QQmlDataBlob *b)
922{
923 QML_MEMORY_SCOPE_URL(b->url());
924#ifdef DATABLOB_DEBUG
925 qWarning("QQmlTypeLoaderThread: %s completed() callback", qPrintable(b->urlString()));
926#endif
927 b->completed();
928 b->release();
929}
930
931void QQmlTypeLoaderThread::callDownloadProgressChangedMain(QQmlDataBlob *b, qreal p)
932{
933#ifdef DATABLOB_DEBUG
934 qWarning("QQmlTypeLoaderThread: %s downloadProgressChanged(%f) callback",
935 qPrintable(b->urlString()), p);
936#endif
937 b->downloadProgressChanged(p);
938 b->release();
939}
940
941void QQmlTypeLoaderThread::initializeEngineMain(QQmlExtensionInterface *iface,
942 const char *uri)
943{
944 Q_ASSERT(m_loader->engine()->thread() == QThread::currentThread());
945 iface->initializeEngine(m_loader->engine(), uri);
946}
947
948/*!
949\class QQmlTypeLoader
950\brief The QQmlTypeLoader class abstracts loading files and their dependencies over the network.
951\internal
952
953The QQmlTypeLoader class is provided for the exclusive use of the QQmlTypeLoader class.
954
955Clients create QQmlDataBlob instances and submit them to the QQmlTypeLoader class
956through the QQmlTypeLoader::load() or QQmlTypeLoader::loadWithStaticData() methods.
957The loader then fetches the data over the network or from the local file system in an efficient way.
958QQmlDataBlob is an abstract class, so should always be specialized.
959
960Once data is received, the QQmlDataBlob::dataReceived() method is invoked on the blob. The
961derived class should use this callback to process the received data. Processing of the data can
962result in an error being set (QQmlDataBlob::setError()), or one or more dependencies being
963created (QQmlDataBlob::addDependency()). Dependencies are other QQmlDataBlob's that
964are required before processing can fully complete.
965
966To complete processing, the QQmlDataBlob::done() callback is invoked. done() is called when
967one of these three preconditions are met.
968
969\list 1
970\li The QQmlDataBlob has no dependencies.
971\li The QQmlDataBlob has an error set.
972\li All the QQmlDataBlob's dependencies are themselves "done()".
973\endlist
974
975Thus QQmlDataBlob::done() will always eventually be called, even if the blob has an error set.
976*/
977
978void QQmlTypeLoader::invalidate()
979{
980 if (m_thread) {
981 shutdownThread();
982 delete m_thread;
983 m_thread = nullptr;
984 }
985
986#if QT_CONFIG(qml_network)
987 // Need to delete the network replies after
988 // the loader thread is shutdown as it could be
989 // getting new replies while we clear them
990 for (NetworkReplies::Iterator iter = m_networkReplies.begin(); iter != m_networkReplies.end(); ++iter)
991 (*iter)->release();
992 m_networkReplies.clear();
993#endif // qml_network
994}
995
996#if QT_CONFIG(qml_debug)
997void QQmlTypeLoader::setProfiler(QQmlProfiler *profiler)
998{
999 Q_ASSERT(!m_profiler);
1000 m_profiler.reset(profiler);
1001}
1002#endif
1003
1004struct PlainLoader {
1005 void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
1006 {
1007 loader->loadThread(blob);
1008 }
1009 void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
1010 {
1011 loader->m_thread->load(blob);
1012 }
1013 void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
1014 {
1015 loader->m_thread->loadAsync(blob);
1016 }
1017};
1018
1019struct StaticLoader {
1020 const QByteArray &data;
1021 StaticLoader(const QByteArray &data) : data(data) {}
1022
1023 void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
1024 {
1025 loader->loadWithStaticDataThread(blob, data);
1026 }
1027 void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
1028 {
1029 loader->m_thread->loadWithStaticData(blob, data);
1030 }
1031 void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
1032 {
1033 loader->m_thread->loadWithStaticDataAsync(blob, data);
1034 }
1035};
1036
1037struct CachedLoader {
1038 const QV4::CompiledData::Unit *unit;
1039 CachedLoader(const QV4::CompiledData::Unit *unit) : unit(unit) {}
1040
1041 void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
1042 {
1043 loader->loadWithCachedUnitThread(blob, unit);
1044 }
1045 void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
1046 {
1047 loader->m_thread->loadWithCachedUnit(blob, unit);
1048 }
1049 void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
1050 {
1051 loader->m_thread->loadWithCachedUnitAsync(blob, unit);
1052 }
1053};
1054
1055template<typename Loader>
1056void QQmlTypeLoader::doLoad(const Loader &loader, QQmlDataBlob *blob, Mode mode)
1057{
1058#ifdef DATABLOB_DEBUG
1059 qWarning("QQmlTypeLoader::doLoad(%s): %s thread", qPrintable(blob->urlString()),
1060 m_thread->isThisThread()?"Compile":"Engine");
1061#endif
1062 blob->startLoading();
1063
1064 if (m_thread->isThisThread()) {
1065 unlock();
1066 loader.loadThread(this, blob);
1067 lock();
1068 } else if (mode == Asynchronous) {
1069 blob->m_data.setIsAsync(true);
1070 unlock();
1071 loader.loadAsync(this, blob);
1072 lock();
1073 } else {
1074 unlock();
1075 loader.load(this, blob);
1076 lock();
1077 if (mode == PreferSynchronous) {
1078 if (!blob->isCompleteOrError())
1079 blob->m_data.setIsAsync(true);
1080 } else {
1081 Q_ASSERT(mode == Synchronous);
1082 while (!blob->isCompleteOrError()) {
1083 unlock();
1084 m_thread->waitForNextMessage();
1085 lock();
1086 }
1087 }
1088 }
1089}
1090
1091/*!
1092Load the provided \a blob from the network or filesystem.
1093
1094The loader must be locked.
1095*/
1096void QQmlTypeLoader::load(QQmlDataBlob *blob, Mode mode)
1097{
1098 doLoad(PlainLoader(), blob, mode);
1099}
1100
1101/*!
1102Load the provided \a blob with \a data. The blob's URL is not used by the data loader in this case.
1103
1104The loader must be locked.
1105*/
1106void QQmlTypeLoader::loadWithStaticData(QQmlDataBlob *blob, const QByteArray &data, Mode mode)
1107{
1108 doLoad(StaticLoader(data), blob, mode);
1109}
1110
1111void QQmlTypeLoader::loadWithCachedUnit(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit, Mode mode)
1112{
1113 doLoad(CachedLoader(unit), blob, mode);
1114}
1115
1116void QQmlTypeLoader::loadWithStaticDataThread(QQmlDataBlob *blob, const QByteArray &data)
1117{
1118 ASSERT_LOADTHREAD();
1119
1120 setData(blob, data);
1121}
1122
1123void QQmlTypeLoader::loadWithCachedUnitThread(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit)
1124{
1125 ASSERT_LOADTHREAD();
1126
1127 setCachedUnit(blob, unit);
1128}
1129
1130void QQmlTypeLoader::loadThread(QQmlDataBlob *blob)
1131{
1132 ASSERT_LOADTHREAD();
1133
1134 // Don't continue loading if we've been shutdown
1135 if (m_thread->isShutdown()) {
1136 QQmlError error;
1137 error.setDescription(QLatin1String("Interrupted by shutdown"));
1138 blob->setError(error);
1139 return;
1140 }
1141
1142 if (blob->m_url.isEmpty()) {
1143 QQmlError error;
1144 error.setDescription(QLatin1String("Invalid null URL"));
1145 blob->setError(error);
1146 return;
1147 }
1148
1149 QML_MEMORY_SCOPE_URL(blob->m_url);
1150
1151 if (QQmlFile::isSynchronous(blob->m_url)) {
1152 const QString fileName = QQmlFile::urlToLocalFileOrQrc(blob->m_url);
1153 if (!QQml_isFileCaseCorrect(fileName)) {
1154 blob->setError(QLatin1String("File name case mismatch"));
1155 return;
1156 }
1157
1158 blob->m_data.setProgress(0xFF);
1159 if (blob->m_data.isAsync())
1160 m_thread->callDownloadProgressChanged(blob, 1.);
1161
1162 setData(blob, fileName);
1163
1164 } else {
1165#if QT_CONFIG(qml_network)
1166 QNetworkReply *reply = m_thread->networkAccessManager()->get(QNetworkRequest(blob->m_url));
1167 QQmlTypeLoaderNetworkReplyProxy *nrp = m_thread->networkReplyProxy();
1168 blob->addref();
1169 m_networkReplies.insert(reply, blob);
1170
1171 if (reply->isFinished()) {
1172 nrp->manualFinished(reply);
1173 } else {
1174 QObject::connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
1175 nrp, SLOT(downloadProgress(qint64,qint64)));
1176 QObject::connect(reply, SIGNAL(finished()),
1177 nrp, SLOT(finished()));
1178 }
1179
1180#ifdef DATABLOB_DEBUG
1181 qWarning("QQmlDataBlob: requested %s", qPrintable(blob->urlString()));
1182#endif // DATABLOB_DEBUG
1183#endif // qml_network
1184 }
1185}
1186
1187#define DATALOADER_MAXIMUM_REDIRECT_RECURSION 16
1188
1189#ifndef TYPELOADER_MINIMUM_TRIM_THRESHOLD
1190#define TYPELOADER_MINIMUM_TRIM_THRESHOLD 64
1191#endif
1192
1193#if QT_CONFIG(qml_network)
1194void QQmlTypeLoader::networkReplyFinished(QNetworkReply *reply)
1195{
1196 Q_ASSERT(m_thread->isThisThread());
1197
1198 reply->deleteLater();
1199
1200 QQmlDataBlob *blob = m_networkReplies.take(reply);
1201
1202 Q_ASSERT(blob);
1203
1204 blob->m_redirectCount++;
1205
1206 if (blob->m_redirectCount < DATALOADER_MAXIMUM_REDIRECT_RECURSION) {
1207 QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
1208 if (redirect.isValid()) {
1209 QUrl url = reply->url().resolved(redirect.toUrl());
1210 blob->m_finalUrl = url;
1211 blob->m_finalUrlString.clear();
1212
1213 QNetworkReply *reply = m_thread->networkAccessManager()->get(QNetworkRequest(url));
1214 QObject *nrp = m_thread->networkReplyProxy();
1215 QObject::connect(reply, SIGNAL(finished()), nrp, SLOT(finished()));
1216 m_networkReplies.insert(reply, blob);
1217#ifdef DATABLOB_DEBUG
1218 qWarning("QQmlDataBlob: redirected to %s", qPrintable(blob->finalUrlString()));
1219#endif
1220 return;
1221 }
1222 }
1223
1224 if (reply->error()) {
1225 blob->networkError(reply->error());
1226 } else {
1227 QByteArray data = reply->readAll();
1228 setData(blob, data);
1229 }
1230
1231 blob->release();
1232}
1233
1234void QQmlTypeLoader::networkReplyProgress(QNetworkReply *reply,
1235 qint64 bytesReceived, qint64 bytesTotal)
1236{
1237 Q_ASSERT(m_thread->isThisThread());
1238
1239 QQmlDataBlob *blob = m_networkReplies.value(reply);
1240
1241 Q_ASSERT(blob);
1242
1243 if (bytesTotal != 0) {
1244 quint8 progress = 0xFF * (qreal(bytesReceived) / qreal(bytesTotal));
1245 blob->m_data.setProgress(progress);
1246 if (blob->m_data.isAsync())
1247 m_thread->callDownloadProgressChanged(blob, blob->m_data.progress());
1248 }
1249}
1250#endif // qml_network
1251
1252/*!
1253Return the QQmlEngine associated with this loader
1254*/
1255QQmlEngine *QQmlTypeLoader::engine() const
1256{
1257 return m_engine;
1258}
1259
1260/*!
1261Call the initializeEngine() method on \a iface. Used by QQmlImportDatabase to ensure it
1262gets called in the correct thread.
1263*/
1264void QQmlTypeLoader::initializeEngine(QQmlExtensionInterface *iface,
1265 const char *uri)
1266{
1267 Q_ASSERT(m_thread->isThisThread() || engine()->thread() == QThread::currentThread());
1268
1269 if (m_thread->isThisThread()) {
1270 m_thread->initializeEngine(iface, uri);
1271 } else {
1272 Q_ASSERT(engine()->thread() == QThread::currentThread());
1273 iface->initializeEngine(engine(), uri);
1274 }
1275}
1276
1277
1278void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QByteArray &data)
1279{
1280 QML_MEMORY_SCOPE_URL(blob->url());
1281 QQmlDataBlob::SourceCodeData d;
1282 d.inlineSourceCode = QString::fromUtf8(data);
1283 d.hasInlineSourceCode = true;
1284 setData(blob, d);
1285}
1286
1287void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QString &fileName)
1288{
1289 QML_MEMORY_SCOPE_URL(blob->url());
1290 QQmlDataBlob::SourceCodeData d;
1291 d.fileInfo = QFileInfo(fileName);
1292 setData(blob, d);
1293}
1294
1295void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QQmlDataBlob::SourceCodeData &d)
1296{
1297 QML_MEMORY_SCOPE_URL(blob->url());
1298 QQmlCompilingProfiler prof(profiler(), blob);
1299
1300 blob->m_inCallback = true;
1301
1302 blob->dataReceived(d);
1303
1304 if (!blob->isError() && !blob->isWaiting())
1305 blob->allDependenciesDone();
1306
1307 if (blob->status() != QQmlDataBlob::Error)
1308 blob->m_data.setStatus(QQmlDataBlob::WaitingForDependencies);
1309
1310 blob->m_inCallback = false;
1311
1312 blob->tryDone();
1313}
1314
1315void QQmlTypeLoader::setCachedUnit(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit)
1316{
1317 QML_MEMORY_SCOPE_URL(blob->url());
1318 QQmlCompilingProfiler prof(profiler(), blob);
1319
1320 blob->m_inCallback = true;
1321
1322 blob->initializeFromCachedUnit(unit);
1323
1324 if (!blob->isError() && !blob->isWaiting())
1325 blob->allDependenciesDone();
1326
1327 if (blob->status() != QQmlDataBlob::Error)
1328 blob->m_data.setStatus(QQmlDataBlob::WaitingForDependencies);
1329
1330 blob->m_inCallback = false;
1331
1332 blob->tryDone();
1333}
1334
1335void QQmlTypeLoader::shutdownThread()
1336{
1337 if (m_thread && !m_thread->isShutdown())
1338 m_thread->shutdown();
1339}
1340
1341QQmlTypeLoader::Blob::Blob(const QUrl &url, QQmlDataBlob::Type type, QQmlTypeLoader *loader)
1342 : QQmlDataBlob(url, type, loader), m_importCache(loader)
1343{
1344}
1345
1346QQmlTypeLoader::Blob::~Blob()
1347{
1348}
1349
1350bool QQmlTypeLoader::Blob::fetchQmldir(const QUrl &url, const QV4::CompiledData::Import *import, int priority, QList<QQmlError> *errors)
1351{
1352 QQmlRefPointer<QQmlQmldirData> data = typeLoader()->getQmldir(url);
1353
1354 data->setImport(this, import);
1355 data->setPriority(this, priority);
1356
1357 if (data->status() == Error) {
1358 // This qmldir must not exist - which is not an error
1359 return true;
1360 } else if (data->status() == Complete) {
1361 // This data is already available
1362 return qmldirDataAvailable(data, errors);
1363 }
1364
1365 // Wait for this data to become available
1366 addDependency(data.data());
1367 return true;
1368}
1369
1370bool QQmlTypeLoader::Blob::updateQmldir(const QQmlRefPointer<QQmlQmldirData> &data, const QV4::CompiledData::Import *import, QList<QQmlError> *errors)
1371{
1372 QString qmldirIdentifier = data->urlString();
1373 QString qmldirUrl = qmldirIdentifier.left(qmldirIdentifier.lastIndexOf(QLatin1Char('/')) + 1);
1374
1375 typeLoader()->setQmldirContent(qmldirIdentifier, data->content());
1376
1377 if (!m_importCache.updateQmldirContent(typeLoader()->importDatabase(), stringAt(import->uriIndex), stringAt(import->qualifierIndex), qmldirIdentifier, qmldirUrl, errors))
1378 return false;
1379
1380 QHash<const QV4::CompiledData::Import *, int>::iterator it = m_unresolvedImports.find(import);
1381 if (it != m_unresolvedImports.end()) {
1382 *it = data->priority(this);
1383 }
1384
1385 // Release this reference at destruction
1386 m_qmldirs << data;
1387
1388 const QString &importQualifier = stringAt(import->qualifierIndex);
1389 if (!importQualifier.isEmpty()) {
1390 // Does this library contain any qualified scripts?
1391 QUrl libraryUrl(qmldirUrl);
1392 const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(qmldirIdentifier);
1393 const auto qmldirScripts = qmldir.scripts();
1394 for (const QQmlDirParser::Script &script : qmldirScripts) {
1395 QUrl scriptUrl = libraryUrl.resolved(QUrl(script.fileName));
1396 QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl);
1397 addDependency(blob.data());
1398
1399 scriptImported(blob, import->location, script.nameSpace, importQualifier);
1400 }
1401 }
1402
1403 return true;
1404}
1405
1406bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import, QList<QQmlError> *errors)
1407{
1408 Q_ASSERT(errors);
1409
1410 QQmlImportDatabase *importDatabase = typeLoader()->importDatabase();
1411
1412 const QString &importUri = stringAt(import->uriIndex);
1413 const QString &importQualifier = stringAt(import->qualifierIndex);
1414 if (import->type == QV4::CompiledData::Import::ImportScript) {
1415 QUrl scriptUrl = finalUrl().resolved(QUrl(importUri));
1416 QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl);
1417 addDependency(blob.data());
1418
1419 scriptImported(blob, import->location, importQualifier, QString());
1420 } else if (import->type == QV4::CompiledData::Import::ImportLibrary) {
1421 QString qmldirFilePath;
1422 QString qmldirUrl;
1423
1424 if (QQmlMetaType::isLockedModule(importUri, import->majorVersion)) {
1425 //Locked modules are checked first, to save on filesystem checks
1426 if (!m_importCache.addLibraryImport(importDatabase, importUri, importQualifier, import->majorVersion,
1427 import->minorVersion, QString(), QString(), false, errors))
1428 return false;
1429
1430 } else if (m_importCache.locateQmldir(importDatabase, importUri, import->majorVersion, import->minorVersion,
1431 &qmldirFilePath, &qmldirUrl)) {
1432 // This is a local library import
1433 if (!m_importCache.addLibraryImport(importDatabase, importUri, importQualifier, import->majorVersion,
1434 import->minorVersion, qmldirFilePath, qmldirUrl, false, errors))
1435 return false;
1436
1437 if (!importQualifier.isEmpty()) {
1438 // Does this library contain any qualified scripts?
1439 QUrl libraryUrl(qmldirUrl);
1440 const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(qmldirFilePath);
1441 const auto qmldirScripts = qmldir.scripts();
1442 for (const QQmlDirParser::Script &script : qmldirScripts) {
1443 QUrl scriptUrl = libraryUrl.resolved(QUrl(script.fileName));
1444 QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl);
1445 addDependency(blob.data());
1446
1447 scriptImported(blob, import->location, script.nameSpace, importQualifier);
1448 }
1449 }
1450 } else {
1451 // Is this a module?
1452 if (QQmlMetaType::isAnyModule(importUri)) {
1453 if (!m_importCache.addLibraryImport(importDatabase, importUri, importQualifier, import->majorVersion,
1454 import->minorVersion, QString(), QString(), false, errors))
1455 return false;
1456 } else {
1457 // We haven't yet resolved this import
1458 m_unresolvedImports.insert(import, 0);
1459
1460 QQmlAbstractUrlInterceptor *interceptor = typeLoader()->engine()->urlInterceptor();
1461
1462 // Query any network import paths for this library.
1463 // Interceptor might redirect local paths.
1464 QStringList remotePathList = importDatabase->importPathList(
1465 interceptor ? QQmlImportDatabase::LocalOrRemote
1466 : QQmlImportDatabase::Remote);
1467 if (!remotePathList.isEmpty()) {
1468 // Add this library and request the possible locations for it
1469 if (!m_importCache.addLibraryImport(importDatabase, importUri, importQualifier, import->majorVersion,
1470 import->minorVersion, QString(), QString(), true, errors))
1471 return false;
1472
1473 // Probe for all possible locations
1474 int priority = 0;
1475 const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(importUri, remotePathList, import->majorVersion, import->minorVersion);
1476 for (const QString &qmldirPath : qmlDirPaths) {
1477 if (interceptor) {
1478 QUrl url = interceptor->intercept(
1479 QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
1480 QQmlAbstractUrlInterceptor::QmldirFile);
1481 if (!QQmlFile::isLocalFile(url)
1482 && !fetchQmldir(url, import, ++priority, errors)) {
1483 return false;
1484 }
1485 } else if (!fetchQmldir(QUrl(qmldirPath), import, ++priority, errors)) {
1486 return false;
1487 }
1488
1489 }
1490 }
1491 }
1492 }
1493 } else {
1494 Q_ASSERT(import->type == QV4::CompiledData::Import::ImportFile);
1495
1496 bool incomplete = false;
1497
1498 QUrl qmldirUrl = finalUrl().resolved(QUrl(importUri + QLatin1String("/qmldir")));
1499 if (!QQmlImports::isLocal(qmldirUrl)) {
1500 // This is a remote file; the import is currently incomplete
1501 incomplete = true;
1502 }
1503
1504 if (!m_importCache.addFileImport(importDatabase, importUri, importQualifier, import->majorVersion,
1505 import->minorVersion, incomplete, errors))
1506 return false;
1507
1508 if (incomplete) {
1509 if (!fetchQmldir(qmldirUrl, import, 1, errors))
1510 return false;
1511 }
1512 }
1513
1514 return true;
1515}
1516
1517void QQmlTypeLoader::Blob::dependencyComplete(QQmlDataBlob *blob)
1518{
1519 if (blob->type() == QQmlDataBlob::QmldirFile) {
1520 QQmlQmldirData *data = static_cast<QQmlQmldirData *>(blob);
1521
1522 const QV4::CompiledData::Import *import = data->import(this);
1523
1524 QList<QQmlError> errors;
1525 if (!qmldirDataAvailable(data, &errors)) {
1526 Q_ASSERT(errors.size());
1527 QQmlError error(errors.takeFirst());
1528 error.setUrl(m_importCache.baseUrl());
1529 error.setLine(import->location.line);
1530 error.setColumn(import->location.column);
1531 errors.prepend(error); // put it back on the list after filling out information.
1532 setError(errors);
1533 }
1534 }
1535}
1536
1537bool QQmlTypeLoader::Blob::isDebugging() const
1538{
1539 return typeLoader()->engine()->handle()->debugger() != nullptr;
1540}
1541
1542bool QQmlTypeLoader::Blob::qmldirDataAvailable(const QQmlRefPointer<QQmlQmldirData> &data, QList<QQmlError> *errors)
1543{
1544 bool resolve = true;
1545
1546 const QV4::CompiledData::Import *import = data->import(this);
1547 data->setImport(this, nullptr);
1548
1549 int priority = data->priority(this);
1550 data->setPriority(this, 0);
1551
1552 if (import) {
1553 // Do we need to resolve this import?
1554 QHash<const QV4::CompiledData::Import *, int>::iterator it = m_unresolvedImports.find(import);
1555 if (it != m_unresolvedImports.end()) {
1556 resolve = (*it == 0) || (*it > priority);
1557 }
1558
1559 if (resolve) {
1560 // This is the (current) best resolution for this import
1561 if (!updateQmldir(data, import, errors)) {
1562 return false;
1563 }
1564
1565 if (it != m_unresolvedImports.end())
1566 *it = priority;
1567 return true;
1568 }
1569 }
1570
1571 return true;
1572}
1573
1574
1575QQmlTypeLoaderQmldirContent::QQmlTypeLoaderQmldirContent()
1576{
1577}
1578
1579bool QQmlTypeLoaderQmldirContent::hasError() const
1580{
1581 return m_parser.hasError();
1582}
1583
1584QList<QQmlError> QQmlTypeLoaderQmldirContent::errors(const QString &uri) const
1585{
1586 return m_parser.errors(uri);
1587}
1588
1589QString QQmlTypeLoaderQmldirContent::typeNamespace() const
1590{
1591 return m_parser.typeNamespace();
1592}
1593
1594void QQmlTypeLoaderQmldirContent::setContent(const QString &location, const QString &content)
1595{
1596 m_hasContent = true;
1597 m_location = location;
1598 m_parser.parse(content);
1599}
1600
1601void QQmlTypeLoaderQmldirContent::setError(const QQmlError &error)
1602{
1603 m_parser.setError(error);
1604}
1605
1606QQmlDirComponents QQmlTypeLoaderQmldirContent::components() const
1607{
1608 return m_parser.components();
1609}
1610
1611QQmlDirScripts QQmlTypeLoaderQmldirContent::scripts() const
1612{
1613 return m_parser.scripts();
1614}
1615
1616QQmlDirPlugins QQmlTypeLoaderQmldirContent::plugins() const
1617{
1618 return m_parser.plugins();
1619}
1620
1621QString QQmlTypeLoaderQmldirContent::pluginLocation() const
1622{
1623 return m_location;
1624}
1625
1626bool QQmlTypeLoaderQmldirContent::designerSupported() const
1627{
1628 return m_parser.designerSupported();
1629}
1630
1631/*!
1632Constructs a new type loader that uses the given \a engine.
1633*/
1634QQmlTypeLoader::QQmlTypeLoader(QQmlEngine *engine)
1635 : m_engine(engine)
1636 , m_thread(new QQmlTypeLoaderThread(this))
1637 , m_mutex(m_thread->mutex())
1638 , m_typeCacheTrimThreshold(TYPELOADER_MINIMUM_TRIM_THRESHOLD)
1639{
1640}
1641
1642/*!
1643Destroys the type loader, first clearing the cache of any information about
1644loaded files.
1645*/
1646QQmlTypeLoader::~QQmlTypeLoader()
1647{
1648 // Stop the loader thread before releasing resources
1649 shutdownThread();
1650
1651 clearCache();
1652
1653 invalidate();
1654}
1655
1656QQmlImportDatabase *QQmlTypeLoader::importDatabase() const
1657{
1658 return &QQmlEnginePrivate::get(engine())->importDatabase;
1659}
1660
1661QUrl QQmlTypeLoader::normalize(const QUrl &unNormalizedUrl)
1662{
1663 QUrl normalized(unNormalizedUrl);
1664 if (normalized.scheme() == QLatin1String("qrc"))
1665 normalized.setHost(QString()); // map qrc:///a.qml to qrc:/a.qml
1666 return normalized;
1667}
1668
1669/*!
1670Returns a QQmlTypeData for the specified \a url. The QQmlTypeData may be cached.
1671*/
1672QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QUrl &unNormalizedUrl, Mode mode)
1673{
1674 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1675 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1676 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1677
1678 const QUrl url = normalize(unNormalizedUrl);
1679
1680 LockHolder<QQmlTypeLoader> holder(this);
1681
1682 QQmlTypeData *typeData = m_typeCache.value(url);
1683
1684 if (!typeData) {
1685 // Trim before adding the new type, so that we don't immediately trim it away
1686 if (m_typeCache.size() >= m_typeCacheTrimThreshold)
1687 trimCache();
1688
1689 typeData = new QQmlTypeData(url, this);
1690 // TODO: if (compiledData == 0), is it safe to omit this insertion?
1691 m_typeCache.insert(url, typeData);
1692 QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
1693 if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(typeData->url(), &error)) {
1694 QQmlTypeLoader::loadWithCachedUnit(typeData, cachedUnit, mode);
1695 } else {
1696 typeData->setCachedUnitStatus(error);
1697 QQmlTypeLoader::load(typeData, mode);
1698 }
1699 } else if ((mode == PreferSynchronous || mode == Synchronous) && QQmlFile::isSynchronous(url)) {
1700 // this was started Asynchronous, but we need to force Synchronous
1701 // completion now (if at all possible with this type of URL).
1702
1703 if (!m_thread->isThisThread()) {
1704 // this only works when called directly from the UI thread, but not
1705 // when recursively called on the QML thread via resolveTypes()
1706
1707 while (!typeData->isCompleteOrError()) {
1708 unlock();
1709 m_thread->waitForNextMessage();
1710 lock();
1711 }
1712 }
1713 }
1714
1715 return typeData;
1716}
1717
1718/*!
1719Returns a QQmlTypeData for the given \a data with the provided base \a url. The
1720QQmlTypeData will not be cached.
1721*/
1722QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QByteArray &data, const QUrl &url, Mode mode)
1723{
1724 LockHolder<QQmlTypeLoader> holder(this);
1725
1726 QQmlTypeData *typeData = new QQmlTypeData(url, this);
1727 QQmlTypeLoader::loadWithStaticData(typeData, data, mode);
1728
1729 return QQmlRefPointer<QQmlTypeData>(typeData, QQmlRefPointer<QQmlTypeData>::Adopt);
1730}
1731
1732/*!
1733Return a QQmlScriptBlob for \a url. The QQmlScriptData may be cached.
1734*/
1735QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(const QUrl &unNormalizedUrl)
1736{
1737 Q_ASSERT(!unNormalizedUrl.isRelative() &&
1738 (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
1739 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
1740
1741 const QUrl url = normalize(unNormalizedUrl);
1742
1743 LockHolder<QQmlTypeLoader> holder(this);
1744
1745 QQmlScriptBlob *scriptBlob = m_scriptCache.value(url);
1746
1747 if (!scriptBlob) {
1748 scriptBlob = new QQmlScriptBlob(url, this);
1749 m_scriptCache.insert(url, scriptBlob);
1750
1751 QQmlMetaType::CachedUnitLookupError error;
1752 if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(scriptBlob->url(), &error)) {
1753 QQmlTypeLoader::loadWithCachedUnit(scriptBlob, cachedUnit);
1754 } else {
1755 scriptBlob->setCachedUnitStatus(error);
1756 QQmlTypeLoader::load(scriptBlob);
1757 }
1758 }
1759
1760 return scriptBlob;
1761}
1762
1763/*!
1764Returns a QQmlQmldirData for \a url. The QQmlQmldirData may be cached.
1765*/
1766QQmlRefPointer<QQmlQmldirData> QQmlTypeLoader::getQmldir(const QUrl &url)
1767{
1768 Q_ASSERT(!url.isRelative() &&
1769 (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() ||
1770 !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url))));
1771 LockHolder<QQmlTypeLoader> holder(this);
1772
1773 QQmlQmldirData *qmldirData = m_qmldirCache.value(url);
1774
1775 if (!qmldirData) {
1776 qmldirData = new QQmlQmldirData(url, this);
1777 m_qmldirCache.insert(url, qmldirData);
1778 QQmlTypeLoader::load(qmldirData);
1779 }
1780
1781 return qmldirData;
1782}
1783
1784// #### Qt 6: Remove this function, it exists only for binary compatibility.
1785/*!
1786 * \internal
1787 */
1788bool QQmlEngine::addNamedBundle(const QString &name, const QString &fileName)
1789{
1790 Q_UNUSED(name)
1791 Q_UNUSED(fileName)
1792 return false;
1793}
1794
1795/*!
1796Returns the absolute filename of path via a directory cache.
1797Returns a empty string if the path does not exist.
1798
1799Why a directory cache? QML checks for files in many paths with
1800invalid directories. By caching whether a directory exists
1801we avoid many stats. We also cache the files' existence in the
1802directory, for the same reason.
1803*/
1804QString QQmlTypeLoader::absoluteFilePath(const QString &path)
1805{
1806 if (path.isEmpty())
1807 return QString();
1808 if (path.at(0) == QLatin1Char(':')) {
1809 // qrc resource
1810 QFileInfo fileInfo(path);
1811 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1812 } else if (path.count() > 3 && path.at(3) == QLatin1Char(':') &&
1813 path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) {
1814 // qrc resource url
1815 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1816 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1817 }
1818#if defined(Q_OS_ANDROID)
1819 else if (path.count() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') &&
1820 path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) {
1821 // assets resource url
1822 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1823 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1824 } else if (path.count() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') &&
1825 path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) {
1826 // content url
1827 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
1828 return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
1829 }
1830#endif
1831
1832 int lastSlash = path.lastIndexOf(QLatin1Char('/'));
1833 QString dirPath(path.left(lastSlash));
1834
1835 LockHolder<QQmlTypeLoader> holder(this);
1836 if (!m_importDirCache.contains(dirPath)) {
1837 bool exists = QDir(dirPath).exists();
1838 QCache<QString, bool> *entry = exists ? new QCache<QString, bool> : nullptr;
1839 m_importDirCache.insert(dirPath, entry);
1840 }
1841 QCache<QString, bool> *fileSet = m_importDirCache.object(dirPath);
1842 if (!fileSet)
1843 return QString();
1844
1845 QString absoluteFilePath;
1846 QString fileName(path.mid(lastSlash+1, path.length()-lastSlash-1));
1847
1848 bool *value = fileSet->object(fileName);
1849 if (value) {
1850 if (*value)
1851 absoluteFilePath = path;
1852 } else {
1853 bool exists = QFile::exists(path);
1854 fileSet->insert(fileName, new bool(exists));
1855 if (exists)
1856 absoluteFilePath = path;
1857 }
1858
1859 if (absoluteFilePath.length() > 2 && absoluteFilePath.at(0) != QLatin1Char('/') && absoluteFilePath.at(1) != QLatin1Char(':'))
1860 absoluteFilePath = QFileInfo(absoluteFilePath).absoluteFilePath();
1861
1862 return absoluteFilePath;
1863}
1864
1865bool QQmlTypeLoader::fileExists(const QString &path, const QString &file)
1866{
1867 if (path.isEmpty())
1868 return false;
1869 Q_ASSERT(path.endsWith(QLatin1Char('/')));
1870 if (path.at(0) == QLatin1Char(':')) {
1871 // qrc resource
1872 QFileInfo fileInfo(path + file);
1873 return fileInfo.isFile();
1874 } else if (path.count() > 3 && path.at(3) == QLatin1Char(':') &&
1875 path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) {
1876 // qrc resource url
1877 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path + file));
1878 return fileInfo.isFile();
1879 }
1880#if defined(Q_OS_ANDROID)
1881 else if (path.count() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') &&
1882 path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) {
1883 // assets resource url
1884 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path + file));
1885 return fileInfo.isFile();
1886 } else if (path.count() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') &&
1887 path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) {
1888 // content url
1889 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path + file));
1890 return fileInfo.isFile();
1891 }
1892#endif
1893
1894 LockHolder<QQmlTypeLoader> holder(this);
1895 if (!m_importDirCache.contains(path)) {
1896 bool exists = QDir(path).exists();
1897 QCache<QString, bool> *entry = exists ? new QCache<QString, bool> : nullptr;
1898 m_importDirCache.insert(path, entry);
1899 }
1900 QCache<QString, bool> *fileSet = m_importDirCache.object(path);
1901 if (!fileSet)
1902 return false;
1903
1904 bool *value = fileSet->object(file);
1905 if (value) {
1906 return *value;
1907 } else {
1908 bool exists = QFile::exists(path + file);
1909 fileSet->insert(file, new bool(exists));
1910 return exists;
1911 }
1912}
1913
1914
1915/*!
1916Returns true if the path is a directory via a directory cache. Cache is
1917shared with absoluteFilePath().
1918*/
1919bool QQmlTypeLoader::directoryExists(const QString &path)
1920{
1921 if (path.isEmpty())
1922 return false;
1923
1924 bool isResource = path.at(0) == QLatin1Char(':');
1925#if defined(Q_OS_ANDROID)
1926 isResource = isResource || path.startsWith(QLatin1String("assets:/")) || path.startsWith(QLatin1String("content:/"));
1927#endif
1928
1929 if (isResource) {
1930 // qrc resource
1931 QFileInfo fileInfo(path);
1932 return fileInfo.exists() && fileInfo.isDir();
1933 }
1934
1935 int length = path.length();
1936 if (path.endsWith(QLatin1Char('/')))
1937 --length;
1938 QString dirPath(path.left(length));
1939
1940 LockHolder<QQmlTypeLoader> holder(this);
1941 if (!m_importDirCache.contains(dirPath)) {
1942 bool exists = QDir(dirPath).exists();
1943 QCache<QString, bool> *files = exists ? new QCache<QString, bool> : nullptr;
1944 m_importDirCache.insert(dirPath, files);
1945 }
1946
1947 QCache<QString, bool> *fileSet = m_importDirCache.object(dirPath);
1948 return fileSet != nullptr;
1949}
1950
1951
1952/*!
1953Return a QQmlTypeLoaderQmldirContent for absoluteFilePath. The QQmlTypeLoaderQmldirContent may be cached.
1954
1955\a filePath is a local file path.
1956
1957It can also be a remote path for a remote directory import, but it will have been cached by now in this case.
1958*/
1959const QQmlTypeLoaderQmldirContent QQmlTypeLoader::qmldirContent(const QString &filePathIn)
1960{
1961 LockHolder<QQmlTypeLoader> holder(this);
1962
1963 QString filePath;
1964
1965 // Try to guess if filePathIn is already a URL. This is necessarily fragile, because
1966 // - paths can contain ':', which might make them appear as URLs with schemes.
1967 // - windows drive letters appear as schemes (thus "< 2" below).
1968 // - a "file:" URL is equivalent to the respective file, but will be treated differently.
1969 // Yet, this heuristic is the best we can do until we pass more structured information here,
1970 // for example a QUrl also for local files.
1971 QUrl url(filePathIn);
1972 if (url.scheme().length() < 2) {
1973 filePath = filePathIn;
1974 } else {
1975 filePath = QQmlFile::urlToLocalFileOrQrc(url);
1976 if (filePath.isEmpty()) { // Can't load the remote here, but should be cached
1977 if (auto entry = m_importQmlDirCache.value(filePathIn))
1978 return **entry;
1979 else
1980 return QQmlTypeLoaderQmldirContent();
1981 }
1982 }
1983
1984 QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(filePath);
1985 if (val)
1986 return **val;
1987 QQmlTypeLoaderQmldirContent *qmldir = new QQmlTypeLoaderQmldirContent;
1988
1989#define ERROR(description) { QQmlError e; e.setDescription(description); qmldir->setError(e); }
1990#define NOT_READABLE_ERROR QString(QLatin1String("module \"$$URI$$\" definition \"%1\" not readable"))
1991#define CASE_MISMATCH_ERROR QString(QLatin1String("cannot load module \"$$URI$$\": File name case mismatch for \"%1\""))
1992
1993 QFile file(filePath);
1994 if (!QQml_isFileCaseCorrect(filePath)) {
1995 ERROR(CASE_MISMATCH_ERROR.arg(filePath));
1996 } else if (file.open(QFile::ReadOnly)) {
1997 QByteArray data = file.readAll();
1998 qmldir->setContent(filePath, QString::fromUtf8(data));
1999 } else {
2000 ERROR(NOT_READABLE_ERROR.arg(filePath));
2001 }
2002
2003#undef ERROR
2004#undef NOT_READABLE_ERROR
2005#undef CASE_MISMATCH_ERROR
2006
2007 m_importQmlDirCache.insert(filePath, qmldir);
2008 return *qmldir;
2009}
2010
2011void QQmlTypeLoader::setQmldirContent(const QString &url, const QString &content)
2012{
2013 QQmlTypeLoaderQmldirContent *qmldir;
2014 QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(url);
2015 if (val) {
2016 qmldir = *val;
2017 } else {
2018 qmldir = new QQmlTypeLoaderQmldirContent;
2019 m_importQmlDirCache.insert(url, qmldir);
2020 }
2021
2022 qmldir->setContent(url, content);
2023}
2024
2025/*!
2026Clears cached information about loaded files, including any type data, scripts
2027and qmldir information.
2028*/
2029void QQmlTypeLoader::clearCache()
2030{
2031 for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end; ++iter)
2032 (*iter)->release();
2033 for (ScriptCache::Iterator iter = m_scriptCache.begin(), end = m_scriptCache.end(); iter != end; ++iter)
2034 (*iter)->release();
2035 for (QmldirCache::Iterator iter = m_qmldirCache.begin(), end = m_qmldirCache.end(); iter != end; ++iter)
2036 (*iter)->release();
2037
2038 qDeleteAll(m_importQmlDirCache);
2039
2040 m_typeCache.clear();
2041 m_typeCacheTrimThreshold = TYPELOADER_MINIMUM_TRIM_THRESHOLD;
2042 m_scriptCache.clear();
2043 m_qmldirCache.clear();
2044 m_importDirCache.clear();
2045 m_importQmlDirCache.clear();
2046 QQmlMetaType::freeUnusedTypesAndCaches();
2047}
2048
2049void QQmlTypeLoader::updateTypeCacheTrimThreshold()
2050{
2051 int size = m_typeCache.size();
2052 if (size > m_typeCacheTrimThreshold)
2053 m_typeCacheTrimThreshold = size * 2;
2054 if (size < m_typeCacheTrimThreshold / 2)
2055 m_typeCacheTrimThreshold = qMax(size * 2, TYPELOADER_MINIMUM_TRIM_THRESHOLD);
2056}
2057
2058void QQmlTypeLoader::trimCache()
2059{
2060 while (true) {
2061 QList<TypeCache::Iterator> unneededTypes;
2062 for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end; ++iter) {
2063 QQmlTypeData *typeData = iter.value();
2064
2065 // typeData->m_compiledData may be set early on in the proccess of loading a file, so
2066 // it's important to check the general loading status of the typeData before making any
2067 // other decisions.
2068 if (typeData->count() == 1 && (typeData->isError() || typeData->isComplete())
2069 && (!typeData->m_compiledData || typeData->m_compiledData->count() == 1)) {
2070 // There are no live objects of this type
2071 unneededTypes.append(iter);
2072 }
2073 }
2074
2075 if (unneededTypes.isEmpty())
2076 break;
2077
2078 while (!unneededTypes.isEmpty()) {
2079 TypeCache::Iterator iter = unneededTypes.takeLast();
2080
2081 iter.value()->release();
2082 m_typeCache.erase(iter);
2083 }
2084 }
2085
2086 updateTypeCacheTrimThreshold();
2087
2088 QQmlMetaType::freeUnusedTypesAndCaches();
2089
2090 // TODO: release any scripts which are no longer referenced by any types
2091}
2092
2093bool QQmlTypeLoader::isTypeLoaded(const QUrl &url) const
2094{
2095 LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this));
2096 return m_typeCache.contains(url);
2097}
2098
2099bool QQmlTypeLoader::isScriptLoaded(const QUrl &url) const
2100{
2101 LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this));
2102 return m_scriptCache.contains(url);
2103}
2104
2105QQmlTypeData::TypeDataCallback::~TypeDataCallback()
2106{
2107}
2108
2109QString QQmlTypeData::TypeReference::qualifiedName() const
2110{
2111 QString result;
2112 if (!prefix.isEmpty()) {
2113 result = prefix + QLatin1Char('.');
2114 }
2115 result.append(type.qmlTypeName());
2116 return result;
2117}
2118
2119QQmlTypeData::QQmlTypeData(const QUrl &url, QQmlTypeLoader *manager)
2120: QQmlTypeLoader::Blob(url, QmlFile, manager),
2121 m_typesResolved(false), m_implicitImportLoaded(false)
2122{
2123
2124}
2125
2126QQmlTypeData::~QQmlTypeData()
2127{
2128 m_scripts.clear();
2129 m_compositeSingletons.clear();
2130 m_resolvedTypes.clear();
2131}
2132
2133const QList<QQmlTypeData::ScriptReference> &QQmlTypeData::resolvedScripts() const
2134{
2135 return m_scripts;
2136}
2137
2138QV4::CompiledData::CompilationUnit *QQmlTypeData::compilationUnit() const
2139{
2140 return m_compiledData.data();
2141}
2142
2143void QQmlTypeData::registerCallback(TypeDataCallback *callback)
2144{
2145 Q_ASSERT(!m_callbacks.contains(callback));
2146 m_callbacks.append(callback);
2147}
2148
2149void QQmlTypeData::unregisterCallback(TypeDataCallback *callback)
2150{
2151 Q_ASSERT(m_callbacks.contains(callback));
2152 m_callbacks.removeOne(callback);
2153 Q_ASSERT(!m_callbacks.contains(callback));
2154}
2155
2156bool QQmlTypeData::tryLoadFromDiskCache()
2157{
2158 if (disableDiskCache() && !forceDiskCache())
2159 return false;
2160
2161 if (isDebugging())
2162 return false;
2163
2164 QV4::ExecutionEngine *v4 = typeLoader()->engine()->handle();
2165 if (!v4)
2166 return false;
2167
2168 QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit = QV4::Compiler::Codegen::createUnitForLoading();
2169 {
2170 QString error;
2171 if (!unit->loadFromDisk(url(), m_backupSourceCode.sourceTimeStamp(), &error)) {
2172 qCDebug(DBG_DISK_CACHE) << "Error loading" << urlString() << "from disk cache:" << error;
2173 return false;
2174 }
2175 }
2176
2177 if (unit->unitData()->flags & QV4::CompiledData::Unit::PendingTypeCompilation) {
2178 restoreIR(unit);
2179 return true;
2180 }
2181
2182 m_compiledData = unit;
2183
2184 for (int i = 0, count = m_compiledData->objectCount(); i < count; ++i)
2185 m_typeReferences.collectFromObject(m_compiledData->objectAt(i));
2186
2187 m_importCache.setBaseUrl(finalUrl(), finalUrlString());
2188
2189 // For remote URLs, we don't delay the loading of the implicit import
2190 // because the loading probably requires an asynchronous fetch of the
2191 // qmldir (so we can't load it just in time).
2192 if (!finalUrl().scheme().isEmpty()) {
2193 QUrl qmldirUrl = finalUrl().resolved(QUrl(QLatin1String("qmldir")));
2194 if (!QQmlImports::isLocal(qmldirUrl)) {
2195 if (!loadImplicitImport())
2196 return false;
2197
2198 // find the implicit import
2199 for (quint32 i = 0, count = m_compiledData->importCount(); i < count; ++i) {
2200 const QV4::CompiledData::Import *import = m_compiledData->importAt(i);
2201 if (m_compiledData->stringAt(import->uriIndex) == QLatin1String(".")
2202 && import->qualifierIndex == 0
2203 && import->majorVersion == -1
2204 && import->minorVersion == -1) {
2205 QList<QQmlError> errors;
2206 if (!fetchQmldir(qmldirUrl, import, 1, &errors)) {
2207 setError(errors);
2208 return false;
2209 }
2210 break;
2211 }
2212 }
2213 }
2214 }
2215
2216 for (int i = 0, count = m_compiledData->importCount(); i < count; ++i) {
2217 const QV4::CompiledData::Import *import = m_compiledData->importAt(i);
2218 QList<QQmlError> errors;
2219 if (!addImport(import, &errors)) {
2220 Q_ASSERT(errors.size());
2221 QQmlError error(errors.takeFirst());
2222 error.setUrl(m_importCache.baseUrl());
2223 error.setLine(import->location.line);
2224 error.setColumn(import->location.column);
2225 errors.prepend(error); // put it back on the list after filling out information.
2226 setError(errors);
2227 return false;
2228 }
2229 }
2230
2231 return true;
2232}
2233
2234void QQmlTypeData::createTypeAndPropertyCaches(const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache,
2235 const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache)
2236{
2237 Q_ASSERT(m_compiledData);
2238 m_compiledData->typeNameCache = typeNameCache;
2239 m_compiledData->resolvedTypes = resolvedTypeCache;
2240
2241 QQmlEnginePrivate * const engine = QQmlEnginePrivate::get(typeLoader()->engine());
2242
2243 QQmlPendingGroupPropertyBindings pendingGroupPropertyBindings;
2244
2245 {
2246 QQmlPropertyCacheCreator<QV4::CompiledData::CompilationUnit> propertyCacheCreator(&m_compiledData->propertyCaches,
2247 &pendingGroupPropertyBindings,
2248 engine, m_compiledData.data(), &m_importCache);
2249 QQmlCompileError error = propertyCacheCreator.buildMetaObjects();
2250 if (error.isSet()) {
2251 setError(error);
2252 return;
2253 }
2254 }
2255
2256 QQmlPropertyCacheAliasCreator<QV4::CompiledData::CompilationUnit> aliasCreator(&m_compiledData->propertyCaches, m_compiledData.data());
2257 aliasCreator.appendAliasPropertiesToMetaObjects();
2258
2259 pendingGroupPropertyBindings.resolveMissingPropertyCaches(engine, &m_compiledData->propertyCaches);
2260}
2261
2262static bool addTypeReferenceChecksumsToHash(const QList<QQmlTypeData::TypeReference> &typeRefs, QCryptographicHash *hash, QQmlEngine *engine)
2263{
2264 for (const auto &typeRef: typeRefs) {
2265 if (typeRef.typeData) {
2266 const auto unit = typeRef.typeData->compilationUnit()->unitData();
2267 hash->addData(unit->md5Checksum, sizeof(unit->md5Checksum));
2268 } else if (typeRef.type.isValid()) {
2269 const auto propertyCache = QQmlEnginePrivate::get(engine)->cache(typeRef.type.metaObject());
2270 bool ok = false;
2271 hash->addData(propertyCache->checksum(&ok));
2272 if (!ok)
2273 return false;
2274 }
2275 }
2276 return true;
2277}
2278
2279void QQmlTypeData::done()
2280{
2281 auto cleanup = qScopeGuard([this]{
2282 m_document.reset();
2283 m_typeReferences.clear();
2284 if (isError())
2285 m_compiledData = nullptr;
2286 });
2287
2288 if (isError())
2289 return;
2290
2291 // Check all script dependencies for errors
2292 for (int ii = 0; ii < m_scripts.count(); ++ii) {
2293 const ScriptReference &script = m_scripts.at(ii);
2294 Q_ASSERT(script.script->isCompleteOrError());
2295 if (script.script->isError()) {
2296 QList<QQmlError> errors = script.script->errors();
2297 QQmlError error;
2298 error.setUrl(url());
2299 error.setLine(script.location.line);
2300 error.setColumn(script.location.column);
2301 error.setDescription(QQmlTypeLoader::tr("Script %1 unavailable").arg(script.script->urlString()));
2302 errors.prepend(error);
2303 setError(errors);
2304 return;
2305 }
2306 }
2307
2308 // Check all type dependencies for errors
2309 for (auto it = m_resolvedTypes.constBegin(), end = m_resolvedTypes.constEnd(); it != end;
2310 ++it) {
2311 const TypeReference &type = *it;
2312 Q_ASSERT(!type.typeData || type.typeData->isCompleteOrError());
2313 if (type.typeData && type.typeData->isError()) {
2314 const QString typeName = stringAt(it.key());
2315
2316 QList<QQmlError> errors = type.typeData->errors();
2317 QQmlError error;
2318 error.setUrl(url());
2319 error.setLine(type.location.line);
2320 error.setColumn(type.location.column);
2321 error.setDescription(QQmlTypeLoader::tr("Type %1 unavailable").arg(typeName));
2322 errors.prepend(error);
2323 setError(errors);
2324 return;
2325 }
2326 }
2327
2328 // Check all composite singleton type dependencies for errors
2329 for (int ii = 0; ii < m_compositeSingletons.count(); ++ii) {
2330 const TypeReference &type = m_compositeSingletons.at(ii);
2331 Q_ASSERT(!type.typeData || type.typeData->isCompleteOrError());
2332 if (type.typeData && type.typeData->isError()) {
2333 QString typeName = type.type.qmlTypeName();
2334
2335 QList<QQmlError> errors = type.typeData->errors();
2336 QQmlError error;
2337 error.setUrl(url());
2338 error.setLine(type.location.line);
2339 error.setColumn(type.location.column);
2340 error.setDescription(QQmlTypeLoader::tr("Type %1 unavailable").arg(typeName));
2341 errors.prepend(error);
2342 setError(errors);
2343 return;
2344 }
2345 }
2346
2347 QQmlRefPointer<QQmlTypeNameCache> typeNameCache;
2348 QV4::CompiledData::ResolvedTypeReferenceMap resolvedTypeCache;
2349 {
2350 QQmlCompileError error = buildTypeResolutionCaches(&typeNameCache, &resolvedTypeCache);
2351 if (error.isSet()) {
2352 setError(error);
2353 return;
2354 }
2355 }
2356
2357 QQmlEngine *const engine = typeLoader()->engine();
2358
2359 const auto dependencyHasher = [engine, &resolvedTypeCache, this](QCryptographicHash *hash) {
2360 if (!resolvedTypeCache.addToHash(hash, engine))
2361 return false;
2362 return ::addTypeReferenceChecksumsToHash(m_compositeSingletons, hash, engine);
2363 };
2364
2365 // verify if any dependencies changed if we're using a cache
2366 if (m_document.isNull() && !m_compiledData->verifyChecksum(dependencyHasher)) {
2367 qCDebug(DBG_DISK_CACHE) << "Checksum mismatch for cached version of" << m_compiledData->fileName();
2368 if (!loadFromSource())
2369 return;
2370 m_backupSourceCode = SourceCodeData();
2371 m_compiledData = nullptr;
2372 }
2373
2374 if (!m_document.isNull()) {
2375 // Compile component
2376 compile(typeNameCache, &resolvedTypeCache, dependencyHasher);
2377 } else {
2378 createTypeAndPropertyCaches(typeNameCache, resolvedTypeCache);
2379 }
2380
2381 if (isError())
2382 return;
2383
2384 {
2385 QQmlEnginePrivate *const enginePrivate = QQmlEnginePrivate::get(engine);
2386 {
2387 // Sanity check property bindings
2388 QQmlPropertyValidator validator(enginePrivate, m_importCache, m_compiledData);
2389 QVector<QQmlCompileError> errors = validator.validate();
2390 if (!errors.isEmpty()) {
2391 setError(errors);
2392 return;
2393 }
2394 }
2395
2396 m_compiledData->finalizeCompositeType(enginePrivate);
2397 }
2398
2399 {
2400 QQmlType type = QQmlMetaType::qmlType(finalUrl(), true);
2401 if (m_compiledData && m_compiledData->unitData()->flags & QV4::CompiledData::Unit::IsSingleton) {
2402 if (!type.isValid()) {
2403 QQmlError error;
2404 error.setDescription(QQmlTypeLoader::tr("No matching type found, pragma Singleton files cannot be used by QQmlComponent."));
2405 setError(error);
2406 return;
2407 } else if (!type.isCompositeSingleton()) {
2408 QQmlError error;
2409 error.setDescription(QQmlTypeLoader::tr("pragma Singleton used with a non composite singleton type %1").arg(type.qmlTypeName()));
2410 setError(error);
2411 return;
2412 }
2413 } else {
2414 // If the type is CompositeSingleton but there was no pragma Singleton in the
2415 // QML file, lets report an error.
2416 if (type.isValid() && type.isCompositeSingleton()) {
2417 QString typeName = type.qmlTypeName();
2418 setError(QQmlTypeLoader::tr("qmldir defines type as singleton, but no pragma Singleton found in type %1.").arg(typeName));
2419 return;
2420 }
2421 }
2422 }
2423
2424 {
2425 // Collect imported scripts
2426 m_compiledData->dependentScripts.reserve(m_scripts.count());
2427 for (int scriptIndex = 0; scriptIndex < m_scripts.count(); ++scriptIndex) {
2428 const QQmlTypeData::ScriptReference &script = m_scripts.at(scriptIndex);
2429
2430 QStringRef qualifier(&script.qualifier);
2431 QString enclosingNamespace;
2432
2433 const int lastDotIndex = qualifier.lastIndexOf(QLatin1Char('.'));
2434 if (lastDotIndex != -1) {
2435 enclosingNamespace = qualifier.left(lastDotIndex).toString();
2436 qualifier = qualifier.mid(lastDotIndex+1);
2437 }
2438
2439 m_compiledData->typeNameCache->add(qualifier.toString(), scriptIndex, enclosingNamespace);
2440 QQmlRefPointer<QQmlScriptData> scriptData = script.script->scriptData();
2441 m_compiledData->dependentScripts << scriptData;
2442 }
2443 }
2444}
2445
2446void QQmlTypeData::completed()
2447{
2448 // Notify callbacks
2449 while (!m_callbacks.isEmpty()) {
2450 TypeDataCallback *callback = m_callbacks.takeFirst();
2451 callback->typeDataReady(this);
2452 }
2453}
2454
2455bool QQmlTypeData::loadImplicitImport()
2456{
2457 m_implicitImportLoaded = true; // Even if we hit an error, count as loaded (we'd just keep hitting the error)
2458
2459 m_importCache.setBaseUrl(finalUrl(), finalUrlString());
2460
2461 QQmlImportDatabase *importDatabase = typeLoader()->importDatabase();
2462 // For local urls, add an implicit import "." as most overridden lookup.
2463 // This will also trigger the loading of the qmldir and the import of any native
2464 // types from available plugins.
2465 QList<QQmlError> implicitImportErrors;
2466 m_importCache.addImplicitImport(importDatabase, &implicitImportErrors);
2467
2468 if (!implicitImportErrors.isEmpty()) {
2469 setError(implicitImportErrors);
2470 return false;
2471 }
2472
2473 return true;
2474}
2475
2476void QQmlTypeData::dataReceived(const SourceCodeData &data)
2477{
2478 m_backupSourceCode = data;
2479
2480 if (tryLoadFromDiskCache())
2481 return;
2482
2483 if (isError())
2484 return;
2485
2486 if (!m_backupSourceCode.exists() || m_backupSourceCode.isEmpty()) {
2487 if (m_cachedUnitStatus == QQmlMetaType::CachedUnitLookupError::VersionMismatch)
2488 setError(QQmlTypeLoader::tr("File was compiled ahead of time with an incompatible version of Qt and the original file cannot be found. Please recompile"));
2489 else if (!m_backupSourceCode.exists())
2490 setError(QQmlTypeLoader::tr("No such file or directory"));
2491 else
2492 setError(QQmlTypeLoader::tr("File is empty"));
2493 return;
2494 }
2495
2496 if (!loadFromSource())
2497 return;
2498
2499 continueLoadFromIR();
2500}
2501
2502void QQmlTypeData::initializeFromCachedUnit(const QV4::CompiledData::Unit *unit)
2503{
2504 m_document.reset(new QmlIR::Document(isDebugging()));
2505 QmlIR::IRLoader loader(unit, m_document.data());
2506 loader.load();
2507 m_document->jsModule.fileName = urlString();
2508 m_document->jsModule.finalUrl = finalUrlString();
2509 m_document->javaScriptCompilationUnit.adopt(new QV4::CompiledData::CompilationUnit(unit));
2510 continueLoadFromIR();
2511}
2512
2513bool QQmlTypeData::loadFromSource()
2514{
2515 m_document.reset(new QmlIR::Document(isDebugging()));
2516 m_document->jsModule.sourceTimeStamp = m_backupSourceCode.sourceTimeStamp();
2517 QQmlEngine *qmlEngine = typeLoader()->engine();
2518 QmlIR::IRBuilder compiler(qmlEngine->handle()->v8Engine->illegalNames());
2519
2520 QString sourceError;
2521 const QString source = m_backupSourceCode.readAll(&sourceError);
2522 if (!sourceError.isEmpty()) {
2523 setError(sourceError);
2524 return false;
2525 }
2526
2527 if (!compiler.generateFromQml(source, finalUrlString(), m_document.data())) {
2528 QList<QQmlError> errors;
2529 errors.reserve(compiler.errors.count());
2530 for (const QQmlJS::DiagnosticMessage &msg : qAsConst(compiler.errors)) {
2531 QQmlError e;
2532 e.setUrl(url());
2533 e.setLine(msg.loc.startLine);
2534 e.setColumn(msg.loc.startColumn);
2535 e.setDescription(msg.message);
2536 errors << e;
2537 }
2538 setError(errors);
2539 return false;
2540 }
2541 return true;
2542}
2543
2544void QQmlTypeData::restoreIR(QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit)
2545{
2546 m_document.reset(new QmlIR::Document(isDebugging()));
2547 QmlIR::IRLoader loader(unit->unitData(), m_document.data());
2548 loader.load();
2549 m_document->jsModule.fileName = urlString();
2550 m_document->jsModule.finalUrl = finalUrlString();
2551 m_document->javaScriptCompilationUnit = unit;
2552 continueLoadFromIR();
2553}
2554
2555void QQmlTypeData::continueLoadFromIR()
2556{
2557 m_typeReferences.collectFromObjects(m_document->objects.constBegin(), m_document->objects.constEnd());
2558 m_importCache.setBaseUrl(finalUrl(), finalUrlString());
2559
2560 // For remote URLs, we don't delay the loading of the implicit import
2561 // because the loading probably requires an asynchronous fetch of the
2562 // qmldir (so we can't load it just in time).
2563 if (!finalUrl().scheme().isEmpty()) {
2564 QUrl qmldirUrl = finalUrl().resolved(QUrl(QLatin1String("qmldir")));
2565 if (!QQmlImports::isLocal(qmldirUrl)) {
2566 if (!loadImplicitImport())
2567 return;
2568 // This qmldir is for the implicit import
2569 QQmlJS::MemoryPool *pool = m_document->jsParserEngine.pool();
2570 auto implicitImport = pool->New<QV4::CompiledData::Import>();
2571 implicitImport->uriIndex = m_document->registerString(QLatin1String("."));
2572 implicitImport->qualifierIndex = 0; // empty string
2573 implicitImport->majorVersion = -1;
2574 implicitImport->minorVersion = -1;
2575 QList<QQmlError> errors;
2576
2577 if (!fetchQmldir(qmldirUrl, implicitImport, 1, &errors)) {
2578 setError(errors);
2579 return;
2580 }
2581 }
2582 }
2583
2584 QList<QQmlError> errors;
2585
2586 for (const QV4::CompiledData::Import *import : qAsConst(m_document->imports)) {
2587 if (!addImport(import, &errors)) {
2588 Q_ASSERT(errors.size());
2589 QQmlError error(errors.takeFirst());
2590 error.setUrl(m_importCache.baseUrl());
2591 error.setLine(import->location.line);
2592 error.setColumn(import->location.column);
2593 errors.prepend(error); // put it back on the list after filling out information.
2594 setError(errors);
2595 return;
2596 }
2597 }
2598}
2599
2600void QQmlTypeData::allDependenciesDone()
2601{
2602 QQmlTypeLoader::Blob::allDependenciesDone();
2603
2604 if (!m_typesResolved) {
2605 // Check that all imports were resolved
2606 QList<QQmlError> errors;
2607 QHash<const QV4::CompiledData::Import *, int>::const_iterator it = m_unresolvedImports.constBegin(), end = m_unresolvedImports.constEnd();
2608 for ( ; it != end; ++it) {
2609 if (*it == 0) {
2610 // This import was not resolved
2611 for (auto keyIt = m_unresolvedImports.keyBegin(),
2612 keyEnd = m_unresolvedImports.keyEnd();
2613 keyIt != keyEnd; ++keyIt) {
2614 const QV4::CompiledData::Import *import = *keyIt;
2615 QQmlError error;
2616 error.setDescription(QQmlTypeLoader::tr("module \"%1\" is not installed").arg(stringAt(import->uriIndex)));
2617 error.setUrl(m_importCache.baseUrl());
2618 error.setLine(import->location.line);
2619 error.setColumn(import->location.column);
2620 errors.prepend(error);
2621 }
2622 }
2623 }
2624 if (errors.size()) {
2625 setError(errors);
2626 return;
2627 }
2628
2629 resolveTypes();
2630 m_typesResolved = true;
2631 }
2632}
2633
2634void QQmlTypeData::downloadProgressChanged(qreal p)
2635{
2636 for (int ii = 0; ii < m_callbacks.count(); ++ii) {
2637 TypeDataCallback *callback = m_callbacks.at(ii);
2638 callback->typeDataProgress(this, p);
2639 }
2640}
2641
2642QString QQmlTypeData::stringAt(int index) const
2643{
2644 if (m_compiledData)
2645 return m_compiledData->stringAt(index);
2646 return m_document->jsGenerator.stringTable.stringForIndex(index);
2647}
2648
2649void QQmlTypeData::compile(const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache,
2650 QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache,
2651 const QV4::CompiledData::DependentTypesHasher &dependencyHasher)
2652{
2653 Q_ASSERT(m_compiledData.isNull());
2654
2655 const bool typeRecompilation = m_document && m_document->javaScriptCompilationUnit && m_document->javaScriptCompilationUnit->unitData()->flags & QV4::CompiledData::Unit::PendingTypeCompilation;
2656
2657 QQmlEnginePrivate * const enginePrivate = QQmlEnginePrivate::get(typeLoader()->engine());
2658 QQmlTypeCompiler compiler(enginePrivate, this, m_document.data(), typeNameCache, resolvedTypeCache, dependencyHasher);
2659 m_compiledData = compiler.compile();
2660 if (!m_compiledData) {
2661 setError(compiler.compilationErrors());
2662 return;
2663 }
2664
2665 const bool trySaveToDisk = (!disableDiskCache() || forceDiskCache()) && !m_document->jsModule.debugMode && !typeRecompilation;
2666 if (trySaveToDisk) {
2667 QString errorString;
2668 if (