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 <private/qqmltypeloader_p.h>
41 
42 #include <private/qqmldirdata_p.h>
43 #include <private/qqmlprofiler_p.h>
44 #include <private/qqmlscriptblob_p.h>
45 #include <private/qqmltypedata_p.h>
46 #include <private/qqmltypeloaderqmldircontent_p.h>
47 #include <private/qqmltypeloaderthread_p.h>
48 #include <private/qqmlsourcecoordinate_p.h>
49 
50 #include <QtQml/qqmlabstracturlinterceptor.h>
51 #include <QtQml/qqmlengine.h>
52 #include <QtQml/qqmlextensioninterface.h>
53 #include <QtQml/qqmlfile.h>
54 
55 #include <qtqml_tracepoints_p.h>
56 
57 #include <QtCore/qdir.h>
58 #include <QtCore/qdiriterator.h>
59 #include <QtCore/qfile.h>
60 #include <QtCore/qthread.h>
61 
62 #include <functional>
63 
64 // #define DATABLOB_DEBUG
65 #ifdef DATABLOB_DEBUG
66 #define ASSERT_LOADTHREAD() do { if (!m_thread->isThisThread()) qFatal("QQmlTypeLoader: Caller not in load thread"); } while (false)
67 #else
68 #define ASSERT_LOADTHREAD()
69 #endif
70 
71 DEFINE_BOOL_CONFIG_OPTION(disableDiskCache, QML_DISABLE_DISK_CACHE);
72 DEFINE_BOOL_CONFIG_OPTION(forceDiskCache, QML_FORCE_DISK_CACHE);
73 
74 QT_BEGIN_NAMESPACE
75 
76 namespace {
77 
78     template<typename LockType>
79     struct LockHolder
80     {
81         LockType& lock;
LockHolder__anon66fc73b20111::LockHolder82         LockHolder(LockType *l) : lock(*l) { lock.lock(); }
~LockHolder__anon66fc73b20111::LockHolder83         ~LockHolder() { lock.unlock(); }
84     };
85 }
86 
87 /*!
88 \class QQmlTypeLoader
89 \brief The QQmlTypeLoader class abstracts loading files and their dependencies over the network.
90 \internal
91 
92 The QQmlTypeLoader class is provided for the exclusive use of the QQmlTypeLoader class.
93 
94 Clients create QQmlDataBlob instances and submit them to the QQmlTypeLoader class
95 through the QQmlTypeLoader::load() or QQmlTypeLoader::loadWithStaticData() methods.
96 The loader then fetches the data over the network or from the local file system in an efficient way.
97 QQmlDataBlob is an abstract class, so should always be specialized.
98 
99 Once data is received, the QQmlDataBlob::dataReceived() method is invoked on the blob.  The
100 derived class should use this callback to process the received data.  Processing of the data can
101 result in an error being set (QQmlDataBlob::setError()), or one or more dependencies being
102 created (QQmlDataBlob::addDependency()).  Dependencies are other QQmlDataBlob's that
103 are required before processing can fully complete.
104 
105 To complete processing, the QQmlDataBlob::done() callback is invoked.  done() is called when
106 one of these three preconditions are met.
107 
108 \list 1
109 \li The QQmlDataBlob has no dependencies.
110 \li The QQmlDataBlob has an error set.
111 \li All the QQmlDataBlob's dependencies are themselves "done()".
112 \endlist
113 
114 Thus QQmlDataBlob::done() will always eventually be called, even if the blob has an error set.
115 */
116 
invalidate()117 void QQmlTypeLoader::invalidate()
118 {
119     if (m_thread) {
120         shutdownThread();
121         delete m_thread;
122         m_thread = nullptr;
123     }
124 
125 #if QT_CONFIG(qml_network)
126     // Need to delete the network replies after
127     // the loader thread is shutdown as it could be
128     // getting new replies while we clear them
129     for (NetworkReplies::Iterator iter = m_networkReplies.begin(); iter != m_networkReplies.end(); ++iter)
130         (*iter)->release();
131     m_networkReplies.clear();
132 #endif // qml_network
133 }
134 
135 #if QT_CONFIG(qml_debug)
setProfiler(QQmlProfiler * profiler)136 void QQmlTypeLoader::setProfiler(QQmlProfiler *profiler)
137 {
138     Q_ASSERT(!m_profiler);
139     m_profiler.reset(profiler);
140 }
141 #endif
142 
143 struct PlainLoader {
loadThreadPlainLoader144     void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
145     {
146         loader->loadThread(blob);
147     }
loadPlainLoader148     void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
149     {
150         loader->m_thread->load(blob);
151     }
loadAsyncPlainLoader152     void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
153     {
154         loader->m_thread->loadAsync(blob);
155     }
156 };
157 
158 struct StaticLoader {
159     const QByteArray &data;
StaticLoaderStaticLoader160     StaticLoader(const QByteArray &data) : data(data) {}
161 
loadThreadStaticLoader162     void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
163     {
164         loader->loadWithStaticDataThread(blob, data);
165     }
loadStaticLoader166     void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
167     {
168         loader->m_thread->loadWithStaticData(blob, data);
169     }
loadAsyncStaticLoader170     void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
171     {
172         loader->m_thread->loadWithStaticDataAsync(blob, data);
173     }
174 };
175 
176 struct CachedLoader {
177     const QV4::CompiledData::Unit *unit;
CachedLoaderCachedLoader178     CachedLoader(const QV4::CompiledData::Unit *unit) :  unit(unit) {}
179 
loadThreadCachedLoader180     void loadThread(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
181     {
182         loader->loadWithCachedUnitThread(blob, unit);
183     }
loadCachedLoader184     void load(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
185     {
186         loader->m_thread->loadWithCachedUnit(blob, unit);
187     }
loadAsyncCachedLoader188     void loadAsync(QQmlTypeLoader *loader, QQmlDataBlob *blob) const
189     {
190         loader->m_thread->loadWithCachedUnitAsync(blob, unit);
191     }
192 };
193 
194 template<typename Loader>
doLoad(const Loader & loader,QQmlDataBlob * blob,Mode mode)195 void QQmlTypeLoader::doLoad(const Loader &loader, QQmlDataBlob *blob, Mode mode)
196 {
197 #ifdef DATABLOB_DEBUG
198     qWarning("QQmlTypeLoader::doLoad(%s): %s thread", qPrintable(blob->urlString()),
199              m_thread->isThisThread()?"Compile":"Engine");
200 #endif
201     blob->startLoading();
202 
203     if (m_thread->isThisThread()) {
204         unlock();
205         loader.loadThread(this, blob);
206         lock();
207     } else if (mode == Asynchronous) {
208         blob->m_data.setIsAsync(true);
209         unlock();
210         loader.loadAsync(this, blob);
211         lock();
212     } else {
213         unlock();
214         loader.load(this, blob);
215         lock();
216         if (mode == PreferSynchronous) {
217             if (!blob->isCompleteOrError())
218                 blob->m_data.setIsAsync(true);
219         } else {
220             Q_ASSERT(mode == Synchronous);
221             while (!blob->isCompleteOrError()) {
222                 unlock();
223                 m_thread->waitForNextMessage();
224                 lock();
225             }
226         }
227     }
228 }
229 
230 /*!
231 Load the provided \a blob from the network or filesystem.
232 
233 The loader must be locked.
234 */
load(QQmlDataBlob * blob,Mode mode)235 void QQmlTypeLoader::load(QQmlDataBlob *blob, Mode mode)
236 {
237     doLoad(PlainLoader(), blob, mode);
238 }
239 
240 /*!
241 Load the provided \a blob with \a data.  The blob's URL is not used by the data loader in this case.
242 
243 The loader must be locked.
244 */
loadWithStaticData(QQmlDataBlob * blob,const QByteArray & data,Mode mode)245 void QQmlTypeLoader::loadWithStaticData(QQmlDataBlob *blob, const QByteArray &data, Mode mode)
246 {
247     doLoad(StaticLoader(data), blob, mode);
248 }
249 
loadWithCachedUnit(QQmlDataBlob * blob,const QV4::CompiledData::Unit * unit,Mode mode)250 void QQmlTypeLoader::loadWithCachedUnit(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit, Mode mode)
251 {
252     doLoad(CachedLoader(unit), blob, mode);
253 }
254 
loadWithStaticDataThread(QQmlDataBlob * blob,const QByteArray & data)255 void QQmlTypeLoader::loadWithStaticDataThread(QQmlDataBlob *blob, const QByteArray &data)
256 {
257     ASSERT_LOADTHREAD();
258 
259     setData(blob, data);
260 }
261 
loadWithCachedUnitThread(QQmlDataBlob * blob,const QV4::CompiledData::Unit * unit)262 void QQmlTypeLoader::loadWithCachedUnitThread(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit)
263 {
264     ASSERT_LOADTHREAD();
265 
266     setCachedUnit(blob, unit);
267 }
268 
loadThread(QQmlDataBlob * blob)269 void QQmlTypeLoader::loadThread(QQmlDataBlob *blob)
270 {
271     ASSERT_LOADTHREAD();
272 
273     // Don't continue loading if we've been shutdown
274     if (m_thread->isShutdown()) {
275         QQmlError error;
276         error.setDescription(QLatin1String("Interrupted by shutdown"));
277         blob->setError(error);
278         return;
279     }
280 
281     if (blob->m_url.isEmpty()) {
282         QQmlError error;
283         error.setDescription(QLatin1String("Invalid null URL"));
284         blob->setError(error);
285         return;
286     }
287 
288     if (QQmlFile::isSynchronous(blob->m_url)) {
289         const QString fileName = QQmlFile::urlToLocalFileOrQrc(blob->m_url);
290         if (!QQml_isFileCaseCorrect(fileName)) {
291             blob->setError(QLatin1String("File name case mismatch"));
292             return;
293         }
294 
295         blob->m_data.setProgress(0xFF);
296         if (blob->m_data.isAsync())
297             m_thread->callDownloadProgressChanged(blob, 1.);
298 
299         setData(blob, fileName);
300 
301     } else {
302 #if QT_CONFIG(qml_network)
303         QNetworkReply *reply = m_thread->networkAccessManager()->get(QNetworkRequest(blob->m_url));
304         QQmlTypeLoaderNetworkReplyProxy *nrp = m_thread->networkReplyProxy();
305         blob->addref();
306         m_networkReplies.insert(reply, blob);
307 
308         if (reply->isFinished()) {
309             nrp->manualFinished(reply);
310         } else {
311             QObject::connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
312                              nrp, SLOT(downloadProgress(qint64,qint64)));
313             QObject::connect(reply, SIGNAL(finished()),
314                              nrp, SLOT(finished()));
315         }
316 
317 #ifdef DATABLOB_DEBUG
318         qWarning("QQmlDataBlob: requested %s", qPrintable(blob->urlString()));
319 #endif // DATABLOB_DEBUG
320 #endif // qml_network
321     }
322 }
323 
324 #define DATALOADER_MAXIMUM_REDIRECT_RECURSION 16
325 
326 #ifndef TYPELOADER_MINIMUM_TRIM_THRESHOLD
327 #define TYPELOADER_MINIMUM_TRIM_THRESHOLD 64
328 #endif
329 
330 #if QT_CONFIG(qml_network)
networkReplyFinished(QNetworkReply * reply)331 void QQmlTypeLoader::networkReplyFinished(QNetworkReply *reply)
332 {
333     Q_ASSERT(m_thread->isThisThread());
334 
335     reply->deleteLater();
336 
337     QQmlDataBlob *blob = m_networkReplies.take(reply);
338 
339     Q_ASSERT(blob);
340 
341     blob->m_redirectCount++;
342 
343     if (blob->m_redirectCount < DATALOADER_MAXIMUM_REDIRECT_RECURSION) {
344         QVariant redirect = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
345         if (redirect.isValid()) {
346             QUrl url = reply->url().resolved(redirect.toUrl());
347             blob->m_finalUrl = url;
348             blob->m_finalUrlString.clear();
349 
350             QNetworkReply *reply = m_thread->networkAccessManager()->get(QNetworkRequest(url));
351             QObject *nrp = m_thread->networkReplyProxy();
352             QObject::connect(reply, SIGNAL(finished()), nrp, SLOT(finished()));
353             m_networkReplies.insert(reply, blob);
354 #ifdef DATABLOB_DEBUG
355             qWarning("QQmlDataBlob: redirected to %s", qPrintable(blob->finalUrlString()));
356 #endif
357             return;
358         }
359     }
360 
361     if (reply->error()) {
362         blob->networkError(reply->error());
363     } else {
364         QByteArray data = reply->readAll();
365         setData(blob, data);
366     }
367 
368     blob->release();
369 }
370 
networkReplyProgress(QNetworkReply * reply,qint64 bytesReceived,qint64 bytesTotal)371 void QQmlTypeLoader::networkReplyProgress(QNetworkReply *reply,
372                                                   qint64 bytesReceived, qint64 bytesTotal)
373 {
374     Q_ASSERT(m_thread->isThisThread());
375 
376     QQmlDataBlob *blob = m_networkReplies.value(reply);
377 
378     Q_ASSERT(blob);
379 
380     if (bytesTotal != 0) {
381         quint8 progress = 0xFF * (qreal(bytesReceived) / qreal(bytesTotal));
382         blob->m_data.setProgress(progress);
383         if (blob->m_data.isAsync())
384             m_thread->callDownloadProgressChanged(blob, blob->m_data.progress());
385     }
386 }
387 #endif // qml_network
388 
389 /*!
390 Return the QQmlEngine associated with this loader
391 */
engine() const392 QQmlEngine *QQmlTypeLoader::engine() const
393 {
394     return m_engine;
395 }
396 
397 /*! \internal
398 Call the initializeEngine() method on \a iface.  Used by QQmlImportDatabase to ensure it
399 gets called in the correct thread.
400 */
401 template<class Interface>
doInitializeEngine(Interface * iface,QQmlTypeLoaderThread * thread,QQmlEngine * engine,const char * uri)402 void doInitializeEngine(Interface *iface, QQmlTypeLoaderThread *thread, QQmlEngine *engine,
403                       const char *uri)
404 {
405     Q_ASSERT(thread->isThisThread() || engine->thread() == QThread::currentThread());
406 
407     if (thread->isThisThread()) {
408         thread->initializeEngine(iface, uri);
409     } else {
410         Q_ASSERT(engine->thread() == QThread::currentThread());
411         iface->initializeEngine(engine, uri);
412     }
413 }
414 
initializeEngine(QQmlEngineExtensionInterface * iface,const char * uri)415 void QQmlTypeLoader::initializeEngine(QQmlEngineExtensionInterface *iface, const char *uri)
416 {
417     doInitializeEngine(iface, m_thread, engine(), uri);
418 }
419 
initializeEngine(QQmlExtensionInterface * iface,const char * uri)420 void QQmlTypeLoader::initializeEngine(QQmlExtensionInterface *iface, const char *uri)
421 {
422     doInitializeEngine(iface, m_thread, engine(), uri);
423 }
424 
setData(QQmlDataBlob * blob,const QByteArray & data)425 void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QByteArray &data)
426 {
427     QQmlDataBlob::SourceCodeData d;
428     d.inlineSourceCode = QString::fromUtf8(data);
429     d.hasInlineSourceCode = true;
430     setData(blob, d);
431 }
432 
setData(QQmlDataBlob * blob,const QString & fileName)433 void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QString &fileName)
434 {
435     QQmlDataBlob::SourceCodeData d;
436     d.fileInfo = QFileInfo(fileName);
437     setData(blob, d);
438 }
439 
setData(QQmlDataBlob * blob,const QQmlDataBlob::SourceCodeData & d)440 void QQmlTypeLoader::setData(QQmlDataBlob *blob, const QQmlDataBlob::SourceCodeData &d)
441 {
442     Q_TRACE_SCOPE(QQmlCompiling, blob->url());
443     QQmlCompilingProfiler prof(profiler(), blob);
444 
445     blob->m_inCallback = true;
446 
447     blob->dataReceived(d);
448 
449     if (!blob->isError() && !blob->isWaiting())
450         blob->allDependenciesDone();
451 
452     if (blob->status() != QQmlDataBlob::Error)
453         blob->m_data.setStatus(QQmlDataBlob::WaitingForDependencies);
454 
455     blob->m_inCallback = false;
456 
457     blob->tryDone();
458 }
459 
setCachedUnit(QQmlDataBlob * blob,const QV4::CompiledData::Unit * unit)460 void QQmlTypeLoader::setCachedUnit(QQmlDataBlob *blob, const QV4::CompiledData::Unit *unit)
461 {
462     Q_TRACE_SCOPE(QQmlCompiling, blob->url());
463     QQmlCompilingProfiler prof(profiler(), blob);
464 
465     blob->m_inCallback = true;
466 
467     blob->initializeFromCachedUnit(unit);
468 
469     if (!blob->isError() && !blob->isWaiting())
470         blob->allDependenciesDone();
471 
472     if (blob->status() != QQmlDataBlob::Error)
473         blob->m_data.setStatus(QQmlDataBlob::WaitingForDependencies);
474 
475     blob->m_inCallback = false;
476 
477     blob->tryDone();
478 }
479 
shutdownThread()480 void QQmlTypeLoader::shutdownThread()
481 {
482     if (m_thread && !m_thread->isShutdown())
483         m_thread->shutdown();
484 }
485 
PendingImport(QQmlTypeLoader::Blob * blob,const QV4::CompiledData::Import * import)486 QQmlTypeLoader::Blob::PendingImport::PendingImport(QQmlTypeLoader::Blob *blob, const QV4::CompiledData::Import *import)
487 {
488     type = static_cast<QV4::CompiledData::Import::ImportType>(quint32(import->type));
489     uri = blob->stringAt(import->uriIndex);
490     qualifier = blob->stringAt(import->qualifierIndex);
491     majorVersion = import->majorVersion;
492     minorVersion = import->minorVersion;
493     location = import->location;
494 }
495 
Blob(const QUrl & url,QQmlDataBlob::Type type,QQmlTypeLoader * loader)496 QQmlTypeLoader::Blob::Blob(const QUrl &url, QQmlDataBlob::Type type, QQmlTypeLoader *loader)
497   : QQmlDataBlob(url, type, loader), m_importCache(loader)
498 {
499 }
500 
~Blob()501 QQmlTypeLoader::Blob::~Blob()
502 {
503 }
504 
fetchQmldir(const QUrl & url,PendingImportPtr import,int priority,QList<QQmlError> * errors)505 bool QQmlTypeLoader::Blob::fetchQmldir(const QUrl &url, PendingImportPtr import, int priority, QList<QQmlError> *errors)
506 {
507     QQmlRefPointer<QQmlQmldirData> data = typeLoader()->getQmldir(url);
508 
509     data->setImport(this, std::move(import));
510     data->setPriority(this, priority);
511 
512     if (data->status() == Error) {
513         // This qmldir must not exist - which is not an error
514         return true;
515     } else if (data->status() == Complete) {
516         // This data is already available
517         return qmldirDataAvailable(data, errors);
518     }
519 
520     // Wait for this data to become available
521     addDependency(data.data());
522     return true;
523 }
524 
updateQmldir(const QQmlRefPointer<QQmlQmldirData> & data,PendingImportPtr import,QList<QQmlError> * errors)525 bool QQmlTypeLoader::Blob::updateQmldir(const QQmlRefPointer<QQmlQmldirData> &data, PendingImportPtr import, QList<QQmlError> *errors)
526 {
527     QString qmldirIdentifier = data->urlString();
528     QString qmldirUrl = qmldirIdentifier.left(qmldirIdentifier.lastIndexOf(QLatin1Char('/')) + 1);
529 
530     typeLoader()->setQmldirContent(qmldirIdentifier, data->content());
531 
532     if (!m_importCache.updateQmldirContent(typeLoader()->importDatabase(), import->uri, import->qualifier, qmldirIdentifier, qmldirUrl, errors))
533         return false;
534 
535     if (!loadImportDependencies(import, qmldirIdentifier, errors))
536         return false;
537 
538     import->priority = data->priority(this);
539 
540     // Release this reference at destruction
541     m_qmldirs << data;
542 
543     if (!import->qualifier.isEmpty()) {
544         // Does this library contain any qualified scripts?
545         QUrl libraryUrl(qmldirUrl);
546         const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(qmldirIdentifier);
547         const auto qmldirScripts = qmldir.scripts();
548         for (const QQmlDirParser::Script &script : qmldirScripts) {
549             QUrl scriptUrl = libraryUrl.resolved(QUrl(script.fileName));
550             QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl);
551             addDependency(blob.data());
552 
553             scriptImported(blob, import->location, script.nameSpace, import->qualifier);
554         }
555     }
556 
557     return true;
558 }
559 
addImport(const QV4::CompiledData::Import * import,QList<QQmlError> * errors)560 bool QQmlTypeLoader::Blob::addImport(const QV4::CompiledData::Import *import, QList<QQmlError> *errors)
561 {
562     return addImport(std::make_shared<PendingImport>(this, import), errors);
563 }
564 
addImport(QQmlTypeLoader::Blob::PendingImportPtr import,QList<QQmlError> * errors)565 bool QQmlTypeLoader::Blob::addImport(QQmlTypeLoader::Blob::PendingImportPtr import, QList<QQmlError> *errors)
566 {
567     Q_ASSERT(errors);
568 
569     QQmlImportDatabase *importDatabase = typeLoader()->importDatabase();
570 
571     if (import->type == QV4::CompiledData::Import::ImportScript) {
572         QUrl scriptUrl = finalUrl().resolved(QUrl(import->uri));
573         QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl);
574         addDependency(blob.data());
575 
576         scriptImported(blob, import->location, import->qualifier, QString());
577     } else if (import->type == QV4::CompiledData::Import::ImportLibrary) {
578         QString qmldirFilePath;
579         QString qmldirUrl;
580 
581         const QQmlImports::LocalQmldirResult qmldirResult = m_importCache.locateLocalQmldir(
582                     importDatabase, import->uri, import->majorVersion, import->minorVersion,
583                     &qmldirFilePath, &qmldirUrl);
584         if (qmldirResult == QQmlImports::QmldirFound) {
585             // This is a local library import
586             if (!m_importCache.addLibraryImport(importDatabase, import->uri, import->qualifier, import->majorVersion,
587                                           import->minorVersion, qmldirFilePath, qmldirUrl, false, errors))
588                 return false;
589 
590             if (!loadImportDependencies(import, qmldirFilePath, errors))
591                 return false;
592 
593             if (!import->qualifier.isEmpty()) {
594                 // Does this library contain any qualified scripts?
595                 QUrl libraryUrl(qmldirUrl);
596                 const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(qmldirFilePath);
597                 const auto qmldirScripts = qmldir.scripts();
598                 for (const QQmlDirParser::Script &script : qmldirScripts) {
599                     QUrl scriptUrl = libraryUrl.resolved(QUrl(script.fileName));
600                     QQmlRefPointer<QQmlScriptBlob> blob = typeLoader()->getScript(scriptUrl);
601                     addDependency(blob.data());
602 
603                     scriptImported(blob, import->location, script.nameSpace, import->qualifier);
604                 }
605             }
606         } else if (
607                 // Major version of module already registered:
608                 // We believe that the registration is complete.
609                 QQmlMetaType::typeModule(import->uri, import->majorVersion)
610 
611                 // Otherwise, try to register further module types.
612                 || (qmldirResult != QQmlImports::QmldirInterceptedToRemote
613                     && QQmlMetaType::qmlRegisterModuleTypes(import->uri, import->majorVersion))
614 
615                 // Otherwise, there is no way to register any further types.
616                 // Try with any module of that name.
617                 || QQmlMetaType::isAnyModule(import->uri)) {
618 
619             if (!m_importCache.addLibraryImport(
620                         importDatabase, import->uri, import->qualifier, import->majorVersion,
621                         import->minorVersion, QString(), QString(), false, errors)) {
622                 return false;
623             }
624         } else {
625             // We haven't yet resolved this import
626             m_unresolvedImports << import;
627 
628             QQmlAbstractUrlInterceptor *interceptor = typeLoader()->engine()->urlInterceptor();
629 
630             // Query any network import paths for this library.
631             // Interceptor might redirect local paths.
632             QStringList remotePathList = importDatabase->importPathList(
633                         interceptor ? QQmlImportDatabase::LocalOrRemote
634                                     : QQmlImportDatabase::Remote);
635             if (!remotePathList.isEmpty()) {
636                 // Add this library and request the possible locations for it
637                 if (!m_importCache.addLibraryImport(importDatabase, import->uri, import->qualifier, import->majorVersion,
638                                               import->minorVersion, QString(), QString(), true, errors))
639                     return false;
640 
641                 // Probe for all possible locations
642                 int priority = 0;
643                 const QStringList qmlDirPaths = QQmlImports::completeQmldirPaths(import->uri, remotePathList, import->majorVersion, import->minorVersion);
644                 for (const QString &qmldirPath : qmlDirPaths) {
645                     if (interceptor) {
646                         QUrl url = interceptor->intercept(
647                                     QQmlImports::urlFromLocalFileOrQrcOrUrl(qmldirPath),
648                                     QQmlAbstractUrlInterceptor::QmldirFile);
649                         if (!QQmlFile::isLocalFile(url)
650                                 && !fetchQmldir(url, import, ++priority, errors)) {
651                             return false;
652                         }
653                     } else if (!fetchQmldir(QUrl(qmldirPath), import, ++priority, errors)) {
654                         return false;
655                     }
656 
657                 }
658             }
659         }
660     } else {
661         Q_ASSERT(import->type == QV4::CompiledData::Import::ImportFile);
662 
663         bool incomplete = false;
664 
665         QUrl importUrl(import->uri);
666         QString path = importUrl.path();
667         path.append(QLatin1String(path.endsWith(QLatin1Char('/')) ? "qmldir" : "/qmldir"));
668         importUrl.setPath(path);
669         QUrl qmldirUrl = finalUrl().resolved(importUrl);
670         if (!QQmlImports::isLocal(qmldirUrl)) {
671             // This is a remote file; the import is currently incomplete
672             incomplete = true;
673         }
674 
675         if (!m_importCache.addFileImport(importDatabase, import->uri, import->qualifier, import->majorVersion,
676                                    import->minorVersion, incomplete, errors))
677             return false;
678 
679         if (incomplete) {
680             if (!fetchQmldir(qmldirUrl, import, 1, errors))
681                 return false;
682         }
683     }
684 
685     return true;
686 }
687 
dependencyComplete(QQmlDataBlob * blob)688 void QQmlTypeLoader::Blob::dependencyComplete(QQmlDataBlob *blob)
689 {
690     if (blob->type() == QQmlDataBlob::QmldirFile) {
691         QQmlQmldirData *data = static_cast<QQmlQmldirData *>(blob);
692 
693         PendingImportPtr import = data->import(this);
694 
695         QList<QQmlError> errors;
696         if (!qmldirDataAvailable(data, &errors)) {
697             Q_ASSERT(errors.size());
698             QQmlError error(errors.takeFirst());
699             error.setUrl(m_importCache.baseUrl());
700             error.setLine(qmlConvertSourceCoordinate<quint32, int>(import->location.line));
701             error.setColumn(qmlConvertSourceCoordinate<quint32, int>(import->location.column));
702             errors.prepend(error); // put it back on the list after filling out information.
703             setError(errors);
704         }
705     }
706 }
707 
loadImportDependencies(PendingImportPtr currentImport,const QString & qmldirUri,QList<QQmlError> * errors)708 bool QQmlTypeLoader::Blob::loadImportDependencies(PendingImportPtr currentImport, const QString &qmldirUri, QList<QQmlError> *errors)
709 {
710     const QQmlTypeLoaderQmldirContent qmldir = typeLoader()->qmldirContent(qmldirUri);
711     for (const QString &implicitImports: qmldir.imports()) {
712         auto dependencyImport = std::make_shared<PendingImport>();
713         dependencyImport->uri = implicitImports;
714         dependencyImport->qualifier = currentImport->qualifier;
715         dependencyImport->majorVersion = currentImport->majorVersion;
716         dependencyImport->minorVersion = currentImport->minorVersion;
717         if (!addImport(dependencyImport, errors))
718             return false;
719     }
720     return true;
721 }
722 
isDebugging() const723 bool QQmlTypeLoader::Blob::isDebugging() const
724 {
725     return typeLoader()->engine()->handle()->debugger() != nullptr;
726 }
727 
diskCacheEnabled() const728 bool QQmlTypeLoader::Blob::diskCacheEnabled() const
729 {
730     return (!disableDiskCache() && !isDebugging()) || forceDiskCache();
731 }
732 
qmldirDataAvailable(const QQmlRefPointer<QQmlQmldirData> & data,QList<QQmlError> * errors)733 bool QQmlTypeLoader::Blob::qmldirDataAvailable(const QQmlRefPointer<QQmlQmldirData> &data, QList<QQmlError> *errors)
734 {
735     PendingImportPtr import = data->import(this);
736     data->setImport(this, nullptr);
737 
738     int priority = data->priority(this);
739     data->setPriority(this, 0);
740 
741     if (import) {
742         // Do we need to resolve this import?
743         const bool resolve = (import->priority == 0) || (import->priority > priority);
744 
745         if (resolve) {
746             // This is the (current) best resolution for this import
747             if (!updateQmldir(data, import, errors)) {
748                 return false;
749             }
750 
751             import->priority = priority;
752             return true;
753         }
754     }
755 
756     return true;
757 }
758 
759 /*!
760 Constructs a new type loader that uses the given \a engine.
761 */
QQmlTypeLoader(QQmlEngine * engine)762 QQmlTypeLoader::QQmlTypeLoader(QQmlEngine *engine)
763     : m_engine(engine)
764     , m_thread(new QQmlTypeLoaderThread(this))
765     , m_mutex(m_thread->mutex())
766     , m_typeCacheTrimThreshold(TYPELOADER_MINIMUM_TRIM_THRESHOLD)
767 {
768 }
769 
770 /*!
771 Destroys the type loader, first clearing the cache of any information about
772 loaded files.
773 */
~QQmlTypeLoader()774 QQmlTypeLoader::~QQmlTypeLoader()
775 {
776     // Stop the loader thread before releasing resources
777     shutdownThread();
778 
779     clearCache();
780 
781     invalidate();
782 }
783 
importDatabase() const784 QQmlImportDatabase *QQmlTypeLoader::importDatabase() const
785 {
786     return &QQmlEnginePrivate::get(engine())->importDatabase;
787 }
788 
normalize(const QUrl & unNormalizedUrl)789 QUrl QQmlTypeLoader::normalize(const QUrl &unNormalizedUrl)
790 {
791     QUrl normalized(unNormalizedUrl);
792     if (normalized.scheme() == QLatin1String("qrc"))
793         normalized.setHost(QString()); // map qrc:///a.qml to qrc:/a.qml
794     return normalized;
795 }
796 
797 /*!
798 Returns a QQmlTypeData for the specified \a url.  The QQmlTypeData may be cached.
799 */
getType(const QUrl & unNormalizedUrl,Mode mode)800 QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QUrl &unNormalizedUrl, Mode mode)
801 {
802     Q_ASSERT(!unNormalizedUrl.isRelative() &&
803             (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
804              !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
805 
806     const QUrl url = normalize(unNormalizedUrl);
807 
808     LockHolder<QQmlTypeLoader> holder(this);
809 
810     QQmlTypeData *typeData = m_typeCache.value(url);
811 
812     if (!typeData) {
813         // Trim before adding the new type, so that we don't immediately trim it away
814         if (m_typeCache.size() >= m_typeCacheTrimThreshold)
815             trimCache();
816 
817         typeData = new QQmlTypeData(url, this);
818         // TODO: if (compiledData == 0), is it safe to omit this insertion?
819         m_typeCache.insert(url, typeData);
820         QQmlMetaType::CachedUnitLookupError error = QQmlMetaType::CachedUnitLookupError::NoError;
821         if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(typeData->url(), &error)) {
822             QQmlTypeLoader::loadWithCachedUnit(typeData, cachedUnit, mode);
823         } else {
824             typeData->setCachedUnitStatus(error);
825             QQmlTypeLoader::load(typeData, mode);
826         }
827     } else if ((mode == PreferSynchronous || mode == Synchronous) && QQmlFile::isSynchronous(url)) {
828         // this was started Asynchronous, but we need to force Synchronous
829         // completion now (if at all possible with this type of URL).
830 
831         if (!m_thread->isThisThread()) {
832             // this only works when called directly from the UI thread, but not
833             // when recursively called on the QML thread via resolveTypes()
834 
835             while (!typeData->isCompleteOrError()) {
836                 unlock();
837                 m_thread->waitForNextMessage();
838                 lock();
839             }
840         }
841     }
842 
843     return typeData;
844 }
845 
846 /*!
847 Returns a QQmlTypeData for the given \a data with the provided base \a url.  The
848 QQmlTypeData will not be cached.
849 */
getType(const QByteArray & data,const QUrl & url,Mode mode)850 QQmlRefPointer<QQmlTypeData> QQmlTypeLoader::getType(const QByteArray &data, const QUrl &url, Mode mode)
851 {
852     LockHolder<QQmlTypeLoader> holder(this);
853 
854     QQmlTypeData *typeData = new QQmlTypeData(url, this);
855     QQmlTypeLoader::loadWithStaticData(typeData, data, mode);
856 
857     return QQmlRefPointer<QQmlTypeData>(typeData, QQmlRefPointer<QQmlTypeData>::Adopt);
858 }
859 
860 /*!
861 Return a QQmlScriptBlob for \a url.  The QQmlScriptData may be cached.
862 */
getScript(const QUrl & unNormalizedUrl)863 QQmlRefPointer<QQmlScriptBlob> QQmlTypeLoader::getScript(const QUrl &unNormalizedUrl)
864 {
865     Q_ASSERT(!unNormalizedUrl.isRelative() &&
866             (QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl).isEmpty() ||
867              !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(unNormalizedUrl))));
868 
869     const QUrl url = normalize(unNormalizedUrl);
870 
871     LockHolder<QQmlTypeLoader> holder(this);
872 
873     QQmlScriptBlob *scriptBlob = m_scriptCache.value(url);
874 
875     if (!scriptBlob) {
876         scriptBlob = new QQmlScriptBlob(url, this);
877         m_scriptCache.insert(url, scriptBlob);
878 
879         QQmlMetaType::CachedUnitLookupError error;
880         if (const QV4::CompiledData::Unit *cachedUnit = QQmlMetaType::findCachedCompilationUnit(scriptBlob->url(), &error)) {
881             QQmlTypeLoader::loadWithCachedUnit(scriptBlob, cachedUnit);
882         } else {
883             scriptBlob->setCachedUnitStatus(error);
884             QQmlTypeLoader::load(scriptBlob);
885         }
886     }
887 
888     return scriptBlob;
889 }
890 
891 /*!
892 Returns a QQmlQmldirData for \a url.  The QQmlQmldirData may be cached.
893 */
getQmldir(const QUrl & url)894 QQmlRefPointer<QQmlQmldirData> QQmlTypeLoader::getQmldir(const QUrl &url)
895 {
896     Q_ASSERT(!url.isRelative() &&
897             (QQmlFile::urlToLocalFileOrQrc(url).isEmpty() ||
898              !QDir::isRelativePath(QQmlFile::urlToLocalFileOrQrc(url))));
899     LockHolder<QQmlTypeLoader> holder(this);
900 
901     QQmlQmldirData *qmldirData = m_qmldirCache.value(url);
902 
903     if (!qmldirData) {
904         qmldirData = new QQmlQmldirData(url, this);
905         m_qmldirCache.insert(url, qmldirData);
906         QQmlTypeLoader::load(qmldirData);
907     }
908 
909     return qmldirData;
910 }
911 
912 /*!
913 Returns the absolute filename of path via a directory cache.
914 Returns a empty string if the path does not exist.
915 
916 Why a directory cache?  QML checks for files in many paths with
917 invalid directories.  By caching whether a directory exists
918 we avoid many stats.  We also cache the files' existence in the
919 directory, for the same reason.
920 */
absoluteFilePath(const QString & path)921 QString QQmlTypeLoader::absoluteFilePath(const QString &path)
922 {
923     if (path.isEmpty())
924         return QString();
925     if (path.at(0) == QLatin1Char(':')) {
926         // qrc resource
927         QFileInfo fileInfo(path);
928         return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
929     } else if (path.count() > 3 && path.at(3) == QLatin1Char(':') &&
930                path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) {
931         // qrc resource url
932         QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
933         return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
934     }
935 #if defined(Q_OS_ANDROID)
936     else if (path.count() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/') &&
937            path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) {
938         // assets resource url
939         QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
940         return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
941     } else if (path.count() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/') &&
942            path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) {
943         // content url
944         QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(path));
945         return fileInfo.isFile() ? fileInfo.absoluteFilePath() : QString();
946     }
947 #endif
948 
949     int lastSlash = path.lastIndexOf(QLatin1Char('/'));
950     QString dirPath(path.left(lastSlash));
951 
952     LockHolder<QQmlTypeLoader> holder(this);
953     if (!m_importDirCache.contains(dirPath)) {
954         bool exists = QDir(dirPath).exists();
955         QCache<QString, bool> *entry = exists ? new QCache<QString, bool> : nullptr;
956         m_importDirCache.insert(dirPath, entry);
957     }
958     QCache<QString, bool> *fileSet = m_importDirCache.object(dirPath);
959     if (!fileSet)
960         return QString();
961 
962     QString absoluteFilePath;
963     QString fileName(path.mid(lastSlash+1, path.length()-lastSlash-1));
964 
965     bool *value = fileSet->object(fileName);
966     if (value) {
967         if (*value)
968             absoluteFilePath = path;
969     } else {
970         bool exists = QFile::exists(path);
971         fileSet->insert(fileName, new bool(exists));
972         if (exists)
973             absoluteFilePath = path;
974     }
975 
976     if (absoluteFilePath.length() > 2 && absoluteFilePath.at(0) != QLatin1Char('/') && absoluteFilePath.at(1) != QLatin1Char(':'))
977         absoluteFilePath = QFileInfo(absoluteFilePath).absoluteFilePath();
978 
979     return absoluteFilePath;
980 }
981 
fileExists(const QString & path,const QString & file)982 bool QQmlTypeLoader::fileExists(const QString &path, const QString &file)
983 {
984     const QChar nullChar(QChar::Null);
985     if (path.isEmpty() || path.contains(nullChar) || file.isEmpty() || file.contains(nullChar))
986         return false;
987 
988     Q_ASSERT(path.endsWith(QLatin1Char('/')));
989 
990     LockHolder<QQmlTypeLoader> holder(this);
991     QCache<QString, bool> *fileSet = m_importDirCache.object(path);
992     if (fileSet) {
993         if (bool *value = fileSet->object(file))
994             return *value;
995     } else if (m_importDirCache.contains(path)) {
996         // explicit nullptr in cache
997         return false;
998     }
999 
1000     auto addToCache = [&](const QFileInfo &fileInfo) {
1001         if (!fileSet) {
1002             fileSet = fileInfo.dir().exists() ? new QCache<QString, bool> : nullptr;
1003             m_importDirCache.insert(path, fileSet);
1004             if (!fileSet)
1005                 return false;
1006         }
1007 
1008         const bool exists = fileInfo.exists();
1009         fileSet->insert(file, new bool(exists));
1010         return exists;
1011     };
1012 
1013     if (path.at(0) == QLatin1Char(':')) {
1014         // qrc resource
1015         return addToCache(QFileInfo(path + file));
1016     }
1017 
1018     if (path.count() > 3 && path.at(3) == QLatin1Char(':')
1019             && path.startsWith(QLatin1String("qrc"), Qt::CaseInsensitive)) {
1020         // qrc resource url
1021         return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file)));
1022     }
1023 
1024 #if defined(Q_OS_ANDROID)
1025     if (path.count() > 7 && path.at(6) == QLatin1Char(':') && path.at(7) == QLatin1Char('/')
1026             && path.startsWith(QLatin1String("assets"), Qt::CaseInsensitive)) {
1027         // assets resource url
1028         return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file)));
1029     }
1030 
1031     if (path.count() > 8 && path.at(7) == QLatin1Char(':') && path.at(8) == QLatin1Char('/')
1032             && path.startsWith(QLatin1String("content"), Qt::CaseInsensitive)) {
1033         // content url
1034         return addToCache(QFileInfo(QQmlFile::urlToLocalFileOrQrc(path + file)));
1035     }
1036 #endif
1037 
1038     return addToCache(QFileInfo(path + file));
1039 }
1040 
1041 
1042 /*!
1043 Returns true if the path is a directory via a directory cache.  Cache is
1044 shared with absoluteFilePath().
1045 */
directoryExists(const QString & path)1046 bool QQmlTypeLoader::directoryExists(const QString &path)
1047 {
1048     if (path.isEmpty())
1049         return false;
1050 
1051     bool isResource = path.at(0) == QLatin1Char(':');
1052 #if defined(Q_OS_ANDROID)
1053     isResource = isResource || path.startsWith(QLatin1String("assets:/")) || path.startsWith(QLatin1String("content:/"));
1054 #endif
1055 
1056     if (isResource) {
1057         // qrc resource
1058         QFileInfo fileInfo(path);
1059         return fileInfo.exists() && fileInfo.isDir();
1060     }
1061 
1062     int length = path.length();
1063     if (path.endsWith(QLatin1Char('/')))
1064         --length;
1065     QString dirPath(path.left(length));
1066 
1067     LockHolder<QQmlTypeLoader> holder(this);
1068     if (!m_importDirCache.contains(dirPath)) {
1069         bool exists = QDir(dirPath).exists();
1070         QCache<QString, bool> *files = exists ? new QCache<QString, bool> : nullptr;
1071         m_importDirCache.insert(dirPath, files);
1072     }
1073 
1074     QCache<QString, bool> *fileSet = m_importDirCache.object(dirPath);
1075     return fileSet != nullptr;
1076 }
1077 
1078 
1079 /*!
1080 Return a QQmlTypeLoaderQmldirContent for absoluteFilePath.  The QQmlTypeLoaderQmldirContent may be cached.
1081 
1082 \a filePath is a local file path.
1083 
1084 It can also be a remote path for a remote directory import, but it will have been cached by now in this case.
1085 */
qmldirContent(const QString & filePathIn)1086 const QQmlTypeLoaderQmldirContent QQmlTypeLoader::qmldirContent(const QString &filePathIn)
1087 {
1088     LockHolder<QQmlTypeLoader> holder(this);
1089 
1090     QString filePath;
1091 
1092     // Try to guess if filePathIn is already a URL. This is necessarily fragile, because
1093     // - paths can contain ':', which might make them appear as URLs with schemes.
1094     // - windows drive letters appear as schemes (thus "< 2" below).
1095     // - a "file:" URL is equivalent to the respective file, but will be treated differently.
1096     // Yet, this heuristic is the best we can do until we pass more structured information here,
1097     // for example a QUrl also for local files.
1098     QUrl url(filePathIn);
1099     if (url.scheme().length() < 2) {
1100         filePath = filePathIn;
1101     } else {
1102         filePath = QQmlFile::urlToLocalFileOrQrc(url);
1103         if (filePath.isEmpty()) { // Can't load the remote here, but should be cached
1104             if (auto entry = m_importQmlDirCache.value(filePathIn))
1105                 return **entry;
1106             else
1107                 return QQmlTypeLoaderQmldirContent();
1108         }
1109     }
1110 
1111     QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(filePath);
1112     if (val)
1113         return **val;
1114     QQmlTypeLoaderQmldirContent *qmldir = new QQmlTypeLoaderQmldirContent;
1115 
1116 #define ERROR(description) { QQmlError e; e.setDescription(description); qmldir->setError(e); }
1117 #define NOT_READABLE_ERROR QString(QLatin1String("module \"$$URI$$\" definition \"%1\" not readable"))
1118 #define CASE_MISMATCH_ERROR QString(QLatin1String("cannot load module \"$$URI$$\": File name case mismatch for \"%1\""))
1119 
1120     QFile file(filePath);
1121     if (!QQml_isFileCaseCorrect(filePath)) {
1122         ERROR(CASE_MISMATCH_ERROR.arg(filePath));
1123     } else if (file.open(QFile::ReadOnly)) {
1124         QByteArray data = file.readAll();
1125         qmldir->setContent(filePath, QString::fromUtf8(data));
1126     } else {
1127         ERROR(NOT_READABLE_ERROR.arg(filePath));
1128     }
1129 
1130 #undef ERROR
1131 #undef NOT_READABLE_ERROR
1132 #undef CASE_MISMATCH_ERROR
1133 
1134     m_importQmlDirCache.insert(filePath, qmldir);
1135     return *qmldir;
1136 }
1137 
setQmldirContent(const QString & url,const QString & content)1138 void QQmlTypeLoader::setQmldirContent(const QString &url, const QString &content)
1139 {
1140     QQmlTypeLoaderQmldirContent *qmldir;
1141     QQmlTypeLoaderQmldirContent **val = m_importQmlDirCache.value(url);
1142     if (val) {
1143         qmldir = *val;
1144     } else {
1145         qmldir = new QQmlTypeLoaderQmldirContent;
1146         m_importQmlDirCache.insert(url, qmldir);
1147     }
1148 
1149     if (!qmldir->hasContent())
1150         qmldir->setContent(url, content);
1151 }
1152 
1153 /*!
1154 Clears cached information about loaded files, including any type data, scripts
1155 and qmldir information.
1156 */
clearCache()1157 void QQmlTypeLoader::clearCache()
1158 {
1159     for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end; ++iter)
1160         (*iter)->release();
1161     for (ScriptCache::Iterator iter = m_scriptCache.begin(), end = m_scriptCache.end(); iter != end; ++iter)
1162         (*iter)->release();
1163     for (QmldirCache::Iterator iter = m_qmldirCache.begin(), end = m_qmldirCache.end(); iter != end; ++iter)
1164         (*iter)->release();
1165 
1166     qDeleteAll(m_importQmlDirCache);
1167 
1168     m_typeCache.clear();
1169     m_typeCacheTrimThreshold = TYPELOADER_MINIMUM_TRIM_THRESHOLD;
1170     m_scriptCache.clear();
1171     m_qmldirCache.clear();
1172     m_importDirCache.clear();
1173     m_importQmlDirCache.clear();
1174     QQmlMetaType::freeUnusedTypesAndCaches();
1175 }
1176 
updateTypeCacheTrimThreshold()1177 void QQmlTypeLoader::updateTypeCacheTrimThreshold()
1178 {
1179     int size = m_typeCache.size();
1180     if (size > m_typeCacheTrimThreshold)
1181         m_typeCacheTrimThreshold = size * 2;
1182     if (size < m_typeCacheTrimThreshold / 2)
1183         m_typeCacheTrimThreshold = qMax(size * 2, TYPELOADER_MINIMUM_TRIM_THRESHOLD);
1184 }
1185 
trimCache()1186 void QQmlTypeLoader::trimCache()
1187 {
1188     while (true) {
1189         QList<TypeCache::Iterator> unneededTypes;
1190         for (TypeCache::Iterator iter = m_typeCache.begin(), end = m_typeCache.end(); iter != end; ++iter)  {
1191             QQmlTypeData *typeData = iter.value();
1192 
1193             // typeData->m_compiledData may be set early on in the proccess of loading a file, so
1194             // it's important to check the general loading status of the typeData before making any
1195             // other decisions.
1196             if (typeData->count() == 1 && (typeData->isError() || typeData->isComplete())
1197                     && (!typeData->m_compiledData || typeData->m_compiledData->count() == 1)) {
1198                 // There are no live objects of this type
1199                 unneededTypes.append(iter);
1200             }
1201         }
1202 
1203         if (unneededTypes.isEmpty())
1204             break;
1205 
1206         while (!unneededTypes.isEmpty()) {
1207             TypeCache::Iterator iter = unneededTypes.takeLast();
1208 
1209             iter.value()->release();
1210             m_typeCache.erase(iter);
1211         }
1212     }
1213 
1214     updateTypeCacheTrimThreshold();
1215 
1216     QQmlMetaType::freeUnusedTypesAndCaches();
1217 
1218     // TODO: release any scripts which are no longer referenced by any types
1219 }
1220 
isTypeLoaded(const QUrl & url) const1221 bool QQmlTypeLoader::isTypeLoaded(const QUrl &url) const
1222 {
1223     LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this));
1224     return m_typeCache.contains(url);
1225 }
1226 
isScriptLoaded(const QUrl & url) const1227 bool QQmlTypeLoader::isScriptLoaded(const QUrl &url) const
1228 {
1229     LockHolder<QQmlTypeLoader> holder(const_cast<QQmlTypeLoader *>(this));
1230     return m_scriptCache.contains(url);
1231 }
1232 
1233 QT_END_NAMESPACE
1234