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 }
1825#endif
1826
1827 int lastSlash = path.lastIndexOf(QLatin1Char('/'));
1828 QString dirPath(path.left(lastSlash));
1829
1830 LockHolder<QQmlTypeLoader> holder(this);
1831 if (!m_importDirCache.contains(dirPath)) {
1832 bool exists = QDir(dirPath).exists();
1833 QCache<QString, bool> *entry = exists ? new QCache<QString, bool> : nullptr;
1834 m_importDirCache.insert(dirPath, entry);
1835 }
1836 QCache<QString, bool> *fileSet = m_importDirCache.object(dirPath);
1837 if (!fileSet)
1838 return QString();
1839
1840 QString absoluteFilePath;
1841 QString fileName(path.mid(lastSlash+1, path.length()-lastSlash-1));
1842
1843 bool *value = fileSet->object(fileName);
1844 if (value) {
1845 if (*value)
1846 absoluteFilePath = path;
1847 } else {
1848 bool exists = QFile::exists(path);
1849 fileSet->insert(fileName, new bool(exists));
1850 if (exists)
1851 absoluteFilePath = path;
1852 }
1853
1854 if (absoluteFilePath.length() > 2 && absoluteFilePath.at(0) != QLatin1Char('/') && absoluteFilePath.at(1) != QLatin1Char(':'))
1855 absoluteFilePath = QFileInfo(absoluteFilePath).absoluteFilePath();
1856
1857 return absoluteFilePath;
1858}
1859
1860bool QQmlTypeLoader::fileExists(const QString &path, const QString &file)
1861{
1862 if (path.isEmpty())
1863 return false;
1864 Q_ASSERT(path.endsWith(QLatin1Char('/')));
1865 if (path.at(0) == QLatin1Char(':')) {
1866 // qrc resource
1867 QFileInfo fileInfo(path + file);
1868 return fileInfo.isFile();
1869 } else if (path.count() > 3 && path.at(3) == QLatin1Char(':') &&
1870 path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) {
1871 // qrc resource url
1872 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path + file));
1873 return fileInfo.isFile();
1874 }
1875#if defined(Q_OS_ANDROID)
1876 else if (path.count() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') &&
1877 path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) {
1878 // assets resource url
1879 QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path + file));
1880 return fileInfo.isFile();
1881 }
1882#endif
1883
1884 LockHolder<QQmlTypeLoader> holder(this);
1885 if (!m_importDirCache.contains(path)) {
1886 bool exists = QDir(path).exists();
1887 QCache<QString, bool> *entry = exists ? new QCache<QString, bool> : nullptr;
1888 m_importDirCache.insert(path, entry);
1889 }
1890 QCache<QString, bool> *fileSet = m_importDirCache.object(path);
1891 if (!fileSet)
1892 return false;
1893
1894 bool *value = fileSet->object(file);
1895 if (value) {
1896 return *value;
1897 } else {
1898 bool exists = QFile::exists(path + file);
1899 fileSet->insert(file, new bool(exists));
1900 return exists;
1901 }
1902}
1903
1904
1905/*!
1906Returns true if the path is a directory via a directory cache. Cache is
1907shared with absoluteFilePath().
1908*/
1909bool QQmlTypeLoader::directoryExists(const QString &path)
1910{
1911 if (path.isEmpty())
1912 return false;
1913
1914 bool isResource = path.at(0) == QLatin1Char(':');
1915#if defined(Q_OS_ANDROID)
1916 isResource = isResource || path.startsWith(QLatin1String("assets:/"));
1917#endif
1918
1919 if (isResource) {
1920 // qrc resource
1921 QFileInfo fileInfo(path);
1922 return fileInfo.exists() && fileInfo.isDir();
1923 }
1924
1925 int length = path.length();
1926 if (path.endsWith(QLatin1Char('/')))
1927 --length;
1928 QString dirPath(path.left(length));
1929
1930 LockHolder<QQmlTypeLoader> holder(this);
1931 if (!m_importDirCache.contains(dirPath)) {
1932 bool exists = QDir(dirPath).exists();
1933 QCache<QString, bool> *files = exists ? new QCache<QString, bool> : nullptr;
1934 m_importDirCache.insert(dirPath, files);
1935 }
1936
1937 QCache<QString, bool> *fileSet = m_importDirCache.object(dirPath);
1938 return fileSet != nullptr;
1939}
1940
1941
1942/*!
1943Return a QQmlTypeLoaderQmldirContent for absoluteFilePath. The QQmlTypeLoaderQmldirContent may be cached.
1944
1945\a filePath is a local file path.
1946
1947It can also be a remote path for a remote directory import, but it will have been cached by now in this case.
1948*/
1949const QQmlTypeLoaderQmldirContent QQmlTypeLoader::qmldirContent(const QString &filePathIn)
1950{
1951 LockHolder<QQmlTypeLoader> holder(this);
1952
1953 QString filePath;
1954
1955 // Try to guess if filePathIn is already a URL. This is necessarily fragile, because
1956 // - paths can contain ':', which might make them appear as URLs with schemes.
1957 // - windows drive letters appear as schemes (thus "< 2" below).
1958 // - a "file:" URL is equivalent to the respective file, but will be treated differently.
1959 // Yet, this heuristic is the best we can do until we pass more structured information here,
1960 // for example a QUrl also for local files.
1961 QUrl url(filePathIn);
1962 if (url.scheme().length() < 2) {
1963 filePath = filePathIn;
1964 } else {
1965 filePath = QQmlFile::urlToLocalFileOrQrc(url);
1966 if (filePath.isEmpty()) { // Can't load the remote here, but should be cached
1967 if (auto entry = m_importQmlDirCache.value(filePathIn))
1968 return **entry;
1969 else
1970 return QQmlTypeLoaderQmldirContent();
1971 }
1972 }
1973
1974 QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(filePath);
1975 if (val)
1976 return **val;
1977 QQmlTypeLoaderQmldirContent *qmldir = new QQmlTypeLoaderQmldirContent;
1978
1979#define ERROR(description) { QQmlError e; e.setDescription(description); qmldir->setError(e); }
1980#define NOT_READABLE_ERROR QString(QLatin1String("module \"$$URI$$\" definition \"%1\" not readable"))
1981#define CASE_MISMATCH_ERROR QString(QLatin1String("cannot load module \"$$URI$$\": File name case mismatch for \"%1\""))
1982
1983 QFile file(filePath);
1984 if (!QQml_isFileCaseCorrect(filePath)) {
1985 ERROR(CASE_MISMATCH_ERROR.arg(filePath));
1986 } else if (file.open(QFile::ReadOnly)) {
1987 QByteArray data = file.readAll();
1988 qmldir->setContent(filePath, QString::fromUtf8(data));
1989 } else {
1990 ERROR(NOT_READABLE_ERROR.arg(filePath));
1991 }
1992
1993#undef ERROR
1994#undef NOT_READABLE_ERROR
1995#undef CASE_MISMATCH_ERROR
1996
1997 m_importQmlDirCache.insert(filePath, qmldir);
1998 return *qmldir;
1999}
2000
2001void QQmlTypeLoader::setQmldirContent(const QString &url, const QString &content)
2002{
2003 QQmlTypeLoaderQmldirContent *qmldir;
2004 QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(url);
2005 if (val) {
2006 qmldir = *val;
2007 } else {
2008 qmldir = new QQmlTypeLoaderQmldirContent;
2009 m_importQmlDirCache.insert(url, qmldir);
2010 }
2011
2012 qmldir->setContent(url, content);
2013}
2014
2015/*!
2016Clears cached information about loaded files, including any type data, scripts
2017and qmldir information.
2018*/
2019void QQmlTypeLoader::clearCache()
2020{
2021 for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end; ++iter)
2022 (*iter)->release();
2023 for (ScriptCache::Iterator iter = m_scriptCache.begin(), end = m_scriptCache.end(); iter != end; ++iter)
2024 (*iter)->release();
2025 for (QmldirCache::Iterator iter = m_qmldirCache.begin(), end = m_qmldirCache.end(); iter != end; ++iter)
2026 (*iter)->release();
2027
2028 qDeleteAll(m_importQmlDirCache);
2029
2030 m_typeCache.clear();
2031 m_typeCacheTrimThreshold = TYPELOADER_MINIMUM_TRIM_THRESHOLD;
2032 m_scriptCache.clear();
2033 m_qmldirCache.clear();
2034 m_importDirCache.clear();
2035 m_importQmlDirCache.clear();
2036 QQmlMetaType::freeUnusedTypesAndCaches();
2037}
2038
2039void QQmlTypeLoader::updateTypeCacheTrimThreshold()
2040{
2041 int size = m_typeCache.size();
2042 if (size > m_typeCacheTrimThreshold)
2043 m_typeCacheTrimThreshold = size * 2;
2044 if (size < m_typeCacheTrimThreshold / 2)
2045 m_typeCacheTrimThreshold = qMax(size * 2, TYPELOADER_MINIMUM_TRIM_THRESHOLD);
2046}
2047
2048void QQmlTypeLoader::trimCache()
2049{
2050 while (true) {
2051 QList<TypeCache::Iterator> unneededTypes;
2052 for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end; ++iter) {
2053 QQmlTypeData *typeData = iter.value();
2054
2055 // typeData->m_compiledData may be set early on in the proccess of loading a file, so
2056 // it's important to check the general loading status of the typeData before making any
2057 // other decisions.
2058 if (typeData->count() == 1 && (typeData->isError() || typeData->isComplete())
2059 && (!typeData->m_compiledData || typeData->m_compiledData->count() == 1)) {
2060 // There are no live objects of this type
2061 unneededTypes.append(iter);
2062 }
2063 }
2064
2065 if (unneededTypes.isEmpty())
2066 break;
2067
2068 while (!unneededTypes.isEmpty()) {
2069 TypeCache::Iterator iter = unneededTypes.takeLast();
2070
2071 iter.value()->release();
2072 m_typeCache.erase(iter);
2073 }
2074 }
2075
2076 updateTypeCacheTrimThreshold();
2077
2078 QQmlMetaType::freeUnusedTypesAndCaches();
2079
2080 // TODO: release any scripts which are no longer referenced by any types
2081}
2082
2083bool QQmlTypeLoader::isTypeLoaded(const QUrl &url) const
2084{
2085 LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this));
2086 return m_typeCache.contains(url);
2087}
2088
2089bool QQmlTypeLoader::isScriptLoaded(const QUrl &url) const
2090{
2091 LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this));
2092 return m_scriptCache.contains(url);
2093}
2094
2095QQmlTypeData::TypeDataCallback::~TypeDataCallback()
2096{
2097}
2098
2099QString QQmlTypeData::TypeReference::qualifiedName() const
2100{
2101 QString result;
2102 if (!prefix.isEmpty()) {
2103 result = prefix + QLatin1Char('.');
2104 }
2105 result.append(type.qmlTypeName());
2106 return result;
2107}
2108
2109QQmlTypeData::QQmlTypeData(const QUrl &url, QQmlTypeLoader *manager)
2110: QQmlTypeLoader::Blob(url, QmlFile, manager),
2111 m_typesResolved(false), m_implicitImportLoaded(false)
2112{
2113
2114}
2115
2116QQmlTypeData::~QQmlTypeData()
2117{
2118 m_scripts.clear();
2119 m_compositeSingletons.clear();
2120 m_resolvedTypes.clear();
2121}
2122
2123const QList<QQmlTypeData::ScriptReference> &QQmlTypeData::resolvedScripts() const
2124{
2125 return m_scripts;
2126}
2127
2128QV4::CompiledData::CompilationUnit *QQmlTypeData::compilationUnit() const
2129{
2130 return m_compiledData.data();
2131}
2132
2133void QQmlTypeData::registerCallback(TypeDataCallback *callback)
2134{
2135 Q_ASSERT(!m_callbacks.contains(callback));
2136 m_callbacks.append(callback);
2137}
2138
2139void QQmlTypeData::unregisterCallback(TypeDataCallback *callback)
2140{
2141 Q_ASSERT(m_callbacks.contains(callback));
2142 m_callbacks.removeOne(callback);
2143 Q_ASSERT(!m_callbacks.contains(callback));
2144}
2145
2146bool QQmlTypeData::tryLoadFromDiskCache()
2147{
2148 if (disableDiskCache() && !forceDiskCache())
2149 return false;
2150
2151 if (isDebugging())
2152 return false;
2153
2154 QV4::ExecutionEngine *v4 = typeLoader()->engine()->handle();
2155 if (!v4)
2156 return false;
2157
2158 QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit = QV4::Compiler::Codegen::createUnitForLoading();
2159 {
2160 QString error;
2161 if (!unit->loadFromDisk(url(), m_backupSourceCode.sourceTimeStamp(), &error)) {
2162 qCDebug(DBG_DISK_CACHE) << "Error loading" << urlString() << "from disk cache:" << error;
2163 return false;
2164 }
2165 }
2166
2167 if (unit->unitData()->flags & QV4::CompiledData::Unit::PendingTypeCompilation) {
2168 restoreIR(unit);
2169 return true;
2170 }
2171
2172 m_compiledData = unit;
2173
2174 for (int i = 0, count = m_compiledData->objectCount(); i < count; ++i)
2175 m_typeReferences.collectFromObject(m_compiledData->objectAt(i));
2176
2177 m_importCache.setBaseUrl(finalUrl(), finalUrlString());
2178
2179 // For remote URLs, we don't delay the loading of the implicit import
2180 // because the loading probably requires an asynchronous fetch of the
2181 // qmldir (so we can't load it just in time).
2182 if (!finalUrl().scheme().isEmpty()) {
2183 QUrl qmldirUrl = finalUrl().resolved(QUrl(QLatin1String("qmldir")));
2184 if (!QQmlImports::isLocal(qmldirUrl)) {
2185 if (!loadImplicitImport())
2186 return false;
2187
2188 // find the implicit import
2189 for (quint32 i = 0, count = m_compiledData->importCount(); i < count; ++i) {
2190 const QV4::CompiledData::Import *import = m_compiledData->importAt(i);
2191 if (m_compiledData->stringAt(import->uriIndex) == QLatin1String(".")
2192 && import->qualifierIndex == 0
2193 && import->majorVersion == -1
2194 && import->minorVersion == -1) {
2195 QList<QQmlError> errors;
2196 if (!fetchQmldir(qmldirUrl, import, 1, &errors)) {
2197 setError(errors);
2198 return false;
2199 }
2200 break;
2201 }
2202 }
2203 }
2204 }
2205
2206 for (int i = 0, count = m_compiledData->importCount(); i < count; ++i) {
2207 const QV4::CompiledData::Import *import = m_compiledData->importAt(i);
2208 QList<QQmlError> errors;
2209 if (!addImport(import, &errors)) {
2210 Q_ASSERT(errors.size());
2211 QQmlError error(errors.takeFirst());
2212 error.setUrl(m_importCache.baseUrl());
2213 error.setLine(import->location.line);
2214 error.setColumn(import->location.column);
2215 errors.prepend(error); // put it back on the list after filling out information.
2216 setError(errors);
2217 return false;
2218 }
2219 }
2220
2221 return true;
2222}
2223
2224void QQmlTypeData::createTypeAndPropertyCaches(const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache,
2225 const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache)
2226{
2227 Q_ASSERT(m_compiledData);
2228 m_compiledData->typeNameCache = typeNameCache;
2229 m_compiledData->resolvedTypes = resolvedTypeCache;
2230
2231 QQmlEnginePrivate * const engine = QQmlEnginePrivate::get(typeLoader()->engine());
2232
2233 QQmlPendingGroupPropertyBindings pendingGroupPropertyBindings;
2234
2235 {
2236 QQmlPropertyCacheCreator<QV4::CompiledData::CompilationUnit> propertyCacheCreator(&m_compiledData->propertyCaches,
2237 &pendingGroupPropertyBindings,
2238 engine, m_compiledData.data(), &m_importCache);
2239 QQmlCompileError error = propertyCacheCreator.buildMetaObjects();
2240 if (error.isSet()) {
2241 setError(error);
2242 return;
2243 }
2244 }
2245
2246 QQmlPropertyCacheAliasCreator<QV4::CompiledData::CompilationUnit> aliasCreator(&m_compiledData->propertyCaches, m_compiledData.data());
2247 aliasCreator.appendAliasPropertiesToMetaObjects();
2248
2249 pendingGroupPropertyBindings.resolveMissingPropertyCaches(engine, &m_compiledData->propertyCaches);
2250}
2251
2252static bool addTypeReferenceChecksumsToHash(const QList<QQmlTypeData::TypeReference> &typeRefs, QCryptographicHash *hash, QQmlEngine *engine)
2253{
2254 for (const auto &typeRef: typeRefs) {
2255 if (typeRef.typeData) {
2256 const auto unit = typeRef.typeData->compilationUnit()->unitData();
2257 hash->addData(unit->md5Checksum, sizeof(unit->md5Checksum));
2258 } else if (typeRef.type.isValid()) {
2259 const auto propertyCache = QQmlEnginePrivate::get(engine)->cache(typeRef.type.metaObject());
2260 bool ok = false;
2261 hash->addData(propertyCache->checksum(&ok));
2262 if (!ok)
2263 return false;
2264 }
2265 }
2266 return true;
2267}
2268
2269void QQmlTypeData::done()
2270{
2271 auto cleanup = qScopeGuard([this]{
2272 m_document.reset();
2273 m_typeReferences.clear();
2274 if (isError())
2275 m_compiledData = nullptr;
2276 });
2277
2278 if (isError())
2279 return;
2280
2281 // Check all script dependencies for errors
2282 for (int ii = 0; ii < m_scripts.count(); ++ii) {
2283 const ScriptReference &script = m_scripts.at(ii);
2284 Q_ASSERT(script.script->isCompleteOrError());
2285 if (script.script->isError()) {
2286 QList<QQmlError> errors = script.script->errors();
2287 QQmlError error;
2288 error.setUrl(url());
2289 error.setLine(script.location.line);
2290 error.setColumn(script.location.column);
2291 error.setDescription(QQmlTypeLoader::tr("Script %1 unavailable").arg(script.script->urlString()));
2292 errors.prepend(error);
2293 setError(errors);
2294 return;
2295 }
2296 }
2297
2298 // Check all type dependencies for errors
2299 for (auto it = m_resolvedTypes.constBegin(), end = m_resolvedTypes.constEnd(); it != end;
2300 ++it) {
2301 const TypeReference &type = *it;
2302 Q_ASSERT(!type.typeData || type.typeData->isCompleteOrError());
2303 if (type.typeData && type.typeData->isError()) {
2304 const QString typeName = stringAt(it.key());
2305
2306 QList<QQmlError> errors = type.typeData->errors();
2307 QQmlError error;
2308 error.setUrl(url());
2309 error.setLine(type.location.line);
2310 error.setColumn(type.location.column);
2311 error.setDescription(QQmlTypeLoader::tr("Type %1 unavailable").arg(typeName));
2312 errors.prepend(error);
2313 setError(errors);
2314 return;
2315 }
2316 }
2317
2318 // Check all composite singleton type dependencies for errors
2319 for (int ii = 0; ii < m_compositeSingletons.count(); ++ii) {
2320 const TypeReference &type = m_compositeSingletons.at(ii);
2321 Q_ASSERT(!type.typeData || type.typeData->isCompleteOrError());
2322 if (type.typeData && type.typeData->isError()) {
2323 QString typeName = type.type.qmlTypeName();
2324
2325 QList<QQmlError> errors = type.typeData->errors();
2326 QQmlError error;
2327 error.setUrl(url());
2328 error.setLine(type.location.line);
2329 error.setColumn(type.location.column);
2330 error.setDescription(QQmlTypeLoader::tr("Type %1 unavailable").arg(typeName));
2331 errors.prepend(error);
2332 setError(errors);
2333 return;
2334 }
2335 }
2336
2337 QQmlRefPointer<QQmlTypeNameCache> typeNameCache;
2338 QV4::CompiledData::ResolvedTypeReferenceMap resolvedTypeCache;
2339 {
2340 QQmlCompileError error = buildTypeResolutionCaches(&typeNameCache, &resolvedTypeCache);
2341 if (error.isSet()) {
2342 setError(error);
2343 return;
2344 }
2345 }
2346
2347 QQmlEngine *const engine = typeLoader()->engine();
2348
2349 const auto dependencyHasher = [engine, resolvedTypeCache, this](QCryptographicHash *hash) {
2350 if (!resolvedTypeCache.addToHash(hash, engine))
2351 return false;
2352 return ::addTypeReferenceChecksumsToHash(m_compositeSingletons, hash, engine);
2353 };
2354
2355 // verify if any dependencies changed if we're using a cache
2356 if (m_document.isNull() && !m_compiledData->verifyChecksum(dependencyHasher)) {
2357 qCDebug(DBG_DISK_CACHE) << "Checksum mismatch for cached version of" << m_compiledData->fileName();
2358 if (!loadFromSource())
2359 return;
2360 m_backupSourceCode = SourceCodeData();
2361 m_compiledData = nullptr;
2362 }
2363
2364 if (!m_document.isNull()) {
2365 // Compile component
2366 compile(typeNameCache, resolvedTypeCache, dependencyHasher);
2367 } else {
2368 createTypeAndPropertyCaches(typeNameCache, resolvedTypeCache);
2369 }
2370
2371 if (isError())
2372 return;
2373
2374 {
2375 QQmlEnginePrivate *const enginePrivate = QQmlEnginePrivate::get(engine);
2376 {
2377 // Sanity check property bindings
2378 QQmlPropertyValidator validator(enginePrivate, m_importCache, m_compiledData);
2379 QVector<QQmlCompileError> errors = validator.validate();
2380 if (!errors.isEmpty()) {
2381 setError(errors);
2382 return;
2383 }
2384 }
2385
2386 m_compiledData->finalizeCompositeType(enginePrivate);
2387 }
2388
2389 {
2390 QQmlType type = QQmlMetaType::qmlType(finalUrl(), true);
2391 if (m_compiledData && m_compiledData->unitData()->flags & QV4::CompiledData::Unit::IsSingleton) {
2392 if (!type.isValid()) {
2393 QQmlError error;
2394 error.setDescription(QQmlTypeLoader::tr("No matching type found, pragma Singleton files cannot be used by QQmlComponent."));
2395 setError(error);
2396 return;
2397 } else if (!type.isCompositeSingleton()) {
2398 QQmlError error;
2399 error.setDescription(QQmlTypeLoader::tr("pragma Singleton used with a non composite singleton type %1").arg(type.qmlTypeName()));
2400 setError(error);
2401 return;
2402 }
2403 } else {
2404 // If the type is CompositeSingleton but there was no pragma Singleton in the
2405 // QML file, lets report an error.
2406 if (type.isValid() && type.isCompositeSingleton()) {
2407 QString typeName = type.qmlTypeName();
2408 setError(QQmlTypeLoader::tr("qmldir defines type as singleton, but no pragma Singleton found in type %1.").arg(typeName));
2409 return;
2410 }
2411 }
2412 }
2413
2414 {
2415 // Collect imported scripts
2416 m_compiledData->dependentScripts.reserve(m_scripts.count());
2417 for (int scriptIndex = 0; scriptIndex < m_scripts.count(); ++scriptIndex) {
2418 const QQmlTypeData::ScriptReference &script = m_scripts.at(scriptIndex);
2419
2420 QStringRef qualifier(&script.qualifier);
2421 QString enclosingNamespace;
2422
2423 const int lastDotIndex = qualifier.lastIndexOf(QLatin1Char('.'));
2424 if (lastDotIndex != -1) {
2425 enclosingNamespace = qualifier.left(lastDotIndex).toString();
2426 qualifier = qualifier.mid(lastDotIndex+1);
2427 }
2428
2429 m_compiledData->typeNameCache->add(qualifier.toString(), scriptIndex, enclosingNamespace);
2430 QQmlRefPointer<QQmlScriptData> scriptData = script.script->scriptData();
2431 m_compiledData->dependentScripts << scriptData;
2432 }
2433 }
2434}
2435
2436void QQmlTypeData::completed()
2437{
2438 // Notify callbacks
2439 while (!m_callbacks.isEmpty()) {
2440 TypeDataCallback *callback = m_callbacks.takeFirst();
2441 callback->typeDataReady(this);
2442 }
2443}
2444
2445bool QQmlTypeData::loadImplicitImport()
2446{
2447 m_implicitImportLoaded = true; // Even if we hit an error, count as loaded (we'd just keep hitting the error)
2448
2449 m_importCache.setBaseUrl(finalUrl(), finalUrlString());
2450
2451 QQmlImportDatabase *importDatabase = typeLoader()->importDatabase();
2452 // For local urls, add an implicit import "." as most overridden lookup.
2453 // This will also trigger the loading of the qmldir and the import of any native
2454 // types from available plugins.
2455 QList<QQmlError> implicitImportErrors;
2456 m_importCache.addImplicitImport(importDatabase, &implicitImportErrors);
2457
2458 if (!implicitImportErrors.isEmpty()) {
2459 setError(implicitImportErrors);
2460 return false;
2461 }
2462
2463 return true;
2464}
2465
2466void QQmlTypeData::dataReceived(const SourceCodeData &data)
2467{
2468 m_backupSourceCode = data;
2469
2470 if (tryLoadFromDiskCache())
2471 return;
2472
2473 if (isError())
2474 return;
2475
2476 if (!m_backupSourceCode.exists() || m_backupSourceCode.isEmpty()) {
2477 if (m_cachedUnitStatus == QQmlMetaType::CachedUnitLookupError::VersionMismatch)
2478 setError(QQmlTypeLoader::tr("File was compiled ahead of time with an incompatible version of Qt and the original file cannot be found. Please recompile"));
2479 else if (!m_backupSourceCode.exists())
2480 setError(QQmlTypeLoader::tr("No such file or directory"));
2481 else
2482 setError(QQmlTypeLoader::tr("File is empty"));
2483 return;
2484 }
2485
2486 if (!loadFromSource())
2487 return;
2488
2489 continueLoadFromIR();
2490}
2491
2492void QQmlTypeData::initializeFromCachedUnit(const QV4::CompiledData::Unit *unit)
2493{
2494 m_document.reset(new QmlIR::Document(isDebugging()));
2495 QmlIR::IRLoader loader(unit, m_document.data());
2496 loader.load();
2497 m_document->jsModule.fileName = urlString();
2498 m_document->jsModule.finalUrl = finalUrlString();
2499 m_document->javaScriptCompilationUnit.adopt(new QV4::CompiledData::CompilationUnit(unit));
2500 continueLoadFromIR();
2501}
2502
2503bool QQmlTypeData::loadFromSource()
2504{
2505 m_document.reset(new QmlIR::Document(isDebugging()));
2506 m_document->jsModule.sourceTimeStamp = m_backupSourceCode.sourceTimeStamp();
2507 QQmlEngine *qmlEngine = typeLoader()->engine();
2508 QmlIR::IRBuilder compiler(qmlEngine->handle()->v8Engine->illegalNames());
2509
2510 QString sourceError;
2511 const QString source = m_backupSourceCode.readAll(&sourceError);
2512 if (!sourceError.isEmpty()) {
2513 setError(sourceError);
2514 return false;
2515 }
2516
2517 if (!compiler.generateFromQml(source, finalUrlString(), m_document.data())) {
2518 QList<QQmlError> errors;
2519 errors.reserve(compiler.errors.count());
2520 for (const QQmlJS::DiagnosticMessage &msg : qAsConst(compiler.errors)) {
2521 QQmlError e;
2522 e.setUrl(url());
2523 e.setLine(msg.loc.startLine);
2524 e.setColumn(msg.loc.startColumn);
2525 e.setDescription(msg.message);
2526 errors << e;
2527 }
2528 setError(errors);
2529 return false;
2530 }
2531 return true;
2532}
2533
2534void QQmlTypeData::restoreIR(QQmlRefPointer<QV4::CompiledData::CompilationUnit> unit)
2535{
2536 m_document.reset(new QmlIR::Document(isDebugging()));
2537 QmlIR::IRLoader loader(unit->unitData(), m_document.data());
2538 loader.load();
2539 m_document->jsModule.fileName = urlString();
2540 m_document->jsModule.finalUrl = finalUrlString();
2541 m_document->javaScriptCompilationUnit = unit;
2542 continueLoadFromIR();
2543}
2544
2545void QQmlTypeData::continueLoadFromIR()
2546{
2547 m_typeReferences.collectFromObjects(m_document->objects.constBegin(), m_document->objects.constEnd());
2548 m_importCache.setBaseUrl(finalUrl(), finalUrlString());
2549
2550 // For remote URLs, we don't delay the loading of the implicit import
2551 // because the loading probably requires an asynchronous fetch of the
2552 // qmldir (so we can't load it just in time).
2553 if (!finalUrl().scheme().isEmpty()) {
2554 QUrl qmldirUrl = finalUrl().resolved(QUrl(QLatin1String("qmldir")));
2555 if (!QQmlImports::isLocal(qmldirUrl)) {
2556 if (!loadImplicitImport())
2557 return;
2558 // This qmldir is for the implicit import
2559 QQmlJS::MemoryPool *pool = m_document->jsParserEngine.pool();
2560 auto implicitImport = pool->New<QV4::CompiledData::Import>();
2561 implicitImport->uriIndex = m_document->registerString(QLatin1String("."));
2562 implicitImport->qualifierIndex = 0; // empty string
2563 implicitImport->majorVersion = -1;
2564 implicitImport->minorVersion = -1;
2565 QList<QQmlError> errors;
2566
2567 if (!fetchQmldir(qmldirUrl, implicitImport, 1, &errors)) {
2568 setError(errors);
2569 return;
2570 }
2571 }
2572 }
2573
2574 QList<QQmlError> errors;
2575
2576 for (const QV4::CompiledData::Import *import : qAsConst(m_document->imports)) {
2577 if (!addImport(import, &errors)) {
2578 Q_ASSERT(errors.size());
2579 QQmlError error(errors.takeFirst());
2580 error.setUrl(m_importCache.baseUrl());
2581 error.setLine(import->location.line);
2582 error.setColumn(import->location.column);
2583 errors.prepend(error); // put it back on the list after filling out information.
2584 setError(errors);
2585 return;
2586 }
2587 }
2588}
2589
2590void QQmlTypeData::allDependenciesDone()
2591{
2592 QQmlTypeLoader::Blob::allDependenciesDone();
2593
2594 if (!m_typesResolved) {
2595 // Check that all imports were resolved
2596 QList<QQmlError> errors;
2597 QHash<const QV4::CompiledData::Import *, int>::const_iterator it = m_unresolvedImports.constBegin(), end = m_unresolvedImports.constEnd();
2598 for ( ; it != end; ++it) {
2599 if (*it == 0) {
2600 // This import was not resolved
2601 for (auto keyIt = m_unresolvedImports.keyBegin(),
2602 keyEnd = m_unresolvedImports.keyEnd();
2603 keyIt != keyEnd; ++keyIt) {
2604 const QV4::CompiledData::Import *import = *keyIt;
2605 QQmlError error;
2606 error.setDescription(QQmlTypeLoader::tr("module \"%1\" is not installed").arg(stringAt(import->uriIndex)));
2607 error.setUrl(m_importCache.baseUrl());
2608 error.setLine(import->location.line);
2609 error.setColumn(import->location.column);
2610 errors.prepend(error);
2611 }
2612 }
2613 }
2614 if (errors.size()) {
2615 setError(errors);
2616 return;
2617 }
2618
2619 resolveTypes();
2620 m_typesResolved = true;
2621 }
2622}
2623
2624void QQmlTypeData::downloadProgressChanged(qreal p)
2625{
2626 for (int ii = 0; ii < m_callbacks.count(); ++ii) {
2627 TypeDataCallback *callback = m_callbacks.at(ii);
2628 callback->typeDataProgress(this, p);
2629 }
2630}
2631
2632QString QQmlTypeData::stringAt(int index) const
2633{
2634 if (m_compiledData)
2635 return m_compiledData->stringAt(index);
2636 return m_document->jsGenerator.stringTable.stringForIndex(index);
2637}
2638
2639void QQmlTypeData::compile(const QQmlRefPointer<QQmlTypeNameCache> &typeNameCache, const QV4::CompiledData::ResolvedTypeReferenceMap &resolvedTypeCache,
2640 const QV4::CompiledData::DependentTypesHasher &dependencyHasher)
2641{
2642 Q_ASSERT(m_compiledData.isNull());
2643
2644 const bool typeRecompilation = m_document && m_document->javaScriptCompilationUnit && m_document->javaScriptCompilationUnit->unitData()->flags & QV4::CompiledData::Unit::PendingTypeCompilation;
2645
2646 QQmlEnginePrivate * const enginePrivate = QQmlEnginePrivate::get(typeLoader()->engine());
2647 QQmlTypeCompiler compiler(enginePrivate, this, m_document.data(), typeNameCache, resolvedTypeCache, dependencyHasher);
2648 m_compiledData = compiler.compile();
2649 if (!m_compiledData) {
2650 setError(compiler.compilationErrors());
2651 return;
2652 }
2653
2654 const bool trySaveToDisk = (!disableDiskCache() || forceDiskCache()) && !m_document->jsModule.debugMode && !typeRecompilation;
2655 if (trySaveToDisk) {
2656 QString errorString;
2657 if (m_compiledData->saveToDisk(url(), &errorString)) {
2658