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